分享 Android 開發效能最佳化的技術要點

來源:互聯網
上載者:User

Android效能調優涉及到多方面的工作,因本人技術水平有限,目前只總結了以下部分,希望大家繼續補充。

要點

使用非同步

    保持 APP 的高度響應,不要在 UI 線程做耗時操作,多使用非同步任務
    使用線程時要做好線程式控制制;使用隊列、線程池
    謹慎使用糟糕的 AysncTask 、 Timer
    警惕非同步任務引起的記憶體泄露
    應該非同步任務分類,比如 HTTP ,圖片下載,檔案讀寫,每一類的非同步任務維護一個任務隊列,而不是每一個任務都開一個線程( Volley 表示我一個可以搞定這些全部 _(:з」∠)_)
    這些常用的任務應該做好優先順序處理(一般 JSON 資料優先於圖片等待用資料的請求)
    一般非同步任務應該開啟一個 SingleAsyncTask ,保證一時只有一個線程在工作
    HTTP 和圖片下載盡量使用同一套網路請求
    使用 MVP 模式規範大型 Activity 類的行為,避免非同步任務造成的記憶體泄露

避免記憶體泄露

    瞭解虛擬機器記憶體回收機制
    頻繁 GC 也會造成卡頓,避免不必要的記憶體開銷
    錯誤的引用姿♂勢造成的記憶體泄露(啊~要泄了~)
    常見的 Activity 泄露(單例、 Application 、後台線程、無限動畫、靜態引用)
    Bitmap 泄露( HoneyComb 這個問題之前壓力好大)
    盡量使用 IntentService 代替 Service ,前者會自動 StopItself
    排查記憶體泄露問題的方法(我一直以來都是簡單暴力的人肉 dump 檢查大法)
    使用 LeakCanary 自動檢查 Activity 泄露問題
    對記憶體負載要保持敏感( Sharp )

視圖最佳化

    布局最佳化、減少層次, Include Merge
    使用 ViewStub 避免不必要的 LayoutInflate ,使用 GONE 代替重複 LayoutInflate 同一個布局
    避免過度繪製,應該減少不必要的布局背景;布局層次太深會造成過度繪製以及 Measure 、 Layout 等方法時間複雜度的指數增長
    使用過渡動畫,比如給圖片的呈現加一個輕量的淡入效果會讓視覺上變得流暢許多
    避免過度的動畫,不要讓一個介面同時出現多齣動畫,比如 List 滾動時 Item 項要停止播放動畫或者 GIF
    複雜動畫使用 SurfaceView 或 TextureView
    盡量提供多套解析度的圖片,使用向量圖

Adapter 最佳化

    複用 convertView ,用 ViewHolder 代替頻繁 findViewById
    不要重複 setListener ,要使用 v.getId 來複用 Listener ,不然會建立一堆 Listener 導致頻繁 GC
    多布局要採用 MutilItemView ,而不是使用一個大布局然後動態控制需要現實的部分
    不要在 getView 方法做做耗時的操作
    快速滾動列表的時候,可以停止載入清單項目的圖片,停止清單項目的動畫,不要在這時候改變清單項目的布局
    盡量用 RecyclerView (增量 Notify 和 RecycledViewPool 帶你飛)

代碼最佳化

    演算法最佳化,減少時間複雜度,參考一些經典的最佳化演算法
    盡量使用 int ,而不是 float 或者 double
    盡量採用基本類型,避免無必要的自動裝箱和拆箱,浪費時間和空間
    選用合適的集合類(盡量以空間換時間)、選用 Android 家的 SparseArray,SparseBooleanArray 和 LongSparseArray
    避免建立額外的對象( StringBuilder )
    使用 SO 庫完成一些比較獨立的功能(高斯模糊)
    預先處理(提前操作)一些比較耗時的初始化工作統一放到啟動圖處理
    懶載入(延遲處理)規避 Activity 的敏感生命週期
    Log 工具類,要在編譯時間刪掉調試代碼,而不是在運行時通過判斷條件規避
    優先使用靜態方法、公有方法還是公有方法?速度區別很大哦
    類內部直接對成員變數進行操作,不要使用 getter/setter 方法,調用方法耗額外的時間
    給內部類訪問的外部類成員變數要聲明稱包內可訪問,而不是私人,不然編譯的時候還是會自動建立用於訪問外部類成員變數的方法
    遍曆集合時,使用 i++代替 Iterator ,後者需要額外的對象操作,應在迴圈體內避免這種情況
    如果一個基本類型或者 String 的值不會改變,盡量用 final static ,編譯時間會直接用變數的值替換變數,也就不需要在查詢變數的值了

其他最佳化

    資料庫最佳化:使用索引、使用非同步線程
    網路最佳化 …… 一堆優秀的輪子
    避免過度使用依賴注入架構,大量的反射
    不過過度設計 /抽象,多態看起來很有設計感,代價就是額外的代碼、空間、時間
    盡量不要開啟多進程,進程的開銷很大

APK 瘦身

    開啟混淆
    使用 zipalign 工具最佳化 APK
    適當有損圖片壓縮、使用向量圖
    刪除項目中冗餘的資源,之前寫過一些刪除沒有 res 資源的指令碼
    動態載入模組化,項目拆分啊!

