Android性能優化總結


以下從幾個方面來總結一下Android的性能優化:

1:界面卡頓優化

2:內存優化

3:App啟動優化

 

1:界面卡頓優化

Android的界面為每秒60幀,即必須在16ms內完成1幀的繪制,如果某個方法耗時過程,導致16ms內無法完成繪制,會導致丟幀,丟幀的多了,直觀上感受就是界面卡頓。

60幀是人眼觀看動畫比較合適的頻率,如果每秒的幀數過少,即頻繁的出現丟幀,就會感覺界面的卡頓。

1:通過Traceview找出卡主主線程的地方
卡住主線程說明函數在主線程被調用的時長比較長,包括:

1)單個函數調用的時長長

2)函數被調用的次數比較多

2:使用方法:

1)Terminal打開DDMS,輸入指令:Monitor

2)點擊start method profiling

 

 3)操作APP可能有問題的界面

4)再次點擊stop method profiling,生成表格:

 

 

3:具體案例分析:recyclerView的onBindViewHolder,當復用屏幕外的數據是,是臟數據,會進行重新綁定,調用onBindViewHolder

@Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
        {
            Log.d(TAG, "onBindViewHolder--->" + position);
            。。。。。。
       。。。。。。。
SystemClock.sleep(7); }

SystemClock.sleep(10)模擬RecyclerView滾動過程中的耗時操作,操作Recyclerview,得到以下表格:

 Real Time/Call表示一個函數被調用的時長, Calls+Recursion + call totals表示一個函數被調用和被遞歸調用的次數

 

2:內存優化:

可達性分析:凡是被GC Root對象引用的對象,都不可回收

 

可以作為GC Root 引用點的是:

 

  • JavaStack中的引用的對象。
  • 方法區中靜態引用指向的對象。
  • 方法區中常量引用指向的對象。
  • Native方法中JNI引用的對象。

 

1:Memory Monitor:

Memory Monitor只能看個大概,可以查看內存抖動,或者內存增長的趨勢,具體的小的泄漏,還得通過Heap Viewer查看

內存抖動:短時間內發生了多次內存的漲跌,意味着很有可能發生率內存抖動。

內存抖動帶來的問題:短時間內的內存飆升,系統需要頻繁的進行GC(在GC線程上),而GC是需要主線程停下來(不單單是主線程,而是把所有的線程掛起來),並且占用CPU資源的,會導致界面卡頓。

 

例子1:

 public void click(View view) {
        for (int i = 0; i < 100; i++) {
            byte[] b = new byte[2000];
        }
    }

 

例子2:字符串String拼接導致,會創建大量的StringBuilder臨時對象,

在循環中頻繁執行字符串拼接操作時。這是因為字符串通常是不可變的,每次進行字符串拼接操作時,都會創建一個新的字符串對象

String result = "";
for (int i = 0; i < 10000; i++) {
    result += " " + i;
}

 

例子3:

 

private int nums[][] = new int[250][250];
//內存抖動案例:
//短時間內創建大量的臨時變量
private void init() { Random random = new Random(); for (int i = 0; i < nums.length; i++) { for(int j=0; j<nums[i].length; j++){ nums[i][j] = random.nextInt(1000); } } } private void printNums(){ String totalNums = ""; for (int i = 0; i < nums.length; i++) { for(int j=0; j<nums[i].length; j++){ totalNums += nums[i][j]; } } }

//優化: private void printNums2(){ StringBuffer totalNums = new StringBuffer(); for (int i = 0; i < nums.length; i++) { for(int j=0; j<nums[i].length; j++){ totalNums.append(nums[i][j]); } } }
 

 

例子4:

自定義View的時候,調用invalidate,會導致不斷調用onDraw,如果在onDraw里面new對象,就會導致內存不斷升高。

要注意在onDraw中的對象創建和資源管理,確保不頻繁地調用invalidate

把創建的對象放在構造函數

 

使用Monitor監控:

 

 

 

避免內存抖動的方法:

1)盡量避免在循環體內創建對象,應該把對象創建移到循環體外

2)自定義view的onDraw會被頻繁調用,避免在這個函數里面new一個新的對象

 

 

2:Heap Viewer:

監控:能夠實時觀測內存的變動(短時間內通過Memory Monitor是看不出來的,曲線坡度太小,內存變動值很小,定位不到具體的代碼)

Heap Viewer具體定位到哪個位置內存泄漏。

內存泄漏例子handler:

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //test1();
        init();
        handler.sendEmptyMessageDelayed(0, 100000);
    }

 

