標簽: 本文由魅族科技有限公司資深A(yù)ndroid開發(fā)工程師degao(嵌入式企鵝圈原創(chuàng)團隊成員)撰寫,,是degao在嵌入式企鵝圈發(fā)表的第一篇原創(chuàng)文章,,毫無保留地總結(jié)分享其在領(lǐng)導(dǎo)魅族多個項目開發(fā)中的Android客戶端性能優(yōu)化經(jīng)驗,極具實踐價值,! 即日起,,嵌入式企鵝圈將在之前五個專欄(Linux內(nèi)核驅(qū)動情景分析、資源緊缺型SOC嵌入式架構(gòu)設(shè)計,、嵌入式交叉工具鏈及其應(yīng)用,、嵌入式設(shè)計和編程、微信硬件平臺和物聯(lián)網(wǎng)解決方案)新增Android開發(fā)專欄,!更多Android,、Linux、嵌入式和物聯(lián)網(wǎng)原創(chuàng)技術(shù)分享敬請關(guān)注微信公眾號:嵌入式企鵝圈,。 眾所周知,,一個好的產(chǎn)品,除了功能強大,,好的性能也必不可少,。有調(diào)查顯示,近90%的受訪者會因為APP性能差而卸載,,性能也是造成APP用戶沮喪的頭號原因,。 那Android客戶端性能的指標都有哪些?如何發(fā)現(xiàn)和定位客戶端的性能問題,?本文結(jié)合多個項目的開發(fā)實踐,,給出了要關(guān)注的重要指標項目,以及定位和解決性能問題的一般步驟,。 性能優(yōu)化應(yīng)該貫穿于功能開發(fā)的全部周期,,而不是做完一次后面便不再關(guān)注。每次發(fā)布版本前,最好能對照標準檢查下性能是否達標,。 記?。寒a(chǎn)品=性能×功能! 一,、 性能檢查項 1. 啟動速度 1)這里的啟動速度指的是冷啟動的速度,,即殺掉應(yīng)用后重新啟動的速度,此項主要是和你的競品對比,。 2)不應(yīng)在Application以及Activity的生命周期回調(diào)中做任何費時操作,,具體指標大概是你在onCreate,onResume,,onStart等回調(diào)中所花費的總時間最好不要超過400ms,,否則用戶在桌面點擊你的應(yīng)用圖標后,將感覺到明顯的卡頓,。 2. 界面切換 1)應(yīng)用操作時,,界面和動畫不應(yīng)有明顯卡頓; 2)可通過在手機上打開 設(shè)置->開發(fā)者選項->調(diào)試GPU過度繪制,然后操作應(yīng)用查看gpu是否超線進行初步判斷,; 3. 內(nèi)存泄露 1)back退出不應(yīng)存在內(nèi)存泄露,,簡單的檢查辦法是在退出應(yīng)用后,用命令`adb shell dumpsys meminfo 應(yīng)用包名`查看 `Activities Views` 是否為零; 2)多次進入退出后的占用內(nèi)存`TOTAL`不應(yīng)變化太大,; 4. onTrimMemory回調(diào) 1)應(yīng)用響應(yīng)此回調(diào)釋放非必須內(nèi)存,; 2驗證可通過命令`adb shelldumpsys gfxinfo 應(yīng)用包名-cmd trim 5`后,再)用命令`adb shell dumpsys meminfo 應(yīng)用包名`查看內(nèi)存大小 5. 過度繪制 1)打開設(shè)置中的GPU過度繪制開關(guān),,各界面過度繪制不應(yīng)超過2.5x,;也就是打開此調(diào)試開關(guān)后,,界面整體呈現(xiàn)淺色,,特別復(fù)雜的界面,紅色區(qū)域也不應(yīng)該超過全屏幕的四分之一,; 6. lint檢查: 1)通過Android Studio中的 Analyze->Inspect Code 對工程代碼做靜態(tài)掃描,;找出潛在的問題代碼并修改; 2) 0 error & 0warning,,如果確實不能解決,,需給出原因。 7. 反射優(yōu)化: 1)在代碼中減少反射調(diào)用,; 2)對頻繁調(diào)用的返回值進行Cache,; 8. 穩(wěn)定性: 1)連續(xù)48小時monkey不應(yīng)出現(xiàn)閃退,anr問題,。 2)如果應(yīng)用接入了數(shù)據(jù)埋點的sdk,,比如百度統(tǒng)計sdk,,友盟統(tǒng)計sdk等,這些sdk都會將應(yīng)用的崩潰信息上報回來,,開發(fā)者應(yīng)每天關(guān)注這些統(tǒng)計到的崩潰日志,,嚴格控制應(yīng)用的崩潰率; 9. 耗電: 1)應(yīng)用進入后臺后不應(yīng)異常消耗電量,; 2)操作應(yīng)用后,,退出應(yīng)用,讓應(yīng)用處于后臺,,一段時間后通過`adb shell dumpsysbatterystats`查看電量消耗日志看是否存在異常,。
二、性能問題常見原因 性能問題一般歸結(jié)為三類: 1. UI卡頓和穩(wěn)定性:這類問題用戶可直接感知,,最為重要,; 2. 內(nèi)存問題:內(nèi)存問題主要表現(xiàn)為內(nèi)存泄露,或者內(nèi)存使用不當導(dǎo)致的內(nèi)存抖動,。如果存在內(nèi)存泄露,,應(yīng)用會不斷消耗內(nèi)存,易導(dǎo)致頻繁gc使系統(tǒng)出現(xiàn)卡頓,,或者出現(xiàn)OOM報錯,;內(nèi)存抖動也會導(dǎo)致UI卡頓,。 3. 耗電問題:會影響續(xù)航,,表現(xiàn)為不必要的自啟動,不恰當持鎖導(dǎo)致系統(tǒng)無法正常休眠,,系統(tǒng)休眠后頻繁喚醒系統(tǒng)等,; 三、UI卡頓常見原因和分析方法 下面分別介紹出現(xiàn)這些問題的常見原因以及分析這些問題的一般步驟,。 1.卡頓常見原因 1)人為在UI線程中做輕微耗時操作,,導(dǎo)致UI線程卡頓; 2) 布局Layout過于復(fù)雜,,無法在16ms內(nèi)完成渲染,; 3)同一時間動畫執(zhí)行的次數(shù)過多,導(dǎo)致CPU或GPU負載過重,; 4) View過度繪制,,導(dǎo)致某些像素在同一幀時間內(nèi)被繪制多次,從而使CPU或GPU負載過重,; 5) View頻繁的觸發(fā)measure,、layout,導(dǎo)致measure,、layout累計耗時過多及整個View頻繁的重新渲染,; 6) 內(nèi)存頻繁觸發(fā)GC過多(同一幀中頻繁創(chuàng)建內(nèi)存),,導(dǎo)致暫時阻塞渲染操作; 7) 冗余資源及邏輯等導(dǎo)致加載和執(zhí)行緩慢,; 8)工作線程優(yōu)先級未設(shè)置為Process.THREAD_PRIORITY_BACKGROUND,,導(dǎo)致后臺線程搶占UI線程cpu時間片,阻塞渲染操作,; 9) ANR,; 2. 卡頓分析解決的一般步驟: 1)解決過度繪制問題 >在設(shè)置->開發(fā)者選項->調(diào)試GPU過度繪制中打開調(diào)試,看對應(yīng)界面是否有過度繪制,,如果有先解決掉:
> 定位過渡繪制區(qū)域 > 利用Android提供的工具進行位置確認以及修改(HierarchyView , Tracer for OpenGL ES) > 定位到具體的視圖(xml文件或者View) > 通過代碼和xml文件分析過渡繪制的原因 > 結(jié)合具體情況進行優(yōu)化 > 使用Lint工具進一步優(yōu)化
2) 檢查是否有主線程做了耗時操作: 嚴苛模式(StrictMode),,是Android提供的一種運行時檢測機制,用于檢測代碼運行時的一些不規(guī)范的操作,,最常見的場景是用于發(fā)現(xiàn)主線程的IO操作,。應(yīng)用程序可以利用StrictMode盡可能的發(fā)現(xiàn)一些編碼的疏漏。 > 開啟 StrictMode: >> 對于應(yīng)用程序而言,,Android 提供了一個最佳使用實踐:盡可能早的在 android.app.Application 或 android.app.Activity 的生命周期使能 StrictMode,,onCreate()方法就是一個最佳的時機,越早開啟就能在更多的代碼執(zhí)行路徑上發(fā)現(xiàn)違規(guī)操作,。 >> 監(jiān)控代碼 public voidonCreate() { if (DEVELOPER_MODE) { StrictMode.setThreadPolicy(newStrictMode.ThreadPolicy.Builder() .detectAll().penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectAll().penaltyLog() .build()); } super.onCreate(); } 如果主線程有網(wǎng)絡(luò)或磁盤讀寫等操作,,在logcat中會有"D/StrictMode"tag的日志輸出,從而定位到耗時操作的代碼,。
3)如果主線程無耗時操作,,還存在卡頓,有很大可能是必須在UI線程操作的一些邏輯有問題,,比如控件measure,、layout耗時過多等,此時可通過Traceview以及systrace來進行分析,。 4)Traceview:Traceview主要用做熱點分析,,找出最需要優(yōu)化的點。 > 打開DDMS然后選擇一個進程,,接著點擊上面的“Start Method Profiling”按鈕(紅色小點變?yōu)楹谏撮_始運行),,然后操作我們的卡頓UI,然后點擊"Stop Method Profiling",會打開如下界面: 圖中展示了Trace期間各方法調(diào)用關(guān)系,調(diào)用次數(shù)以及耗時比例,。通過分析可以找出可疑的耗時函數(shù)并進行優(yōu)化,; 5)systrace:抓取trace: > 執(zhí)行如下命令: $ cd android-sdk/platform-tools/systrace $ python systrace.py --time=10 -o mynewtrace.htmlsched gfx view wm > 操作APP,然后會生成一個mynewtrace.html 文件,,用Chrome打開: > 圖示如下: 通過分析上面的圖,,可以找出明顯存在的layout,measure,,draw的超時問題,。 6)導(dǎo)入如下插件,,可通過在方法上添加@DebugLog來打印方法的耗時: build.gradle: buildscript { dependencies { //用于方便調(diào)試性能問題的打印插件。給訪法加上@DebugLog,,就能輸出該方法的調(diào)用參數(shù),,以及執(zhí)行時間; classpath ‘com.jakewharton.hugo:hugo-plugin:1.2.1‘ } } //用于方便調(diào)試性能問題的打印插件,。給訪法加上@DebugLog,,就能輸出該方法的調(diào)用參數(shù),以及執(zhí)行時間,; apply plugin: ‘com.jakewharton.hugo‘ java: @DebugLog public void test( int a ){ int b=a*a; }
四,、內(nèi)存性能分析優(yōu)化 1.內(nèi)存泄露 該問題目前在項目中一般用leakcanary基本就能搞定,配置起來也相當簡單: build.gradle: dependencies { debugCompile‘com.squareup.leakcanary:leakcanary-android:1.3.1‘ // or 1.4-beta1 releaseCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:1.3.1‘// or 1.4-beta1 testCompile‘com.squareup.leakcanary:leakcanary-android-no-op:1.3.1‘ // or 1.4-beta1 } java: public class ExampleApplication extends Application {
@Overridepublic void onCreate() { super.onCreate(); LeakCanary.install(this); } } 一旦有內(nèi)存泄露,,將會在通知欄生成一條通知,,點開可看到泄露的對象以及引用路徑: 2.內(nèi)存抖動 如果代碼中存在在onDraw或者for循環(huán)等多次執(zhí)行的代碼中分配對象的行為,會導(dǎo)致運行過程中gc次數(shù)增多,,影響ui流暢度,。一般這些問題都可通過lint工具檢測出來。
五,、耗電量優(yōu)化建議 電量優(yōu)化主要是注意盡量不要影響手機進入休眠,,也就是正確申請和釋放WakeLock,另外就是不要頻繁喚醒手機,,主要就是正確使用Alarm,。 六、一些好的代碼實踐 1. 節(jié)制地使用Service 2. 當界面不可見時釋放內(nèi)存 3. 當內(nèi)存緊張時釋放內(nèi)存 4. 避免在Bitmap上浪費內(nèi)存 對大圖片,,先獲取圖片的大小信息,,根據(jù)實際需要展示大小計算inSampleSize,,最后decode,; public static BitmapdecodeSampledBitmapFromFile(String filename, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to checkdimensions final BitmapFactory.Options options = newBitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(filename, options); // Calculate inSampleSize options.inSampleSize = reqHeight); calculateInSampleSize(options, reqWidth, // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeFile(filename, options); } public static intcalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { if (width > height) { inSampleSize = Math.round((float) height / (float)reqHeight); } else { inSampleSize = Math.round((float) width / (float)reqWidth); } } return inSampleSize; } 5. 使用優(yōu)化過的數(shù)據(jù)集合 6. 謹慎使用抽象編程 7. 盡量避免使用依賴注入框架 很多依賴注入框架是基于反射的原理,雖然可以讓代碼看起來簡潔,,但是是有礙性能的,。 8. 謹慎使用externallibraries 9. 優(yōu)化整體性能 10. 使用ProGuard來剔除不需要的代碼 android { buildTypes { release{ minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile(‘proguard-android.txt‘),‘src/main/proguard-project.txt‘ signingConfig signingConfigs.debug } } 11. 慎用異常,異常對性能不利 拋出異常首先要創(chuàng)建一個新的對象。Throwable 接口的構(gòu)造函數(shù)用名為 fillInStackTrace() 的本地方法,fillInStackTrace()方法檢查棧,收集調(diào)用跟蹤信 息,。只要有異常被拋出,VM 就必要調(diào)整調(diào)用棧,因為在處理過程中創(chuàng)建了一 個新對象,。 異常只能用于錯誤處理,不應(yīng)該用來控制程序流程。 以下例子不好: try { startActivity(intentA); } catch () { startActivity(intentB); } 應(yīng)該用下面的語句判斷: if (getPackageManager().resolveActivity(intentA, 0) !=null) 不要再循環(huán)中使用 try/catch 語句,應(yīng)把其放在最外層,,使用 System.arraycopy()代替 for 循環(huán)復(fù)制,。 Android客戶端性能優(yōu)化(魅族資深工程師毫無保留奉獻) 標簽: |
|