一般我們寫的app操作的數(shù)據(jù)多的時侯或者平時使用的時候都會經(jīng)常出現(xiàn)卡頓次慢、閃退玄渗、ANR停止運行等各種問題鳖目。這樣會導(dǎo)致用戶使用的體驗非常差,因此在寫代碼的時候我們就要注意一些代碼的書寫方式和做好優(yōu)化了。一般app的優(yōu)化我們可以從啟動坦冠、布局形耗、內(nèi)存、存儲辙浑、耗電等進行優(yōu)化激涤。
1、啟動優(yōu)化:
*應(yīng)用的啟動分為冷啟動和熱啟動判呕。
冷啟動:應(yīng)用第一次啟動的時候倦踢,系統(tǒng)會為應(yīng)用創(chuàng)建一個新的進程,所以首先會創(chuàng)建和初始化appliction類侠草,然后再創(chuàng)建和初始化activity類(包括測量辱挥、布局、繪制)边涕,最后顯示在界面上晤碘。如下圖
熱啟動:熱啟動會從已經(jīng)有的進程中啟動,所以不會再去創(chuàng)建和初始化application,而是直接創(chuàng)建和初始化activity奥吩。這也可以看出application只會初始化一次哼蛆,只包含activity中的生命周期流程蕊梧。
*一般我們要優(yōu)化啟動得先知道我們啟動耗費了多長時間霞赫,要優(yōu)化到什么程度。這就需要我們?nèi)蚀_獲取應(yīng)用啟動時間肥矢。應(yīng)用的啟動時間可以通過adb命令來獲取,入下圖上面可以看出有三個時間
ThisTime:一般和totalTime時間一樣端衰,如果在應(yīng)用啟動時打開一個過渡的全透明的頁面預(yù)處理一些事情,在顯示出主頁面甘改,這樣比totalTime 小旅东。
TotalTime:應(yīng)用的啟動時間,包括創(chuàng)建進程+application初始化+activity初始化到界面顯示W(wǎng)aitTime:一般比? TotalTime大些十艾,包括系統(tǒng)影響的耗時抵代。
*一般我們應(yīng)用功能模塊越多,在需要初始化的就越多忘嫉,這樣就會導(dǎo)致應(yīng)用啟動越慢了荤牍。一般我們的應(yīng)用優(yōu)化有一下:
1、插入啟動頁:一般應(yīng)用啟動過程:點擊啟動應(yīng)用-->application初始化--> AppstartActivity-->HomePageActivity庆冕。因此我們一般打開一個app的時候都可以看到有啟動頁康吵,也就是一些廣告或者一些介紹app的宣傳圖片,在顯示這個期間一般時間比較長访递,那就可以在這期間完成很多初始化工作晦嵌,比如很多第三方庫使用需要初始化、數(shù)據(jù)的預(yù)緩存等操作。
2惭载、可以優(yōu)化我們的代碼旱函,比如一些不必要在啟動先加載的就不要放在初始化中加載,對于一些必須要在初始化中加載的描滔,那我們可以通過線程陡舅、異步加載等方式實現(xiàn)。還有布局中的代碼也可以做盡量的優(yōu)化伴挚、主要就是減少布局的層級和避免過度的繪制靶衍。
2、布局優(yōu)化
*應(yīng)用啟動慢茎芋,使用過程卡頓颅眶,造成這些問題主要場景是在ui的繪制、應(yīng)用啟動田弥、頁面跳轉(zhuǎn)涛酗、事件響應(yīng)。頁面繪制:主要是繪制的界面布局層次踢啊多偷厦,頁面太過復(fù)雜商叹、刷新不合理、由于這些原因的場景更多出現(xiàn)在ui和啟動后的初始界面以及跳轉(zhuǎn)到頁面的繪制上只泼。數(shù)據(jù)處理:有時候應(yīng)用在某些場景需要處理大量數(shù)據(jù)也有可能導(dǎo)致卡頓剖笙,一般分為三種情況,一是在主線程處理一些耗時的操作请唱,而是數(shù)據(jù)處理占用cpu高弥咪,導(dǎo)致主線程拿不到事件片,三是十绑,內(nèi)存消耗太大導(dǎo)致GC頻繁,引起卡頓或者應(yīng)用崩潰聚至。
*在ui的繪制過程中有三個核心的步驟:measure-->Layout-->Draw,mesure是用于計算視圖的大小,layout確定視圖的位置本橙,draw是用于繪制視圖扳躬。在android系統(tǒng)中整體的繪制源碼是在viewGroup類的performTraversals()方法,通過這個方法可以看出Mesure和layout都是遞歸來獲取view的大小和位置甚亭,并且以深度作為優(yōu)先級贷币,當層級越深,元素越多狂鞋,耗時就會越長片择,導(dǎo)致卡頓的幾率也就越大∩ё幔可以在as打開tools-->android -->android device monitor-->Hierarchy view查看自己寫的布局的層級字管,最大最好不超過10級啰挪。,下面是頁面構(gòu)造框架圖嘲叔。
*GPU和CUP原理:在Android的繪制架構(gòu)中亡呵,CPU主要負責了視圖的測量、布局硫戈、記錄锰什、把內(nèi)容計算成Polygons多邊形或者Texture紋理,而GPU主要負責把Polygons或者Textture進行Rasterization柵格化丁逝,這樣才能在屏幕上成像汁胆。在使用硬件加速后,GPU會分擔CPU的計算任務(wù)霜幼,而CPU會專注處理邏輯嫩码,這樣減輕CPU的負擔,使得整個系統(tǒng)效率更高罪既。
*刷新率:屏幕每秒刷新的次數(shù)铸题,是一個與硬件有關(guān)的固定值。在Android平臺上琢感,這個值一般為60HZ丢间,即屏幕每秒刷新60次。即60fps/秒 即16ms/幀驹针,如果繪制屏幕每幀超過16ms就會出現(xiàn)卡頓現(xiàn)象烘挫,所有要盡量保持一幀能在16ms內(nèi)繪制完成。要想知道自己寫的布局是否有過渡繪制牌捷,最簡單的方法是打開手機可以通過打開手機開發(fā)人員工具—>調(diào)節(jié)GPU過度繪制—>顯示過度繪制區(qū)域墙牌,開啟后就可以看到應(yīng)用界面的標了不同顏色了。如下圖
他們具體的含義是:
如果出現(xiàn)淡紅色或者紅色就要注意了暗甥,說明明顯過渡繪制了,即繪制任務(wù)過重捉捅,導(dǎo)致繪制一幀內(nèi)容耗時過長撤防,需要優(yōu)化布局代碼,比如減少布局的層級棒口、減少不必要的背景寄月、暫時不顯示的view設(shè)置為gone而不是invisible、自定義的on ?Draw方法設(shè)置canvas.clipRect()指定繪制區(qū)域或者通過canvas.quickreject()減少繪制區(qū)域无牵、減少頻繁的requerLayout()等漾肮。
*對于布局的優(yōu)化我們可以從下面幾個方面進行優(yōu)化:
1、盡量使用RelativeLayout和LinearLayout.
2茎毁、在布局層級相同的情況下克懊,使用LinearLayout
3忱辅、用LinearLayout有時會使嵌套層級變多,應(yīng)該使用RelativeLayout,使布局扁平化谭溉。
4墙懂、使用merge。
5扮念、如果很多布局相同的話可以共同布局來實現(xiàn)损搬,比如應(yīng)用頭部titleBar就可以只寫一個布局然后通過include添加。
6柜与、當控件是固定大小的盡量就用固定大小,而減少使用wrap_content,因為這會增加布局measure時的計算時間巧勤。
7、刪除控件中無用的屬性弄匕。
3踢关、內(nèi)存優(yōu)化
*java虛擬機擁有垃圾回收的機制,可以通過自動回收不用的垃圾粘茄,因此不需要在代碼中分配和釋放某一塊的內(nèi)存签舞,不容易出現(xiàn)內(nèi)存溢出和泄漏的問題。
*android 系統(tǒng)的的內(nèi)存管理也是通過new關(guān)鍵字來為對象分配內(nèi)存柒瓣,通過垃圾回收器(GC)來回收儒搭。即當手機內(nèi)存空間不足的時候就會根據(jù)不同的規(guī)則自動釋放系統(tǒng)認為可以釋放的內(nèi)存。當我們不合理使用內(nèi)存就很容易導(dǎo)致應(yīng)用出現(xiàn)很多性能的問題芙贫,嚴重有可能崩潰(outOfMemoryError)搂鲫,而且一旦出現(xiàn)內(nèi)存泄漏或溢出會很難排查哪里的問題。所以內(nèi)存的合理應(yīng)用也是非常有必要的磺平,這樣可以讓我們的應(yīng)用更流暢魂仍、用戶體驗更好。
*內(nèi)存的回收機制:整個內(nèi)存可以分為三塊拣挪,yong Generation(年輕代)擦酌、old Generation(年老代)還有permanent Generation(持久代)。
yong Generation:年輕代又可分三個區(qū)菠劝,eden赊舶、s0、s1赶诊,程序中大部分生成的對象都會被存在eden區(qū)笼平,但是當eden區(qū)滿的時候,還存活的對象將被復(fù)制到so或者s1區(qū)中舔痪,如果連這兩個都滿了就會復(fù)制到年老代中寓调。
old Generation: 年老代存放年輕代復(fù)制過來的對象,相對年輕代锄码,年老大對象的生命周期比較長夺英。
permanent Generation:這個區(qū)一般用于存放靜態(tài)的類和方法晌涕,持久代對垃圾回收沒有影響。
系統(tǒng)的年輕代和年老代采用不同的回收機制秋麸,每個內(nèi)存區(qū)都有固定的大小渐排,隨著新對象陸續(xù)被分到此區(qū)域,當對象的大小臨近這一級別內(nèi)存的閾值時灸蟆,就會觸發(fā)GC操作驯耻,回收空間,用來存放新的對象炒考。同時每一塊的GC時間也是不一樣的可缚,年輕代的最短,持久代的最長斋枢,還有跟這個區(qū)中的對象數(shù)量也有關(guān)帘靡,對象越多,回收時間也就越長瓤帚。
*GC可以分三種類型:
kGcCauseForAlloc:在分配內(nèi)存時發(fā)現(xiàn)內(nèi)存不足的情況下觸發(fā)GC描姚,這種情況下的GC會stop world, stop world 是由于并發(fā)GC時戈次,其他線程會停止轩勘,知道 GC完成。kGcCauseBackground:當內(nèi)存達到一定的閾值時觸發(fā)GC怯邪,這個時候是一個后臺GC,不會引起stop world.kGcCauseExplicit:顯式調(diào)用時進行的GC绊寻,如果 ART打開了這個選項,在system.gc時會進行GC悬秉。在android 4.4新增了一種ART(android runtime)模式澄步,在GC時可以選擇不同回收算法,而 Dalvik只有一種和泌,每次觸發(fā)時都會導(dǎo)致其他線程停止工作(包括ui線程)村缸,ART還增加了一個large object space這個主要是用于管理bitmap等占大內(nèi)存對象的。ART還可以在后臺整理內(nèi)存允跑、減少內(nèi)存碎片王凑,因此ART可以避免較多類似GC導(dǎo)致的卡頓問題。*使用內(nèi)存分析工具查找內(nèi)存泄漏聋丝,使用as的可以在底部打開monitors查看memory、CPU工碾、GPU等的使用情況弱睦。還可以打開heap viewer查看 GC情況,這個是在as的tools-->android-->android device monitor打開具體操作可自行查找渊额。

*代碼中如何減少卡頓况木、oom垒拢、異常崩潰發(fā)生:
1、使用一些資源對象(cursor火惊、file求类、sqlite、bitmap等)使用完后及時關(guān)閉或釋放屹耐。
2尸疆、使用改進型的for循環(huán)
3、Context使用不當造成內(nèi)存泄露惶岭;不要對一個Activity Context保持長生命周期的引用寿弱。盡量在一切可以使用應(yīng)用ApplicationContext代替Context的地方進行替換。
4按灶、注冊的廣播接收器症革、注冊的觀察者等,一定要注銷鸯旁,否則會導(dǎo)致觀察者列表中維持對象的引用噪矛,阻止垃圾的回收。
5铺罢、handler在使用的時候需要注意艇挨,在activity的Destory或者stop中要移除消息隊列中的消息(mHandler.removeCallbacksAndMessage(null))避免引發(fā)內(nèi)存泄漏。
6畏铆、webview使用完必須手動銷毀雷袋,否則容易造成內(nèi)存泄漏。
7辞居、不要在執(zhí)行頻率很高的方法或者循環(huán)中創(chuàng)建對象楷怒,可以使用HashTable等創(chuàng)建一組對象容器從容器中取那些對象,而不用每次new與釋放避免代碼設(shè)計模式的錯誤造成內(nèi)存泄露
8瓦灶、對于一些常駐的后臺service使用完后要及時停止鸠删。
9、一些數(shù)據(jù)類型的使用贼陶,如果用int型就盡量不要使用integer因為int對象只有4個字節(jié)刃泡,Integer對象是16個字節(jié)的,這樣會造成額外的內(nèi)存和時間的消耗碉怔。
10烘贴、當使用的對象數(shù)據(jù)比較小(1000以內(nèi))撮胧,但是訪問特別多桨踪,或者刪除和插入頻率不高時,相比HashMap,使用ArrayMap會更好芹啥。
11锻离、盡量減少或者不使用枚舉(enum)類型铺峭,因為枚舉的內(nèi)存開銷比一般的定義常量多三倍以上。
12汽纠、圖片格式的使用卫键,降低圖片的質(zhì)量是可以減少內(nèi)存的消耗的,位圖的最高是ARGB_8888最低是ALPHA_8,還有RGB_565和ARGB_4444可根據(jù)實際需求使用虱朵。實際圖片緩存可以使用第三方的莉炉,例如glide、freso卧秘、picasso等呢袱。
13、耗時的操作一定要放在子線程中翅敌。比如數(shù)據(jù)的加密羞福、解密、編碼蚯涮、運算治专、處理大量數(shù)據(jù)等。
實際還有很多可以實現(xiàn)提高應(yīng)用的流暢度和減少應(yīng)用卡頓遭顶、崩潰的方法张峰,可自行尋找學習,其實這也跟我們寫代碼的方式有關(guān)棒旗,最好養(yǎng)成良好的寫代碼習慣和風格喘批。可以提高后期代碼的維護性铣揉。
4饶深、存儲優(yōu)化
*android存儲方式:android系統(tǒng)有四種存儲方式,分別是sharedPreferences逛拱、文件敌厘、SQLite和ContentProvider。sharedPreferences:一個輕量級的數(shù)據(jù)存儲方式朽合,適用于保存軟件配置參數(shù)俱两。其實質(zhì)是采用了xml文件存放數(shù)據(jù),路徑為:/data/data//shared_prefs。它的優(yōu)點是使用簡單曹步、速度快宪彩,缺點是只能存儲boolean、int讲婚、float毯焕、long和string五種數(shù)據(jù)類型。
SQLite:?SQLite是一個嵌入式庫并且實現(xiàn)了零配置磺樱、無服務(wù)端和事務(wù)功能的SQL數(shù)據(jù)庫引擎纳猫。它在廣泛領(lǐng)域內(nèi)被使用,而且單線程讀寫性能與MySQL比肩竹捉,并且保證ACID性芜辕。支持基本的sql語法,它提供了一個名為SQLiteDatabase的類块差,封裝了一些操作數(shù)據(jù)庫的API侵续。系統(tǒng)自帶的一個數(shù)據(jù)庫,使用簡單憨闰、維護和管理簡單状蜗。
File(文件):通用的文件存儲方式,通常用于存儲大量的數(shù)據(jù)鹉动,但缺點是更新數(shù)據(jù)比較麻煩轧坎。
ContentProvider:android系統(tǒng)中所有應(yīng)用程序?qū)崿F(xiàn)數(shù)據(jù)共享的一種存儲方式。當應(yīng)用繼承ContentProvider類泽示,并重寫該類用于提供數(shù)據(jù)和存儲數(shù)據(jù)的方法缸血,就可以向其他應(yīng)用共享其數(shù)據(jù)。特別是音頻械筛、視頻捎泻、圖片、通信錄等埋哟。
sharedPreference優(yōu)化:
1笆豁、 SharedPreferences實際上是對一個xml文件存儲key-value鍵值對,每一次的commit和apply的操作都是一次I/O寫的操作,眾所周知赤赊,I/O的操作是最慢的操作之一闯狱,在主線程中操作會導(dǎo)致主線程緩慢,所以對于SharedPreferences的設(shè)置操作砍鸠,最好先獲取一個editor扩氢,然后批量操作,然后調(diào)用apply方法爷辱,比commit方法略快录豺。特別是在不需要返回值的情況下,使用apply方法可以極大提高性能饭弓。
2双饥、當sharedPreference文件還沒有被加載到內(nèi)存時,調(diào)用getSharedPreferences方法會初始化文件并讀入內(nèi)存弟断,這容易導(dǎo)致耗時過程咏花。
3、避免頻繁讀寫sharedPreferences,減少無所謂的調(diào)用,即在同一生命周期內(nèi)昏翰,讀一次即可苍匆。
4、避免進程讀寫sharedPreferences棚菊,因為這樣需要用到contentProvider方案支持浸踩,對所有sp操作套上了contentProvider進行訪問,會增加三倍左右的耗時统求。
SQLite數(shù)據(jù)庫使用優(yōu)化:
1检碗、數(shù)據(jù)庫在啟動的時候就準備好,這樣可以避免進入應(yīng)用后再初始化導(dǎo)致相關(guān)操作時間變長码邻,即可以放到Application的onCreate方法中折剃,在application生命周期結(jié)束時再關(guān)閉(應(yīng)用結(jié)束時調(diào)用close方法關(guān)閉數(shù)據(jù)庫)。
2像屋、初始化 DatabaseHelper類需要context怕犁,這里的 Context一定要用ApplicationContext,因為這里是單例,在整個應(yīng)用的生命周期不會銷毀开睡,如果使用某個Activity的Context因苹,會導(dǎo)致這個activity的資源都不會被釋放,出現(xiàn)內(nèi)存泄漏篇恒。
3扶檐、數(shù)據(jù)庫的操作都比較耗時,一定要放到異步線程中胁艰。
4款筑、使用SQLiteStatement類來將數(shù)據(jù)插入數(shù)據(jù)庫,可以減少插入時間腾么,提高性能奈梳。
5、使用事務(wù)解虱,對于插入大量數(shù)據(jù)攘须,使用事務(wù)可以大大減少插入時間。
5殴泰、耗電優(yōu)化
對于我們開發(fā)應(yīng)用來說于宙,對電量消耗優(yōu)化也是很重要的,因此我們在開發(fā)過程中也要盡量減少電量的消耗悍汛。
1捞魁、網(wǎng)路方面:
使用wifi傳輸數(shù)據(jù)的時候,應(yīng)該盡量增大每個包的大小离咐,并降低發(fā)包的頻率谱俭。
在蜂窩移動網(wǎng)路下,最好做到批量執(zhí)行網(wǎng)路請求,盡量避免頻繁的間隔網(wǎng)路請求昆著。
盡量在Wi-Fi環(huán)境下使用數(shù)據(jù)傳輸
使用高效的數(shù)據(jù)格式和解析方法县貌,在數(shù)據(jù)格式方面,使用JSON和Protobuf效率比xml好宣吱。
壓縮數(shù)據(jù)格式窃这,比如采用GZIP壓縮,這昂可以提高下載速度征候,也可以提高上傳數(shù)據(jù)時間,節(jié)省更多電量祟敛。
2疤坝、盡量減少浮點運算,浮點運算比整數(shù)運算更消耗CPU馆铁,會增加耗電跑揉。
3、避免wakeLock使用不當埠巨,下面是幾種使用方式历谍,一定要根據(jù)自己需求使用,完成后記得釋放wakeLock辣垒。
PARTIAL_WAKE_LOCK:保持CPU?運轉(zhuǎn)望侈,屏幕和鍵盤燈有可能是關(guān)閉的。
SCREEN_DIM_WAKE_LOCK:保持CPU?運轉(zhuǎn)勋桶,允許保持屏幕顯示但有可能是灰的脱衙,允許關(guān)閉鍵盤燈
SCREEN_BRIGHT_WAKE_LOCK:保持CPU?運轉(zhuǎn),保持屏幕高亮顯示例驹,允許關(guān)閉鍵盤燈
FULL_WAKE_LOCK:保持CPU?運轉(zhuǎn)捐韩,保持屏幕高亮顯示,鍵盤燈也保持亮度
ACQUIRE_CAUSES_WAKEUP:不會喚醒設(shè)備鹃锈,強制屏幕馬上高亮顯示荤胁,鍵盤燈開啟。有一個例外屎债,如果有notification彈出的話仅政,會喚醒設(shè)備。
ON_AFTER_RELEASE:WakeLock?被釋放后扔茅,維持屏幕亮度一小段時間
4已旧、使用Job Scheduler,android5.0后提供了一個jobScheduler組件,只有一系列的預(yù)置條件滿足時才執(zhí)行對應(yīng)的操作召娜,這樣既省電运褪,又保證了功能的完整性。在以下場景可以考慮使用:
重要不緊急的任務(wù),可以延遲執(zhí)行秸讹,如定期數(shù)據(jù)庫數(shù)據(jù)更新和數(shù)據(jù)上報檀咙。
耗電量較大的任務(wù),比如充電時才希望執(zhí)行的數(shù)據(jù)備份操作璃诀。
不緊急可以不執(zhí)行的網(wǎng)路任務(wù)弧可,如在wifi環(huán)境預(yù)加載數(shù)據(jù)。
可以批量執(zhí)行的任務(wù)劣欢。
5棕诵、耗電檢測,可以使用下面命令查看
adb shell dumpsys batterystats
6、代碼編寫優(yōu)化
1凿将、遵循單一職責原則校套,一個模塊有且只有一個職責,如果一個模塊或者一個類提供了不同類型的功能牧抵,活著一個功能需要幾個模塊共同完成笛匙,這就有可能在抽象層上設(shè)計不合理。
2犀变、開閉原則妹孙,在面向?qū)ο蟮恼Z言中,對象對可擴編開放获枝,對修改關(guān)閉蠢正,所以需要考慮添加/擴編另外的內(nèi)容時是否會帶了新的問題。
3映琳、代碼復(fù)用机隙,根據(jù)“三振法”,即如果代碼復(fù)用超過三次萨西,提取公共的代碼重構(gòu)有鹿。
4、更合理的代碼谎脯,寫的時候思考實現(xiàn)這個功能是否有更好的方法實現(xiàn)葱跋。
5、潛在的缺陷源梭,在寫代碼的時候娱俺,需要思考異常情況考慮是否全面,錯誤的傳參是否會引起其他錯誤废麻,循環(huán)是否是以我們期望的方式終止荠卷。
6、方法名烛愧、類名油宜、資源名掂碱、變量名書寫要規(guī)范。
最后app的性能優(yōu)化除了這些還又很多慎冤,對于不同的問題疼燥,優(yōu)化方法也不一定一樣,只有找到問題的根本蚁堤,才能達到優(yōu)化的目的醉者。優(yōu)化也是為了提高用戶的體驗,所以我們得多站在一個用戶的角度上考慮才能更好的做好一個產(chǎn)品披诗。