//模擬當MainActivity跳轉到MainActivity2的時候,
    // 延遲發送消息導致的內存泄漏問題
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

    public void click(){
        Intent intent = new Intent(this, MainActivity2.class);
        startActivity(intent);
        finish();
    }

 

 首先運行應用,處在MainActivity,點擊Dump Java Heap,獲取當前內存的快照:

 

 得到以下表格:

 

 修改為根據包名查看更方便快速定位到我們自己的代碼:

 

 

Leaks為0說明當前沒有發生內存泄漏

MainActivity的depth為3,說明MainActivity有被引用

 

點擊MainActivity的click,跳轉到MainActivity2,MainActivity.finish(),模擬MainActivty發生內存泄漏的場景(Handler引用MainActivity導致內存泄漏),然后點擊GC,再截取內存快照

Dump Java Heap:

 

 黃色的Leaks為2說明發生了內存泄漏,點擊Leaks,通過以下References可以找到MainActivity被引用的路徑。

 

 3:LeakCanary

在一個Activity執行完onDestroy后,將它放入到WeakReference中,然后將這個WeakReference類型的Activity的對象與ReferenceQueue關聯,注意: 如果一個對象要被GC回收了,會把它引用的對象放入到ReferenceQueue中。這時候只需要在ReferenceQueue中去查找是否存在該對象,如果沒有就執行一個GC,再次查找,如果還是沒有,則說明該對象可能無法被回收,也就可能發生了內存泄漏,最后使用HAHA這個開源庫取分析dump之后的heap內存
來源: 極客熊貓
作者: Mikyou
鏈接: http://www.youkmi.cn/2020/01/01/android-xing-neng-you-hua-zhi-leakcanary-nei-cun-yuan-li-fen-xi/
本文章著作權歸作者所有,任何形式的轉載都請注明出處。
在一個Activity執行完onDestroy后,將它放入到WeakReference中,然后將這個WeakReference類型的Activity的對象與ReferenceQueue關聯,注意: 如果一個對象要被GC回收了,會把它引用的對象放入到ReferenceQueue中。這時候只需要在ReferenceQueue中去查找是否存在該對象,如果沒有就執行一個GC,再次查找,如果還是沒有,則說明該對象可能無法被回收,也就可能發生了內存泄漏,最后使用HAHA這個開源庫取分析dump之后的heap內存
來源: 極客熊貓
作者: Mikyou
鏈接: http://www.youkmi.cn/2020/01/01/android-xing-neng-you-hua-zhi-leakcanary-nei-cun-yuan-li-fen-xi/
本文章著作權歸作者所有,任何形式的轉載都請注明出處。
在一個Activity執行完onDestroy后,將它放入到WeakReference中,然后將這個WeakReference類型的Activity的對象與ReferenceQueue關聯,注意: 如果一個對象要被GC回收了,會把它引用的對象放入到ReferenceQueue中。這時候只需要在ReferenceQueue中去查找是否存在該對象,如果沒有就執行一個GC,再次查找,如果還是沒有,則說明該對象可能無法被回收,也就可能發生了內存泄漏,最后使用HAHA這個開源庫取分析dump之后的heap內存
來源: 極客熊貓
作者: Mikyou
鏈接: http://www.youkmi.cn/2020/01/01/android-xing-neng-you-hua-zhi-leakcanary-nei-cun-yuan-li-fen-xi/
本文章著作權歸作者所有,任何形式的轉載都請注明出處。
在一個Activity執行完onDestroy后,將它放入到WeakReference中,然后將這個WeakReference類型的Activity的對象與ReferenceQueue關聯,注意: 如果一個對象要被GC回收了,會把它引用的對象放入到ReferenceQueue中。這時候只需要在ReferenceQueue中去查找是否存在該對象,如果沒有就執行一個GC,再次查找,如果還是沒有,則說明該對象可能無法被回收,也就可能發生了內存泄漏,最后使用HAHA這個開源庫取分析dump之后的heap內存
來源: 極客熊貓
作者: Mikyou
鏈接: http://www.youkmi.cn/2020/01/01/android-xing-neng-you-hua-zhi-leakcanary-nei-cun-yuan-li-fen-xi/
本文章著作權歸作者所有,任何形式的轉載都請注明出處。

 分為兩個步驟:

1)通過虛引用的ReferenceQueue,判斷一個對象是否被回收:

