Android 優(yōu)化之存儲(chǔ)優(yōu)化的實(shí)現(xiàn)
交換數(shù)據(jù)格式
Google 推出的 Protocal Buffers 是一種更輕便高效的存儲(chǔ)結(jié)構(gòu),但消耗內(nèi)存較大。
FlatBuffers 同樣由 Google 推出,專注性能,適合移動(dòng)端。占用存儲(chǔ)比 Protocal 要大。
SharePreferences 優(yōu)化
- 當(dāng) SharedPreferences 文件還沒(méi)有被加載到內(nèi)存時(shí),調(diào)用 getSharedPreferences 方法會(huì)初始化文件并讀入內(nèi)存,這容易導(dǎo)致 耗時(shí)更長(zhǎng)。
- Editor 的 commit 或者 apply 方法的區(qū)別在于同步寫(xiě)入和異步 寫(xiě)入,以及是否需要返回值。在不需要返回值的情況下,使用 apply 方法可以極大提高性能。
- SharedPreferences 類(lèi) 中的 commitToMemory() 會(huì)鎖定 SharedPreference 對(duì)象,put() 和 getEditor() 方法會(huì)鎖定 Editor 對(duì)象,在寫(xiě)入磁盤(pán)時(shí)更會(huì)鎖定一個(gè)寫(xiě)入鎖。因此,最好的優(yōu)化方法就是避免頻繁地讀寫(xiě) SharedPreferences,減少無(wú)謂的調(diào)用。對(duì)于 SharedPreferences 的批量操作,最好先獲取一個(gè) editor 進(jìn)行批量操作,然后調(diào)用 apply 方法。
Bitmap 解碼
- 4.4 以上 decodeFile 內(nèi)部沒(méi)有使用緩存,效率不高。要使用 decodeStream,同時(shí)傳入的文件流為 BufferedInputStream。
- decodeResource 同樣存在性能問(wèn)題,用 decodeResourceStream。
數(shù)據(jù)庫(kù)優(yōu)化
1、使用 StringBuilder 代替 String
2、查詢時(shí)返回更少的結(jié)果集及更少的字段
查詢時(shí)只取需要的字段和結(jié)果集,更多的結(jié)果集會(huì)消耗更多的時(shí)間及內(nèi)存,更多的字段會(huì)導(dǎo)致更多的內(nèi)存消耗。
3、少用 cursor.getColumnIndex
根據(jù)性能調(diào)優(yōu)過(guò)程中的觀察 cursor.getColumnIndex 的時(shí)間消耗跟 cursor.getInt 相差無(wú)幾。可以在建表的時(shí)候用 static 變量記住某列的 index,直接調(diào)用相應(yīng) index 而不是每次查詢。
4、異步線程
Android 中數(shù)據(jù)不多時(shí)表查詢可能耗時(shí)不多,不會(huì)導(dǎo)致 ANR,不過(guò)大于 100ms 時(shí)同樣會(huì)讓用戶感覺(jué)到延時(shí)和卡頓,可以放在線程中運(yùn)行,但 sqlite 在并發(fā)方面存在局限,多線程控制較麻煩,這時(shí)候可使用單線程池,在任務(wù)中執(zhí)行 db 操作,通過(guò) handler 返回結(jié)果和 UI 線程交互,既不會(huì)影響 UI 線程,同時(shí)也能防止并發(fā)帶來(lái)的異常。
5、SQLiteOpenHelper 維持一個(gè)單例
因?yàn)?SQLite 對(duì)多線程的支持并不是很完善,如果兩個(gè)線程同時(shí)操作數(shù)據(jù)庫(kù),因?yàn)閿?shù)據(jù)庫(kù)被另一個(gè)線程占用, 這種情況下會(huì)報(bào)“Database is locked” 的異常。所以在數(shù)據(jù)庫(kù)管理類(lèi)中使用單例模式,就可以保證無(wú)論在哪個(gè)線程中獲取數(shù)據(jù)庫(kù)對(duì)象,都是同一個(gè)。
最好的方法是所有的數(shù)據(jù)庫(kù)操作統(tǒng)一到同一個(gè)線程隊(duì)列管理,而業(yè)務(wù)層使用緩存同步,這樣可以完全避免多線程操作數(shù)據(jù)庫(kù)導(dǎo)致的不同步和死鎖問(wèn)題。
6、Application 中初始化
- 使用 Application 的 Context 創(chuàng)建數(shù)據(jù)庫(kù),在 Application 生命周期結(jié)束時(shí)再關(guān)閉。
- 在應(yīng)用啟動(dòng)過(guò)程中最先初始化完數(shù)據(jù)庫(kù),避免進(jìn)入應(yīng)用后再初始化導(dǎo)致相關(guān)操作時(shí)間變長(zhǎng)。
7、少用 AUTOINCREMENT
主鍵加上 AUTOINCREMENT 后,可以保證主鍵嚴(yán)格遞增,但并不能保證每次都加 1,因?yàn)樵诓迦胧『螅〉男刑?hào)不會(huì)被復(fù)用,會(huì)造成主鍵有間隔,繼而使 INSERT 耗時(shí) 1 倍以上。
這個(gè) AUTOINCREMENT 關(guān)鍵詞會(huì)增加 CPU,內(nèi)存,磁盤(pán)空間和磁盤(pán) I/O 的負(fù)擔(dān),所以 盡量不要用,除非必需。通常情況下都不是必需的。
事務(wù)
使用事務(wù)的兩大好處是原子提交和更優(yōu)性能:
- 原子提交:意味著同一事務(wù)內(nèi)的所有修改要么都完成要么都不做,如果某個(gè)修改失敗,會(huì)自動(dòng)回滾使得所有修改不生效。
- 更優(yōu)性能:Sqlite 默認(rèn)會(huì)為每個(gè)插入、更新操作創(chuàng)建一個(gè)事務(wù),并且在每次插入、更新后立即提交。這樣如果連續(xù)插入 100 次數(shù)據(jù)實(shí)際是創(chuàng)建事務(wù)、執(zhí)行語(yǔ)句、提交這個(gè)過(guò)程被重復(fù)執(zhí)行了 100 次。如果顯式的創(chuàng)建事務(wù),這個(gè)過(guò)程只做一次,通過(guò)這種一次性事務(wù)可以使得性能大幅提升。尤其當(dāng)數(shù)據(jù)庫(kù)位于 sd 卡時(shí),時(shí)間上能節(jié)省兩個(gè)數(shù)量級(jí)左右。
主要三個(gè)方法:beginTransaction,setTransactionSuccessful,endTransaction。
SQLiteStatement
使用 Android 系統(tǒng)提供的 SQLiteStatement 來(lái)插入數(shù)據(jù),在性能上有一定的提高,并且也解決了 SQL 注入的問(wèn)題。
SQLiteStatement statement = dbOpenHelper.getWritableDatabase().compileStatement("INSERT INTO EMPERORS(name, dynasty, start_year) values(?,?,?)");
statement.clearBindings();
statement.bindString(1, "Max");
statement.bindString(2, "Luk");
statement.bindString(3, "1998");
statement.executeInsert();
SQLiteStatement 只能插入一個(gè)表中的數(shù)據(jù),在插入前要清除上一次的數(shù)據(jù)。
索引
索引就像書(shū)本的目錄,目錄可以快速找到所在頁(yè)數(shù),數(shù)據(jù)庫(kù)中索引可以幫助快速找到數(shù)據(jù),而不用全表掃描,合適的索引可以大大提高數(shù)據(jù)庫(kù)查詢的效率。
優(yōu)點(diǎn):大大加快了數(shù)據(jù)庫(kù)檢索的速度,包括對(duì)單表查詢、連表查詢、分組查詢、排序查詢。經(jīng)常是一到兩個(gè)數(shù)量級(jí)的性能提升,且隨著數(shù)據(jù)數(shù)量級(jí)增長(zhǎng)。
缺點(diǎn):
- 索引的創(chuàng)建和維護(hù)存在消耗,索引會(huì)占用物理空間,且隨著數(shù)據(jù)量的增加而增加。
- 在對(duì)數(shù)據(jù)庫(kù)進(jìn)行增刪改時(shí)需要維護(hù)索引,所以會(huì)對(duì)增刪改的性能存在影響。
分類(lèi)
1、直接創(chuàng)建索引和間接創(chuàng)建索引
- 直接創(chuàng)建: 使用 sql 語(yǔ)句創(chuàng)建,Android 中可以在 SQLiteOpenHelper 的 onCreate 或是 onUpgrade 中直接 excuSql 創(chuàng)建語(yǔ)句,如
CREATE INDEX mycolumn_index ON mytable (myclumn) - 間接創(chuàng)建: 定義主鍵約束或者唯一性鍵約束,可以間接創(chuàng)建索引,主鍵默認(rèn)為唯一索引。
2、普通索引和唯一性索引
- 普通索引:
CREATEINDEXmycolumn_indexONmytable(myclumn) - 唯一性索引:保證在索引列中的全部數(shù)據(jù)是唯一的,對(duì)聚簇索引和非聚簇索引都可以使用,語(yǔ)句為
CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)
3、單個(gè)索引和復(fù)合索引
- 單個(gè)索引:索引建立語(yǔ)句中僅包含單個(gè)字段,如上面的普通索引和唯一性索引創(chuàng)建示例。
- 復(fù)合索引:又叫組合索引,在索引建立語(yǔ)句中同時(shí)包含多個(gè)字段,如
CREATEINDEXname_indexONusername(firstname,lastname),其中 firstname 為前導(dǎo)列。
4、聚簇索引和非聚簇索引 (聚集索引,群集索引)
- 聚簇索引:物理索引,與基表的物理順序相同,數(shù)據(jù)值的順序總是按照順序排列,如
CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn) WITH ALLOW_DUP_ROW,其中WITH ALLOW_DUP_ROW表示允許有重復(fù)記錄的聚簇索引 - 非聚簇索引:
CREATEUNCLUSTEREDINDEXmycolumn_cindexONmytable(mycolumn),索引默認(rèn)為非聚簇索引
使用場(chǎng)景
- 當(dāng)某字段數(shù)據(jù)更新頻率較低,查詢頻率較高,經(jīng)常有范圍查詢
(>, <, =,>=, <=)或order by、group by發(fā)生時(shí)建議使用索引。并且選擇度(一個(gè)字段中唯一值的數(shù)量 / 總的數(shù)量)越大,建索引越有優(yōu)勢(shì) - 經(jīng)常同時(shí)存取多列,且每列都含有重復(fù)值可考慮建立復(fù)合索引
使用規(guī)則
- 對(duì)于復(fù)合索引,把使用最頻繁的列做為前導(dǎo)列 (索引中第一個(gè)字段)。如果查詢時(shí)前導(dǎo)列不在查詢條件中則該復(fù)合索引不會(huì)被使用。如
create unique index PK_GRADE_CLASS on student (grade, class),select * from student where class = 2未使用到索引,select * from dept where grade = 3使用到了索引 - 避免對(duì)索引列進(jìn)行計(jì)算,對(duì) where 子句列的任何計(jì)算如果不能被編譯優(yōu)化,都會(huì)導(dǎo)致查詢時(shí)索引失效
select * from student where tochar(grade)='2 - 比較值避免使用 NULL
- 多表查詢時(shí)要注意是選擇合適的表做為內(nèi)表。連接條件要充份考慮帶有索引的表、行數(shù)多的表,內(nèi)外表的選擇可由公式:外層表中的匹配行數(shù) * 內(nèi)層表中每一次查找的次數(shù)確定,乘積最小為最佳方案。實(shí)際多表操作在被實(shí)際執(zhí)行前,查詢優(yōu)化器會(huì)根據(jù)連接條件,列出幾組可能的連接方案并從中找出系統(tǒng)開(kāi)銷(xiāo)最小的最佳方案
- 查詢列與索引列次序一致
- 用多表連接代替 EXISTS 子句
- 把過(guò)濾記錄數(shù)最多的條件放在最前面
- 善于使用存儲(chǔ)過(guò)程,它使 sql 變得更加靈活和高效 (Sqlite 不支持存儲(chǔ)過(guò)程)
其它通用優(yōu)化
- 經(jīng)常用的數(shù)據(jù)讀取后緩存起來(lái),以免多次重復(fù)讀寫(xiě)造成“寫(xiě)入放大”
- 子線程讀寫(xiě)數(shù)據(jù)
- ObjectOutputStream 在序列化磁盤(pán)時(shí),會(huì)把內(nèi)存中的每個(gè)對(duì)象保存到磁盤(pán),在保存對(duì)象的 時(shí)候,每個(gè)數(shù)據(jù)成員會(huì)帶來(lái)一次 I/O 操作。在 ObjectOutputStream 上面再封裝一個(gè)輸出流 ByteArrayOutputStream 或 BufferedOutputStream,先將對(duì)象序列化后的信息寫(xiě)到緩存區(qū)中,然后再一次性地寫(xiě)到磁盤(pán)上;相應(yīng)的,用 ByteArrayInputStream 或 BufferedInputStream 替代 ObjectInputStream。
- 合理選擇緩沖區(qū) Buffer 的大小。太小導(dǎo)致 I/O 操作次數(shù)增多,太大導(dǎo)致申請(qǐng)時(shí)間變長(zhǎng)。比如 4-8 KB。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android開(kāi)發(fā)學(xué)習(xí)路線圖
這篇文章主要介紹了Android開(kāi)發(fā)學(xué)習(xí)路線圖,本文從基礎(chǔ)、入門(mén)、進(jìn)階、高級(jí)等4個(gè)方面鋪開(kāi)學(xué)習(xí)路線圖,需要的朋友可以參考下2015-04-04
Android畫(huà)板開(kāi)發(fā)之添加文本文字
這篇文章主要為大家詳細(xì)介紹了Android畫(huà)板開(kāi)發(fā)之添加文本文字功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
Android開(kāi)發(fā)之基本控件和四種布局方式詳解
這篇文章主要介紹了Android開(kāi)發(fā)之基本控件和四種布局方式詳解的相關(guān)資料,非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
Android項(xiàng)目中實(shí)體類(lèi)entity的作用詳解
這篇文章主要介紹了Android項(xiàng)目中實(shí)體類(lèi)entity的作用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04
Android利用EditText如何實(shí)現(xiàn)搜索框詳解
EditText 在開(kāi)發(fā)中也是經(jīng)常用到的控件,也是一個(gè)比較必要的組件,下面這篇文章主要給大家介紹了關(guān)于Android利用EditText如何實(shí)現(xiàn)搜索框的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-07-07
Android實(shí)現(xiàn)多媒體之播放音樂(lè)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)多媒體之播放音樂(lè)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02
音量控制鍵控制的音頻流(setVolumeControlStream)描述
當(dāng)開(kāi)發(fā)多媒體應(yīng)用或者游戲應(yīng)用的時(shí)候,需要使用音量控制鍵來(lái)設(shè)置程序的音量大小,在Android系統(tǒng)中有多種音頻流,感興趣的朋友可以了解下2013-01-01

