Android客戶端效能優化(魅族資深工程師毫無保留奉獻)

丿灬安之若死發表於2017-10-27

本文由魅族科技有限公司資深Android開發工程師degao(嵌入式企鵝圈原創團隊成員)撰寫,是degao在嵌入式企鵝圈發表的第一篇原創文章,毫無保留地總結分享其在領導魅族多個專案開發中的Android客戶端效能優化經驗,極具實踐價值!

即日起,嵌入式企鵝圈將在之前五個專欄(Linux核心驅動情景分析、資源緊缺型SOC嵌入式架構設計、嵌入式交叉工具鏈及其應用、嵌入式設計和程式設計、微信硬體平臺和物聯網解決方案)新增Android開發專欄!更多Android、Linux、嵌入式和物聯網原創技術分享敬請關注微信公眾號:嵌入式企鵝圈。

眾所周知,一個好的產品,除了功能強大,好的效能也必不可少。有調查顯示,近90%的受訪者會因為APP效能差而解除安裝,效能也是造成APP使用者沮喪的頭號原因。

那Android客戶端效能的指標都有哪些?如何發現和定位客戶端的效能問題?本文結合多個專案的開發實踐,給出了要關注的重要指標專案,以及定位和解決效能問題的一般步驟。

效能優化應該貫穿於功能開發的全部週期,而不是做完一次後面便不再關注。每次釋出版本前,最好能對照標準檢查下效能是否達標。

記住:產品=效能×功能!

一、 效能檢查項

1. 啟動速度

1)這裡的啟動速度指的是冷啟動的速度,即殺掉應用後重新啟動的速度,此項主要是和你的競品對比。

2)不應在Application以及Activity的生命週期回撥中做任何費時操作,具體指標大概是你在onCreate,onResume,onStart等回撥中所花費的總時間最好不要超過400ms,否則使用者在桌面點選你的應用圖示後,將感覺到明顯的卡頓。

2. 介面切換

1)應用操作時,介面和動畫不應有明顯示卡頓;

2)可通過在手機上開啟 設定->開發者選項->除錯GPU過度繪製,然後操作應用檢視gpu是否超線進行初步判斷;

\

 

3. 記憶體洩露

1)back退出不應存在記憶體洩露,簡單的檢查辦法是在退出應用後,用命令`adb shell dumpsys meminfo 應用包名`檢視 `Activities Views` 是否為零;

2)多次進入退出後的佔用記憶體`TOTAL`不應變化太大;

\

4. onTrimMemory回撥

1)應用響應此回撥釋放非必須記憶體;

2驗證可通過命令`adb shelldumpsys gfxinfo 應用包名-cmd trim 5`後,再)用命令`adb shell dumpsys meminfo 應用包名`檢視記憶體大小

5. 過度繪製

1)開啟設定中的GPU過度繪製開關,各介面過度繪製不應超過2.5x;也就是開啟此除錯開關後,介面整體呈現淺色,特別複雜的介面,紅色區域也不應該超過全螢幕的四分之一;

6. lint檢查:

1)通過Android Studio中的 Analyze->Inspect Code 對工程程式碼做靜態掃描;找出潛在的問題程式碼並修改;

2) 0 error & 0warning,如果確實不能解決,需給出原因。

\

7. 反射優化:

1)在程式碼中減少反射呼叫;

2)對頻繁呼叫的返回值進行Cache;

8. 穩定性:

1)連續48小時monkey不應出現閃退,anr問題。

2)如果應用接入了資料埋點的sdk,比如百度統計sdk,友盟統計sdk等,這些sdk都會將應用的崩潰資訊上報回來,開發者應每天關注這些統計到的崩潰日誌,嚴格控制應用的崩潰率;

9. 耗電:

1)應用進入後臺後不應異常消耗電量;

2)操作應用後,退出應用,讓應用處於後臺,一段時間後通過`adb shell dumpsysbatterystats`檢視電量消耗日誌看是否存在異常。

二、效能問題常見原因

效能問題一般歸結為三類:

1. UI卡頓和穩定性:這類問題使用者可直接感知,最為重要;

2. 記憶體問題:記憶體問題主要表現為記憶體洩露,或者記憶體使用不當導致的記憶體抖動。如果存在記憶體洩露,應用會不斷消耗記憶體,易導致頻繁gc使系統出現卡頓,或者出現OOM報錯;記憶體抖動也會導致UI卡頓。

3. 耗電問題:會影響續航,表現為不必要的自啟動,不恰當持鎖導致系統無法正常休眠,系統休眠後頻繁喚醒系統等;

三、UI卡頓常見原因和分析方法

下面分別介紹出現這些問題的常見原因以及分析這些問題的一般步驟。

1.卡頓常見原因

1)人為在UI執行緒中做輕微耗時操作,導致UI執行緒卡頓;

2) 佈局Layout過於複雜,無法在16ms內完成渲染;

3)同一時間動畫執行的次數過多,導致CPU或GPU負載過重;

4) View過度繪製,導致某些畫素在同一幀時間內被繪製多次,從而使CPU或GPU負載過重;

5) View頻繁的觸發measure、layout,導致measure、layout累計耗時過多及整個View頻繁的重新渲染;

6) 記憶體頻繁觸發GC過多(同一幀中頻繁建立記憶體),導致暫時阻塞渲染操作;

7) 冗餘資源及邏輯等導致載入和執行緩慢;