虛引用:對於對象來說,是無感的,如果只存在虛引用,GC的時候會直接被回收。虛引用的目的是為了追蹤一個對象被回收的時機。如果一個定義了虛引用的對象GC之后被回收了,這個對象會被放入RefereceQueue中,LeakCanary就是在GC之后去檢測該隊列中是否有該對象判斷該對象是否已經被回收。

2)初步判定有內存泄漏之后,通過開源庫Haha分析dump之后的heap內存,從而定位到具體的內存泄露的對象的引用鏈條。

 

4:內存泄露舉例:

1)bindservice導致的內存泄露:非靜態內部類默認持有外部類的引用,這里的ServiceConnection持有外部類

SubAppDelegate的引用,如果serviceConnection沒有被解綁,就會有一直持有外部類,導致外部類無法被回收,造成內存泄露。

解決方案:

應該在不再需要服務綁定的時候,確保解除 ServiceConnection 對象的引用。如果 ServiceConnection 對象必須在較長的時間內存活,您可以考慮使用弱引用(WeakReference)來持有它,這樣可以使其更容易被垃圾回收。

 

 內存泄露有哪些導致:

1)單利模式

2)靜態變量

3)handler

4)匿名內部類

5)資源的額釋放,數據庫,文件,流,bitmap

6)注冊廣播、eventBUs,onDestroy里面要反注冊unRegister

7)context,取短不取長,傳activity的context

8)arrayList和Linkhashmap,在onDestroy里面,進行數據的clear

 

3:APP應用啟動優化:

1:冷啟動和熱啟動

1)冷啟動:app沒有啟動過或者進程被殺死,系統不存在該app進程,此時啟動為冷啟動。冷啟動流程就是app啟動流程全過程,包括創建app進程、加載資源、啟動MainThread、初始化SplashActivity並加載布局等。

2)熱啟動:app暫時退到了后台,熱啟動將它從后台重新帶到前台,展示給客戶。點擊home鍵退出,再進去,執行onResume。

我們要做的優化就是針對熱啟動

3):冷啟動的函數調用過程:

Zygote Fork Proccess 
-> Application:attachBaseContext()
-> Application:onCreate() 
-> MainActiviity:onCreate()

 

2:用TraceView獲取App的啟動耗時,查找具體的耗時的函數進行優化:

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        Debug.startMethodTracing("startApp"); super.onCreate();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Debug.stopMethodTracing();
    }
}

在/sdcard/目錄下得到一個startApp.trace文件

 

 

 通過DDMS打開該文件:可以看到Application里面的onCreate里的哪些代碼進行了耗時操作:

 

 

 3:解決方案:

1)白屏問題:

將啟動頁主題背景設置成閃屏頁圖片,這么做的目的主要是為了消除啟動時的黑白屏,給用戶一種秒響應的感覺,但是並不會真正減少用戶啟動時間,僅屬於視覺優化。

為很么需要一個splashActivity來顯示啟動的背景圖,而不是直接將背景圖設置在MainActivity:

因為被啟動的時候,不一定是到MainActivty,也可能是其他界面,所以需要一個splashActivity界面來統一處理外部調用時的白屏問題,然后再由splashActivity來進行分發到哪個需要啟動的界面。

 2)第三方工具的初始化一般都在Application的onCreate里面執行,會造成大量的耗時,解決方案:

      a:放在子線程中加載不影響業務的情況,則優先選擇放在子線程中加載

      b:第三方工具的懶加載初始化,即用到的時候再進行初始化

 

4:APK瘦身:

1:使用 lint 工具來檢測res/中是否有沒有使用到的資源;

2:使用 WebP 來代替JPG和PNG圖片。WebP 保留了JPG和PNG優點的同時,能提供更好的壓縮,達到更小的體積

3:避免使用枚舉類型

 

5:Parcel:

1:Serializable和Parcelable哪個性能高,為什么?

針對Android設備,Parcelable的性能高。Serializable是針對IO流,而Parcelable是針對共享內存。

 

public class Student implements Parcelable {
    private String name;
    private String address;
    private int age;

    protected Student(Parcel in) {
        name = in.readString();
        address = in.readString();
        age = in.readInt();
    }

    public static final Creator<Student> CREATOR = new Creator<Student>() {
        @Override
        public Student createFromParcel(Parcel in) {
            return new Student(in);
        }

        @Override
        public Student[] newArray(int size) {
            return new Student[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {

        dest.writeString(name);
        dest.writeString(address);
        dest.writeInt(age);
    }
}

 

 

 

 

 

 

 


 

 






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM