編輯:關於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 循環復制。
本文實例為大家分享了Android計時器的三種方法,具體內容如下目錄:1、借助Timer實現2、調用handler.sendMessagedely(Message msg
摘自網上的android生命周期圖: cocos2dx-2.X前後台切換分析,基於android平台:1、從後台進入前台項目的activity一般繼承自Coco
在做項目的時候,想在 ExpandableListView 中嵌套一個 GridView,在實現的過程中,遇到了不少坑,所以寫篇博客記錄一下,也順便幫助下和我一樣的新手。
從前面Android編譯系統環境初始化過程分析這篇文章可以知道,lunch命令其實是定義在build/envsetup.sh文件中的函數lunch提供的。與