8)工作執行緒優先順序未設定為Process.THREAD_PRIORITY_BACKGROUND,導致後臺執行緒搶佔UI執行緒cpu時間片,阻塞渲染操作;

9) ANR;

2. 卡頓分析解決的一般步驟:

1)解決過度繪製問題

>在設定->開發者選項->除錯GPU過度繪製中開啟除錯,看對應介面是否有過度繪製,如果有先解決掉:

\

> 定位過渡繪製區域

> 利用Android提供的工具進行位置確認以及修改(HierarchyView , Tracer for OpenGL ES)

> 定位到具體的檢視(xml檔案或者View)

> 通過程式碼和xml檔案分析過渡繪製的原因

> 結合具體情況進行優化

> 使用Lint工具進一步優化

2) 檢查是否有主執行緒做了耗時操作:

嚴苛模式(StrictMode),是Android提供的一種執行時檢測機制,用於檢測程式碼執行時的一些不規範的操作,最常見的場景是用於發現主執行緒的IO操作。應用程式可以利用StrictMode儘可能的發現一些編碼的疏漏。

> 開啟 StrictMode:

>> 對於應用程式而言,Android 提供了一個最佳使用實踐:儘可能早的在

android.app.Application 或 android.app.Activity 的生命週期使能 StrictMode,onCreate()方法就是一個最佳的時機,越早開啟就能在更多的程式碼執行路徑上發現違規操作。

>> 監控程式碼

public voidonCreate() {

if (DEVELOPER_MODE) {

StrictMode.setThreadPolicy(newStrictMode.ThreadPolicy.Builder()

.detectAll().penaltyLog() .build());

StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()

.detectAll().penaltyLog() .build());

}

super.onCreate();

}

如果主執行緒有網路或磁碟讀寫等操作,在logcat中會有"D/StrictMode"tag的日誌輸出,從而定位到耗時操作的程式碼。

3)如果主執行緒無耗時操作,還存在卡頓,有很大可能是必須在UI執行緒操作的一些邏輯有問題,比如控制元件measure、layout耗時過多等,此時可通過Traceview以及systrace來進行分析。

4)Traceview:Traceview主要用做熱點分析,找出最需要優化的點。

> 開啟DDMS然後選擇一個程式,接著點選上面的“Start Method Profiling”按鈕(紅色小點變為黑色即開始執行),然後操作我們的卡頓UI,然後點選"Stop Method Profiling",會開啟如下介面:

\

圖中展示了Trace期間各方法呼叫關係,呼叫次數以及耗時比例。通過分析可以找出可疑的耗時函式並進行優化;

5)systrace:抓取trace:

> 執行如下命令:

$ 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)匯入如下外掛,可通過在方法上新增@DebugLog來列印方法的耗時:

build.gradle:

buildscript {

dependencies {

//用於方便除錯效能問題的列印外掛。給訪法加上@DebugLog,就能輸出該方法的呼叫引數,以及執行時間;

classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'

}

}

//用於方便除錯效能問題的列印外掛。給訪法加上@DebugLog,就能輸出該方法的呼叫引數,以及執行時間;

apply plugin: 'com.jakewharton.hugo'

java:

@DebugLog

public void test( int a ){

int b=a*a;

}

四、記憶體效能分析優化

1.記憶體洩露

該問題目前在專案中一般用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);

}

}

一旦有記憶體洩露,將會在通知欄生成一條通知,點開可看到洩露的物件以及引用路徑:

\

2.記憶體抖動

如果程式碼中存在在onDraw或者for迴圈等多次執行的程式碼中分配物件的行為,會導致執行過程中gc次數增多,影響ui流暢度。一般這些問題都可通過lint工具檢測出來。

五、耗電量優化建議

電量優化主要是注意儘量不要影響手機進入休眠,也就是正確申請和釋放WakeLock,另外就是不要頻繁喚醒手機,主要就是正確使用Alarm。

六、一些好的程式碼實踐

1. 節制地使用Service

2. 當介面不可見時釋放記憶體

3. 當記憶體緊張時釋放記憶體

4. 避免在Bitmap上浪費記憶體

對大圖片,先獲取圖片的大小資訊,根據實際需要展示大小計算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. 使用優化過的資料集合

6. 謹慎使用抽象程式設計

7. 儘量避免使用依賴注入框架

很多依賴注入框架是基於反射的原理,雖然可以讓程式碼看起來簡潔,但是是有礙效能的。

8. 謹慎使用externallibraries

9. 優化整體效能

10. 使用ProGuard來剔除不需要的程式碼

android {

buildTypes {

release{

minifyEnabled true

shrinkResources true

proguardFiles getDefaultProguardFile('proguard-android.txt'),'src/main/proguard-project.txt'

signingConfig signingConfigs.debug

}

}

11. 慎用異常,異常對效能不利

丟擲異常首先要建立一個新的物件。Throwable 介面的建構函式用名為

fillInStackTrace() 的本地方法,fillInStackTrace()方法檢查棧,收集呼叫跟蹤信

息。只要有異常被丟擲,VM 就必要調整呼叫棧,因為在處理過程中建立了一

個新物件。

異常只能用於錯誤處理,不應該用來控制程式流程。

以下例子不好:

try {

startActivity(intentA);

} catch () {

startActivity(intentB);

}

應該用下面的語句判斷:

if (getPackageManager().resolveActivity(intentA, 0) !=null)

不要再迴圈中使用 try/catch 語句,應把其放在最外層,使用 System.arraycopy()代替 for 迴圈複製。

相關文章