效能問題的排查方法

    GPU 橫條圖,沒事開來看看淘寶
    過度繪製顏色,嗯,不要一篇姨媽紅就好
    LeakCanary ,自動檢測 Activity 泄露,挺好用的
    TraceView ( Device Monitor ), Systrace ,分析哪些代碼佔用的 CPU 時間太大,屢試不爽
    Lint ,檢查不合理的 res 資源
    layoutopt (還是 optlayout ?),對當前布局提出最佳化建議,已被 lint 替代,但是還能用
    HierarchyViewer ,查看手機當前介面的布局層次,布局最佳化時常用(只用於模擬器,真機上用要 ROOT ,不想 ROOT 加得使用 ViewServer )
    StrictMode , UI 操作、網路操作等容易出現效能問題的地方,如果出現異常情況 StrictMode 會警示


Android記憶體效能最佳化


剛入門的童鞋肯能都會有一個疑問,Java不是有虛擬機器了麼,記憶體會自動化管理,我們就不必要手動的釋放資源了,反正系統會給我們完成。其實Java中沒有指標的概念,但是指標的使用方式依然存在,一味的依賴系統的gc,很容易就造成了記憶體的浪費。


Java基於記憶體回收的記憶體機制


Java的記憶體管理機制會自動回收無用對象所佔用的記憶體,減輕手工管理記憶體的負擔

1、C/C++: 從申請、使用、釋放都需要手工管理

2、Java:無用的對象的記憶體會被自動回收


什麼樣的對象是無用的對象

1、Java通過引用來操作一個具體的對象,引用類似於C 中的指標。一個對象可以持有其他對象的引用。

2、從一組根對象(GC Roots)開始,按對象之前的參考關聯性遍曆所有對象,在遍曆過程中標記所有的可達對象。如果一個對象由根對象出發不可達,則將它作為垃圾收集。

GCRoot 都有哪些?

1、 Class:由系統的類載入器載入的類對象

2、 Static Fields

3、 Thread:活著的線程

4、 Stack Local: java方法的局部變數或參數

5、 JNI Local: JNI方法中的局部引用

6、 JNI Global: 全域的JNI引用

7、 Monitor used: 用於同步的監控對象

8、Help by VM: 用於JVM特殊目的由GC保留的對象




Java程式中的記憶體流失

對象的記憶體在分配之後無法通過程式的執行邏輯釋放對該對象的引用,不能被回收該對象所佔記憶體

記憶體流失的危害

1、 引起OutOfMemoryError

2、 記憶體佔用高時JVM虛擬機器會頻繁觸發GC, 影響程式響應速度

3、記憶體佔用大的程式容易被各種清理最佳化程式中止,使用者也更傾向於卸載這些程式

Android應用的開發語言為Java,每個應用最大可使用的堆記憶體受到Android系統的限制

Android每一個應用的堆記憶體大小有限

1、 通常的情況為16M-48M

2、 通過ActivityManager的getMemoryClass()來查詢可用堆記憶體限制

3、3.0(HoneyComb)以上的版本可以通過largeHeap=“true”來申請更多的堆記憶體

Nexus S(4.2.1):normal 192, largeHeap 512

4、如果試圖申請的記憶體大於當前餘下的堆記憶體就會引發OutOfMemoryError()

5、應用程式由於各方面的限制,需要注意減少記憶體佔用,避免出現記憶體流失。

用MAT工具來檢測記憶體流失

在試圖視窗中建立一個Memory Analysis會出現一個


沒有的可以去http://www.eclipse.org/mat/downloads.php安裝一下MAT

在Android 的調試環境DDMS下,找到Heap dump



Dump下當前記憶體中的鏡像檔案,*****.hprof


能清楚的看到每一個部分暫用的記憶體大小。

也可以切換試圖,group查看不同包不同類的佔用細節。


Heap dump

? 包含了觸發Heap dump產生的時刻Java進程的記憶體快照,主要內容為各個Java類和對象在堆記憶體中的分配情況

Memory Analyzer Tool (MAT)


常見記憶體泄露原因

Context對象泄漏

1、如果一個類持有Context對象的強引用,就需要檢查其生存周期是否比Context對象更長。否則就可能發生Context泄漏。

2、View持有其建立所在Context對象的引用,如果將View對象傳遞給其它生存周期比View所在Context更長的強引用,就可能會引起記憶體流失。

例如View#setTag(int, Object)的記憶體流失https://code.google.com/p/android/issues/detail?id=18273

3、把Context對象賦給static變數。

避免Context對象泄漏Checklist

1、檢查所有持有對Context對象強引用的對象的生命週期是否超出其所持有的Context對象的生命週期。

2、檢查有沒有把View傳出到View所在Context之外的地方,如果有的話就需要檢查生命週期。

3、工具類中最好不要有Context成員變數,盡量在調用函數時直接通過調用參數傳入。如果必須有Context成員變數時,可以考慮使用WeakReference來引用Context對象。

4、View持有其建立所在Context對象的引用,如果將View對象傳遞給其它生存周期比View所在Context更長的強引用,就可能會引起記憶體流失。

5、 檢查把Context或者View對象賦給static變數的地方,看是否有Context泄漏。

6、檢查所有把View放入容器類的地方(特別是static容器類),看是否有記憶體流失。7、使用WeakHashMap也需要注意有沒有value-key的引用。

7、盡量使用ApplicationContext。

Handler對象泄漏

1、發送到Handler的Message實際上是加入到了主線程的訊息佇列等待處理,每一個Message持有其目標Handler的強引用。

如我們通常使用的匿名內部類Handler


HandlermHandler = new Handler() {
    @Override
    public voidhandleMessage(Message msg) {
       mImageView.setImageBitmap(mBitmap);
    }
}


上面是一段簡單的Handler的使用。當使用內部類(包括匿名類)來建立Handler的時候,Handler對象會隱式地持有一個外部類對象(通常是一個Activity)的引用,因為View會依附著一個Activity。而Handler通常會伴隨著一個耗時的後台線程(例如從網路拉取圖片)一起出現,這個後台線程在任務執行完畢(例如圖片下載完畢)之後,通過訊息機制通知Handler,然後Handler把圖片更新到介面。然而,如果使用者在網路請求過程中關閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時被回收掉,但由於這時線程尚未執行完,而該線程持有Handler的引用(不然它怎麼發訊息給 Handler?),這個Handler又持有Activity的引用,就導致該Activity無法被回收(即記憶體泄露),直到網路請求結束(例如圖片下載完畢)。另外,如果你執行了Handler的postDelayed()方法,該方法會將你的Handler裝入一個Message,並把這條 Message推到MessageQueue中,那麼在你設定的delay到達之前,會有一條MessageQueue -> Message -> Handler -> Activity的鏈,導致你的Activity被持有引用而無法被回收。

當然,應為是Handler對外部持有引用的原因,我們就可以將Activity設定為一個弱引用,在不必要的時候,不再執行內部方法。



/**
 * @author zhoushengtao
 * @since 2013-12-16 下午3:25:36
 */
 
import android.app.Activity;
importandroid.content.Context;
importandroid.os.Handler;
importandroid.os.Message;
 
importjava.lang.ref.WeakReference;
 
publicclass WeakRefHandler extends Handler
{
    WeakReference<context> mWeakContext;
 
    public WeakRefHandler(Context context)
    {
        mWeakContext = newWeakReference<context>(context);
    }
 
    @Override
    public void handleMessage(Message msg)
    {
        if((mWeakContext.get() instanceofActivity )&& ((Activity)mWeakContext.get()).isFinishing())
                return ;
        if(mWeakContext==null){
            return ;
        }
        super.handleMessage(msg);
    }
}


2、Non-staticinner class 和anonymous class持有其outer class的引用。


Drawable.Callback引起的記憶體流失

Drawable對象持有Drawable.callback的引用。當把一個Drawable對象設定到一個View時,Drawable對象會持有該View的引用作為Drawable.Callback


避免Drawable.Callback引起記憶體流失

? 盡量不要在static成員中儲存Drawable對象

? 對於需要儲存的Drawable對象, 在需要時調用Drawable#setCallback(null).


其他記憶體流失<??#65533;"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjwvcD4KPHAgYWxpZ249"left"> 1、Android DigitalClock引起的記憶體流失http://code.google.com/p/android/issues/detail?id=17015

2、使用Map容器類時,作為Key 的類沒有正確的實現hashCode和equal函數

其他記憶體流失

? JNI程式中的記憶體流失

1、 Malloc/free。

2、 JNI Global reference

? Thread-Local Variable

1、 相當於Thread對象的成員變數, 可以儲存線程相關的狀態

2、 如果thread是alive狀態,那麼Thread-Local中的對象就無法被GC。

進程記憶體佔用監測工具

Dumpsys

? $ dumpsys meminfo [pid]


Procrank + Shell指令碼

? #procrank

1、 VSS - Virtual Set Size 虛擬耗用記憶體(包含共用庫佔用的記憶體)

2、 RSS - Resident Set Size 實際使用實體記憶體(包含共用庫佔用的記憶體)

3、 PSS - Proportional Set Size 實際使用的實體記憶體(比例分配共用庫佔用的記憶體)

4、 USS - Unique Set Size 進程獨自佔用的實體記憶體(不包含共用庫佔用的記憶體)


Shell指令碼

#!/bin/bash

while true; do

adbshell procrank " grep "com.qihoo360.mobilesafe"

sleep1

done


當然,部分機型的sh都是經過第三方手機商精簡過的,很多命令都用不了。Procrank,就是一個經常被精簡掉的命令。

鑒於此:

自己寫了一個小工具,檢測記憶體的即時變化,

Github地址:https://github.com/stchou/JasonTest



小結

1. 儲存對象前要三思

I. 對象本身有無隱含的引用

II. 儲存後何時能夠回收

2. 要瞭解常見的隱含引用

I. anonymous class outer class

II. View to context

3. 要通過各種工具檢查記憶體佔用是否有異常

4. 建立大對象時,要檢查它的生命週期


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.