
性能優(yōu)化前言:
性能優(yōu)化是一個APP不可或缺并需不斷重復(fù)的工作,性能優(yōu)化的深度是一個優(yōu)秀APP的重要憑證,它既繁雜繁瑣但也有一定的規(guī)則規(guī)律。本篇結(jié)合實際項目來簡單分享一下一個線上產(chǎn)品的優(yōu)化過程。也非常非常非常期待大家留言交流,指錯,分享各自的優(yōu)化經(jīng)驗~我會收集補充更新收錄到本篇。
1、布局渲染 方向:
造成問題:大部分Android顯示屏幕是以每秒60幀來刷新的,1000毫秒/60≈16毫秒,所以16毫秒沒有完成繪制用戶就會感受到卡頓現(xiàn)象出現(xiàn),繪制布局渲染不得當(dāng)是卡頓的主要原因,主要是過度繪制(overdraw)布局的層級太深、頁面過于復(fù)雜等。(動畫、頻繁的發(fā)GC導(dǎo)致堵塞渲染、UI線程中有輕微的耗時操作等因素也會導(dǎo)致卡頓后面詳說。)
實用工具:
開發(fā)者模式下打開 調(diào)試GPU過度繪制 和 GPU呈現(xiàn)模式分析
硬件:調(diào)試GPU過度繪制
原色:沒有過度繪制;
藍色:1 次;
綠色:2次 ;
粉色:3 次;
紅色:4次以上 ;

監(jiān)控:GPU呈現(xiàn)模式分析
綠線線:代表16ms 保持動畫流暢的關(guān)鍵就在于讓這些垂直的柱狀條盡可能地保持在綠線下面,任何時候超過綠線,你就有可能丟失一幀的內(nèi)容,
藍色線:表示測量繪制時間,很高可能是因為一堆圖突然無效(需要重新繪制)或者師徒onDraw函數(shù)過于復(fù)雜。
紅色線:表示執(zhí)行時間,過高說明復(fù)雜的自定義view過多。
橙色線:表示處理時間,CPU告訴GPU渲染一幀的地方。 這是一個阻塞調(diào)用,因為CPU會一直等待GPU發(fā)出接到命令的回復(fù),如果柱狀圖很高, 那就意味著你給GPU太多的工作,太多的負責(zé)視圖需要OpenGL命令去繪制和處理.

