編輯:關於Android編程
性能問題一般歸結為3類:
1.UI卡頓和穩定性,這類問題用戶可直接感知,最為重要。
2.內存問題,內存問題主要表現為內存洩漏。如果存在內存洩漏,應用會不斷消耗內存,
容易導致頻繁GC使系統出現卡頓(內存不足時會調用垃圾回收機制回收內存,GC比較消耗性能),
或者出現OOM報錯。
3.耗電問題,會影響續航,表現為不必要的自啟動,不恰當持鎖導致系統無法正常休眠,系統休眠後頻繁
喚醒系統等。
一. UI卡頓常見原因和分析方法
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;二. 卡頓分析解決的一般步驟
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()方法就是一個最佳的時機,越早開啟就能在更多的代碼執行路徑上發現違規操作。
>> 監控代碼
publicvoidonCreate(){ if(DEVELOPER_MODE){ StrictMode.setThreadPolicy(newStrictMode.ThreadPolicy.Builder() .detectAll().penaltyLog().build()); StrictMode.setVmPolicy(newStrictMode.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:
> 執行如下命令:
$cdandroid-sdk/platform-tools/systrace $pythonsystrace.py--time=10-omynewtrace.htmlschedgfxviewwm
> 操作APP,然後會生成一個mynewtrace.html 文件,用Chrome打開。
> 圖示如下:
通過分析上面的圖,可以找出明顯存在的layout,measure,draw的超時問題。
6)導入如下插件,可通過在方法上添加@DebugLog來打印方法的耗時:
build.gradle: buildscript{ dependencies{
//用於方便調試性能問題的打印插件。給訪法加上@DebugLog,就能輸出該方法的調用參數,以及執行時間;
class path com.jakewharton.hugo:hugo-plugin:1.2.1 } }
//用於方便調試性能問題的打印插件。給訪法加上@DebugLog,就能輸出該方法的調用參數,以及執行時間;
apply plugin:com.jakewharton.hugo java: @Debug Log public void test(int a){ int b=a*a; }三.內存性能優化分析(內存洩漏問題)
Java是垃圾回收語言的一種,其優點是開發者無需特意管理內存分配,降低了應用由於局部故障(segmentation fault)導致崩潰,同時防止未釋放的內存把堆棧(heap)擠爆的可能,所以寫出來的代碼更為安全。
不幸的是,在Java中仍存在很多容易導致內存洩漏的邏輯可能(logical leak)。如果不小心,你的Android應用很容易浪費掉未釋放的內存,最終導致內存用光的錯誤拋出(out-of-memory,OOM)。
一般內存洩漏(traditional memory leak)的原因是:當該對象的所有引用都已經釋放了,對象仍未被釋放。(譯者注:Cursor忘記關閉等)
邏輯內存洩漏(logical memory leak)的原因是:當應用不再需要這個對象,當仍未釋放該對象的所有引用。
如果持有對象的強引用,垃圾回收器是無法在內存中回收這個對象。
在Android開發中,最容易引發的內存洩漏問題的是Context。比如Activity的Context,就包含大量的內存引用,例如View Hierarchies和其他資源。一旦洩漏了Context,也意味洩漏它指向的所有對象。Android機器內存有限,太多的內存洩漏容易導致OOM。
檢測邏輯內存洩漏需要主觀判斷,特別是對象的生命周期並不清晰。幸運的是,Activity有著明確的生命周期,很容易發現洩漏的原因。Activity.onDestroy()被視為Activity生命的結束,程序上來看,它應該被銷毀了,或者Android系統需要回收這些內存(譯者注:當內存不夠時,Android會回收看不見的Activity)。
如果這個方法執行完,在堆棧中仍存在持有該Activity的強引用,垃圾回收器就無法把它標記成已回收的內存,而我們本來目的就是要回收它!
結果就是Activity存活在它的生命周期之外。
Activity是重量級對象,應該讓Android系統來處理它。然而,邏輯內存洩漏總是在不經意間發生。(譯者注:曾經試過一個Activity導致20M內存洩漏)。在Android中,導致潛在內存洩漏的陷阱不外乎兩種:
檢查一下你有沒有遇到下列的情況。
在類中定義了靜態Activity變量,把當前運行的Activity實例賦值於這個靜態變量。
如果這個靜態變量在Activity生命周期結束後沒有清空,就導致內存洩漏。因為static變量是貫穿這個應用的生命周期的,所以被洩漏的Activity就會一直存在於應用的進程中,不會被垃圾回收器回收。
static Activity activity; void setStaticActivity() { activity = this; } View saButton = findViewById(R.id.sa_button); saButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setStaticActivity(); nextActivity(); } });Memory Leak 1 – Static Activity
類似的情況會發生在單例模式中,如果Activity經常被用到,那麼在內存中保存一個實例是很實用的。正如之前所述,強制延長Activity的生命周期是相當危險而且不必要的,無論如何都不能這樣做。
特殊情況:如果一個View初始化耗費大量資源,而且在一個Activity生命周期內保持不變,那可以把它變成static,加載到視圖樹上(View Hierachy),像這樣,當Activity被銷毀時,應當釋放資源。(譯者注:示例代碼中並沒有釋放內存,把這個static view置null即可,但是還是不建議用這個static view的方法)
static view; void setStaticView() { view = findViewById(R.id.sv_button); } View svButton = findViewById(R.id.sv_button); svButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setStaticView(); nextActivity(); } });Memory Leak 2 – Static View
繼續,假設Activity中有個內部類,這樣做可以提高可讀性和封裝性。將如我們創建一個內部類,而且持有一個靜態變量的引用,恭喜,內存洩漏就離你不遠了(譯者注:銷毀的時候置空,嗯)。
private static Object inner; void createInnerClass() { class InnerClass { } inner = new InnerClass(); } View icButton = findViewById(R.id.ic_button); icButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { createInnerClass(); nextActivity(); } });Memory Leak 3 – Inner Class
內部類的優勢之一就是可以訪問外部類,不幸的是,導致內存洩漏的原因,就是內部類持有外部類實例的強引用。
相似地,匿名類也維護了外部類的引用。所以內存洩漏很容易發生,當你在Activity中定義了匿名的AsyncTsk
。當異步任務在後台執行耗時任務期間,Activity不幸被銷毀了(譯者注:用戶退出,系統回收),這個被AsyncTask持有的Activity實例就不會被垃圾回收器回收,直到異步任務結束。
void startAsyncTask() { new AsyncTaskMemory Leak 4 – AsyncTask() { @Override protected Void doInBackground(Void... params) { while(true); } }.execute(); } super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); View aicButton = findViewById(R.id.at_button); aicButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startAsyncTask(); nextActivity(); } });
同樣道理,定義匿名的Runnable,用匿名類Handler執行。Runnable內部類會持有外部類的隱式引用,被傳遞到Handler的消息隊列MessageQueue中,在Message消息沒有被處理之前,Activity實例不會被銷毀了,於是導致內存洩漏。
void createHandler() { new Handler() { @Override public void handleMessage(Message message) { super.handleMessage(message); } }.postDelayed(new Runnable() { @Override public void run() { while(true); } }, Long.MAX_VALUE >> 1); } View hButton = findViewById(R.id.h_button); hButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { createHandler(); nextActivity(); } });Memory Leak 5 – Handler
我們再次通過Thread和TimerTask來展現內存洩漏。
void spawnThread() { new Thread() { @Override public void run() { while(true); } }.start(); } View tButton = findViewById(R.id.t_button); tButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { spawnThread(); nextActivity(); } });Memory Leak 6 – Thread
只要是匿名類的實例,不管是不是在工作線程,都會持有Activity的引用,導致內存洩漏。
void scheduleTimer() { new Timer().schedule(new TimerTask() { @Override public void run() { while(true); } }, Long.MAX_VALUE >> 1); } View ttButton = findViewById(R.id.tt_button); ttButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { scheduleTimer(); nextActivity(); } });Memory Leak 7 – TimerTask
最後,通過Context.getSystemService(int name)可以獲取系統服務。這些服務工作在各自的進程中,幫助應用處理後台任務,處理硬件交互。如果需要使用這些服務,可以注冊監聽器,這會導致服務持有了Context的引用,如果在Activity銷毀的時候沒有注銷這些監聽器,會導致內存洩漏。
void registerListener() { SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL); sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST); } View smButton = findViewById(R.id.sm_button); smButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { registerListener(); nextActivity(); } });Memory Leak 8 – Sensor Manager
四、耗電量優化建議
電量優化主要是注意盡量不要影響手機進入休眠,也就是正確申請和釋放WakeLock,另外就是不要頻繁喚醒手機,主要就是正確使用Alarm。
五、一些好的代碼實踐
1. 節制地使用Service
2. 當界面不可見時釋放內存
3. 當內存緊張時釋放內存
4. 避免在Bitmap上浪費內存
對大圖片,先獲取圖片的大小信息,根據實際需要展示大小計算inSampleSize,最後decode;
public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth,int reqHeight){ //First decodewithinJustDecodeBounds=truetocheckdimensions final BitmapFactory.Optionsoptions=new BitmapFactory.Options(); options.inJustDecodeBounds=true; BitmapFactory.decodeFile(filename,options); //CalculateinSampleSize options.inSampleSize= reqHeight); calculateInSampleSize(options, reqWidth,reHeight); //DecodebitmapwithinSampleSizeset options.inJustDecodeBounds=false; return BitmapFactory.decodeFile(filename,options); } public static int calculateInSampleSize(BitmapFactory.Optionsoptions, intreqWidth,intreqHeight){ //Rawheightandwidthofimage 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. 謹慎使用external libraries
9. 優化整體性能
10. 使用ProGuard來剔除不需要的代碼
android{ buildTypes{ release{ minifyEnabledtrue shrinkResources true proguardFilesgetDefaultProguardFile(proguard-android.txt),src/main/proguard-project.txt signingConfigsigningConfigs.debug } }
11. 慎用異常,異常對性能不利
拋出異常首先要創建一個新的對象。Throwable 接口的構造函數用名為
fillInStackTrace() 的本地方法,fillInStackTrace() 方法檢查棧,收集調用跟蹤信
息。只要有異常被拋出,VM 就必要調整調用棧,因為在處理過程中創建了一
個新對象。
異常只能用於錯誤處理,不應該用來控制程序流程。
以下例子不好:
try{ startActivity(intentA); }catch(){ startActivity(intentB); }
應該用下面的語句判斷:
if(getPackageManager().resolveActivity(intentA,0)!=null)
不要再循環中使用 try/catch 語句,應把其放在最外層,使用 System.arraycopy()代替 for 循環復制。
(一)概述在上一節中我們對Fragment進行了一個初步的了解,學習了概念,生命周期,Fragment管理與 Fragment事務,以及動態與靜態加載Fragment。從
layout<?xml version=1.0?>-<LinearLayout android:paddingTop=@dimen/ac
本篇教程中使用到的Android Studio版本為1.0, Eclipse ADT版本23.0.4。請嘗試更新到該版本。Android Studio默認使用 Gradl
簡介:Async-http是一款國外的開源框架,作者是loopj。是基於Apache HttpClient庫的。可以方便快速高效的進行網絡數據請求和發送,文件下載和上傳。