以下從幾個方面來總結一下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
來源: 極客熊貓
作者: Mikyou
鏈接: http://www.youkmi.cn/2020/01/01/android-xing-neng-you-hua-zhi-leakcanary-nei-cun-yuan-li-fen-xi/
本文章著作權歸作者所有,任何形式的轉載都請注明出處。
來源: 極客熊貓
作者: Mikyou
鏈接: http://www.youkmi.cn/2020/01/01/android-xing-neng-you-hua-zhi-leakcanary-nei-cun-yuan-li-fen-xi/
本文章著作權歸作者所有,任何形式的轉載都請注明出處。
來源: 極客熊貓
作者: Mikyou
鏈接: http://www.youkmi.cn/2020/01/01/android-xing-neng-you-hua-zhi-leakcanary-nei-cun-yuan-li-fen-xi/
本文章著作權歸作者所有,任何形式的轉載都請注明出處。
來源: 極客熊貓
作者: 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); } }
