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

來源:互聯網
上載者:User

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

本文由魅族科技有限公司資深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 迴圈複製。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.