解決方案:
1. 從設(shè)計根本上簡化頁面的復(fù)雜度,移除window默認背景。
2. 在view層級相同的情況下,盡量使用LinearLayout代替RelativeLayout 原因是RelativeLayout是相對布局,在測量的時候會對子view(child)分別進行橫向和縱向的兩次測量才能確定相對位置,而LinearLayout顧名思義線性布局,我們設(shè)置了方向后,在不使用weight屬性時只對view測量一次就可以確定子view的位置。這里強調(diào)的是層級相同的情況下!如果布局特別復(fù)雜,單純的使用LinearLayout則會導(dǎo)致層級過深,那采用RelativeLayout比較好,但是,使用ConstraintLayout約束布局代替RelativeLayout繪制速度更快,更靈活不需要嵌套很多層,可以有效的減少布局層級!?。?a target="_blank"> ConstraintLayout性能詳解
3. 使用<merge>標(biāo)簽來減少重復(fù)布局,常配合<include>標(biāo)簽一起使用,當(dāng)一個layout包含另一個layout時,如果被包含的布局文件采用了跟外部一樣的布局模式,可以通過<merge>標(biāo)簽去重,<merge>標(biāo)簽只能用在布局文件的根節(jié)點上。簡單明了的解釋+例子
4.ViewStub的使用,ViewStub繼承了View,非常輕量級且寬高都是0,隱私本身不參與任何的布局和繪制過程。它的意義在于按需加載所需的布局文件,項目中有很多非正常顯示的頁面如網(wǎng)絡(luò)錯誤,加載失敗,空頁面等等,這時候就沒必要在頁面初始化的時候加載進來,通過ViewStub就可以做到使用的時候再加載,提高了程序初始化時的性能。ViewStub詳解
5 view的屬性可以優(yōu)化的地方:
- tools屬性下的方法:
tools:text="張三";
tools:background="@tools:sample/backgrounds/scenic"等 - 顯示隱藏盡量用gone代替invisible,因為gone不繪制,invisible繪制后隱藏。
... ...
6. 繪制方面的優(yōu)化,指的是View的onDraw方法要避免大量的操作
- onDraw方法中不要創(chuàng)建新局部對象,因為這個方法頻繁調(diào)用會產(chǎn)生大量的臨時對象占用過多的內(nèi)存導(dǎo)致系統(tǒng)頻繁調(diào)用gc,降低程序的執(zhí)行效率。
- onDraw方法中不要做耗時操作,也不要執(zhí)行過多的循環(huán)操作,這樣會導(dǎo)致cpu時間片被占用,導(dǎo)致繪制不流暢,出現(xiàn)卡頓。
7. 自定義View優(yōu)化,使用 canvas.clipRect()來幫助系統(tǒng)識別那些可見的區(qū)域,只有在這個區(qū)域內(nèi)才會被繪制。
2、內(nèi)存問題 方向:
存在問題:內(nèi)存泄漏、內(nèi)存溢出、內(nèi)存浪費等
內(nèi)存泄漏相關(guān)問題:
1. 單例模式導(dǎo)致內(nèi)存泄漏:單例的靜態(tài)特性使得它的生命周期跟APP的生命周期一樣長,如果一個對象已經(jīng)沒有用處了但是單例還持有它的引用,應(yīng)用程序的整個生命周期都不能回收, 從而導(dǎo)致內(nèi)存泄露。構(gòu)造單例盡量別使用Activity的引用,否則Activity關(guān)閉后單例模式還持有該Activity的引用使得Activity得不到回收,會導(dǎo)致內(nèi)存泄漏,優(yōu)化案例是采用application中的content。
2. 靜態(tài)變量導(dǎo)致內(nèi)存泄漏:靜態(tài)持有變量很多是因為其使用的生命周期不一致而導(dǎo)致內(nèi)存泄露,static指向的內(nèi)存引用,GC永遠不會回收,優(yōu)化方案是盡量少使用,不用時給它重置為null使其不再持有。
3. 非靜態(tài)內(nèi)部類導(dǎo)致內(nèi)存泄漏:非靜態(tài)內(nèi)部類的默認持有外部類的引用,當(dāng)內(nèi)部類的生命周期比外部類還要長時就會導(dǎo)致內(nèi)存泄漏。典型場景可以看一下此篇:Handler詳解,優(yōu)化方案:靜態(tài)內(nèi)部類+弱引用+銷毀時將銷毀handler的回調(diào)和發(fā)送消息移除掉。
4. 資源對象及時關(guān)閉導(dǎo)致內(nèi)存泄漏:在使用IO、File流或者Sqlite、Cursor等資源時要及時關(guān)閉。原因是這些資源在進行讀寫操作時通常使用了緩沖,如果不及時關(guān)閉,這些緩沖會一直被占用從而得不到釋放,導(dǎo)致內(nèi)存泄漏。
5. 未取消注冊或回調(diào)導(dǎo)致內(nèi)存泄漏:比如在activity中注冊廣播,如果activity銷毀后不取消注冊,這個廣播會一直存在系統(tǒng)中。
6. 屬性動畫導(dǎo)致內(nèi)存泄漏:動畫是一個耗時操作,如果啟動了動畫后,在頁面或視圖銷毀時沒有調(diào)用cancle銷毀動畫,這個動畫雖然看不見了但會一直不斷地播下去,動畫引用的view,view引用的activity無法正常釋放造成內(nèi)存泄漏。
7.Handle.post(Runnable)引發(fā)泄漏,或者有的地方postDelay(Runnable,time) 在頁面釋放的時候應(yīng)該removeMessageAndCallback,最好把runnable改為靜態(tài)內(nèi)部類。
8.及時合理的釋放資源,release掉不使用的資源;監(jiān)聽回調(diào)不使用時記得要釋放掉,置為null。
9.錯用Application,導(dǎo)致GC不能回收。
10.注意子類繼承父類方法 ,遺漏了surper.xxx(),有一些釋放資源的邏輯在父類中,而子類重寫父類方法的時候沒有調(diào)用父類方法,導(dǎo)致的錯誤。
11. WebView造成的內(nèi)存泄漏:造成的原因是加載網(wǎng)頁后長期占用內(nèi)存不能被釋放,WebView控件會持有activity引用,activity銷毀后WebView持有的引用得不到釋放,優(yōu)化方式是做好銷毀工作或采用成熟的開源WebView庫。
內(nèi)存溢出相關(guān)問題:
內(nèi)存空間不夠,導(dǎo)致內(nèi)存溢出OOM(內(nèi)存泄漏多了或者直接就不夠用,多在于圖片等大資源)

查詢圖片不加載到內(nèi)存中:Android中Bitmap有四種圖片色彩模式:
ALPHA_8:每個像素需要占用內(nèi)存中的1byte
RGB_565:每個像素需要占用內(nèi)存中的2byte
ARGB_4444:每個像素需要占用內(nèi)存中的2byte
ARGB_8888:每個像素需要占用內(nèi)存中的4byte
我們創(chuàng)建Bitmap時,默認的色彩模式是ARGB_8888的,這種色彩模式是質(zhì)量最高的,當(dāng)然這樣的模式占用的內(nèi)存也最大。

3、代碼優(yōu)化來提高性能:
1. 合理的使用設(shè)計模式
單利模式:控制內(nèi)存中只存在一個對象可以減少內(nèi)存、減輕加載負擔(dān)和時間,提升加載效率。
享元模式:減少對象創(chuàng)建的數(shù)量來達到減少內(nèi)存。
2. 避免過多創(chuàng)建Java對象,可以采用享元模式來減少對象的創(chuàng)建,可以用基本數(shù)據(jù)類型代替包裝類型的對象。
3. 盡可能使用final修飾符,在Java中final關(guān)鍵字修飾后不會直接定義內(nèi)聯(lián)函數(shù)。而是告訴編譯器可以將其修飾的函數(shù)看作內(nèi)聯(lián)函數(shù),編譯器會比較效率后決定是否視其為內(nèi)聯(lián)函數(shù)。如果是內(nèi)嵌調(diào)用,虛擬機不再執(zhí)行正常的方法調(diào)用(參數(shù)壓棧,跳轉(zhuǎn)到方法處執(zhí)行,再調(diào)回,處理棧參數(shù),處理返回值),而是直接將方法展開,以方法體重的實際代碼替代原來的方法調(diào)用。
相關(guān)文章:JVM 技術(shù)內(nèi)幕——HotSpot VM
4. 盡可能使用局部變量,傳遞的參數(shù)和臨時變量都保存在棧中,靜態(tài)變量和實例變量等都存在堆中,棧相比于堆查詢的效率高、空間小、產(chǎn)生的碎片小,棧自動釋放而堆需要GC。
5. 盡量使用基本數(shù)據(jù)類型代替對象,盡可能使用基本類型代替包裝類型。
基本類型跟包裝類型、對象產(chǎn)生的內(nèi)存區(qū)域是不同的,基本類型數(shù)據(jù)產(chǎn)生和處理都在棧中,包裝類型屬于對象,在堆中產(chǎn)生實例。
String str = "hello"; 上面這種方式會創(chuàng)建一個"hello"字符串,而且JVM的字符緩存池還會緩存這個字符串;
String str = new String("hello"); 此時程序除創(chuàng)建字符串外,str所引用的String對象底層還包含一個char[]數(shù)組,這個char[]數(shù)組依次存放了h,e,l,l,o。
6. 盡量不要將資源清理放在finalize方法中,由于GC的工作量很大,回收Young代內(nèi)存時會引起應(yīng)用程序暫停,使用finalize方法進行資源清理,會增加GC負擔(dān),程序運行效率更差。
盡量在finally塊中釋放資源,避免資源泄漏。
7. 盡可能減少使用synchronize,同步對系統(tǒng)開銷大甚至形成死鎖,盡可能避免無謂的同步控制,synchronize的方法盡可能小,因為被鎖住的方法執(zhí)行完,其余線程無法調(diào)用當(dāng)前對象的其余方法。
單線程應(yīng)盡量使用HashMap、ArrayList,因為HashTable、Vector等使用了同步機制,下降了性能。
盡量合理的創(chuàng)建HashMap,建立較大hashMap時,避免多次擴容,減少進行hash重構(gòu)的次數(shù),準(zhǔn)確估計大小,利用public HashMap(int initialCapacity, float loadFactor)構(gòu)造函數(shù)來設(shè)置初始容量和加載因子。
8. 合理選擇String、StringBuilder 、StringBuffer。
StringBuffer:線程安全(內(nèi)部大量使用synchronized機制)但效率低,提前估算容量避免重建提高效率StringBuffer buffer = new StringBuffer(1000);
StringBuilder:線程不安全、效率塊。
String:不可變。String的引用在棧、String的值是一個字符數(shù)組,字符數(shù)組的值存放在常量池中。(字面常量存在常量池)
- String str = "ABC"; str 在棧,"ABC"在常量池中。
- String str = new String("ABC"); str的value存在堆中(為字符數(shù)組的引用),字符數(shù)組存在常量池中。
9. 減少對變量的重復(fù)計算:for(int i=0;i<list.size();i++) 改為 for(int i=0,len=list.size();i<len;i++)
System.arraycopy() 循環(huán)復(fù)制數(shù)組
10. 沒必要的建立,比如:判斷條件滿足時再new 相關(guān)對象等。
11. 對象釋放前置:局部變量在方法結(jié)束后會變成垃圾,比如:方法1中沒必要obj設(shè)為null,方法2中可以提前將不用的Obj設(shè)為null。
1.
Public void test(){
Object obj = new Object();
……
Obj=null;
}
2.
Public void test(){
Object obj = new Object();
……
Obj=null;
//執(zhí)行耗時,耗內(nèi)存操做;或調(diào)用耗時,耗內(nèi)存的方法
……
}
12. 位運算符代替乘法、除法操作更高效。nt num = a * 2; int num = a / 2; 改為int num = a << 2; int num = a >> 2;
13. 合理的選用容器
4、其他相關(guān)優(yōu)化方向
1. ListView的優(yōu)化:老生常談,convertView復(fù)用、ViewHolder的使用避免getView中執(zhí)行耗時操作,根據(jù)頁面滑動的速度來控制異步是否開啟,開啟硬件加速等。ListVIew優(yōu)化
現(xiàn)在很難見到ListView了,更多使用的是RecyclerView,RecyclerView優(yōu)化。
2. 線程優(yōu)化:線程的創(chuàng)建和銷毀影響性能的開銷,大量的線程互相搶占資源還容易導(dǎo)致堵塞,互必要時采用線程池,避免程序中出現(xiàn)大量的Thread。
3. 盡量少用枚舉,枚舉占用的內(nèi)存空間比整形大。
4. 使用Android特有的數(shù)據(jù)結(jié)構(gòu),具有更好的性能。
5. 充分利用內(nèi)存緩存和磁盤緩存。
6. 強軟弱虛,了解jvm等。
5、應(yīng)用瘦身
1. 只使用一套圖片,xxhdpi : 1080P ;
2. 資源圖大小png>jpg,更推薦轉(zhuǎn)成 webp格式, WebP既支持有損壓縮也支持無損壓縮, 谷歌(google)開發(fā)的一種旨在加快圖片加載速度的圖片格式
3. 代碼邏輯簡化、無用文件、無用依賴庫。
4. 微信的安裝包壓縮。
5. Android設(shè)備的CPU類型(ABIs):
armeabi-v7a: 第7代及以上的 ARM 處理器。2011年15月以后的生產(chǎn)的大部分Android設(shè)備都使用它.
arm64-v8a: 第8代、64位ARM處理器,很少設(shè)備,三星 Galaxy S6是其中之一。
armeabi: 第5代、第6代的ARM處理器,早期的手機用的比較多。
x86: 平板、模擬器用得比較多。
x86_64: 64位的平板。
ndk {
abiFilters "armeabi-v7a"
// abiFilters "armeabi-v7a","x86"
}
6.開啟minifyEnabled混淆代碼可大大減少APP大小。
7.刪除無用的語言資源,如內(nèi)陸應(yīng)用只保留zh中文資源。
android {
defaultConfig {
resConfigs "zh"
}
}
8. 代碼混淆,使用proGuard 代碼混淆器工具,它包括壓縮、優(yōu)化、混淆等功能。
9. 圖片資源壓縮,壓縮地址
10. arsc優(yōu)化:減少string國家化支持的類型,只保留中文、英文等必要國家語言,gradle配置國際化語言支持。
11. 相關(guān)資源、so庫等做成動態(tài)下載。