前言
北京時間2022年8月16日凌晨 Android 13正式版全球也已發布,android 每次發新版本都會圍繞“優化”這個詞展開,android 13也不例外 個人認為他最大的亮點是UI界面優化,其實經過這么多年更新迭代 Android系統性能也已經非常流暢,可以在體驗上完全媲美iOS。但是,到了各大廠商手里,改源碼、自定義系統,使得Android原生系統變得魚龍混雜,同時開發人員技術水平的參差不齊,即使很多手機在跑分軟件性能非常高,打開應用依然存在卡頓現象。另外,隨著產品內容迭代,功能越來越復雜,UI頁面也越來越豐富,也成為流暢運行的一種阻礙。綜上所述,對APP進行性能優化已成為開發者該有的一種綜合素質,也是開發者能夠完成高質量應用程序作品的保證。
優化點
Android的性能優化,主要是從以下幾個方面進行優化的:
整體優化方向:
優化方案
一:流暢性
1. 響應速度:
多線程的方式 包括:AsyncTask、繼承 Thread類、實現 Runnable接口、Handler消息機制、HandlerThread等
注:實際開發中,當一個進程發生了ANR后,系統會在 /data/anr目錄下創建一個文件 traces.txt,通過分析該文件可定位出ANR的原因
2. 啟動速度:app啟動(Application )、頁面啟動(activity)、h5(wabView)
app啟動(Application ):app啟動分為冷啟動(Cold start)、熱啟動(Hot start)和溫啟動(Warm start)三種(概念不再贅述,類似博客挺多挺詳細的)。
無論何種啟動,我們的優化點都是: Application、Activity創建以及回調等過程
谷歌官方給的建議是:
1、利用提前展示出來的Window,快速展示出來一個界面,給用戶快速反饋的體驗;
2、避免在啟動時做密集沉重的初始化(Heavy app initialization);
3、避免I/O操作、反序列化、網絡操作、布局嵌套等。
方案1: 治標不治本的迷惑性做法:主題設置圖片資源
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
<!-- The background color, preferably the same as your normal theme -->
<item android:drawable="@android:color/white"/>
<!-- Your product logo - 144dp color version of your app icon -->
<item>
<bitmap
android:src="@drawable/product_logo_144dp"
android:gravity="center"/>
</item>
</layer-list>
方案2: 避免在啟動時做密集沉重的初始化
application 中init三方SDK耗時優化:
1、業務非必要的可以的異步加載(線程池)。
2、非第一時間需要的可以在主線程做延時啟動。當程序已經啟動起來之后,在進行初始化。
3、對于圖片,網絡請求框架必須在主線程里初始化了。
4、啟動圖+開屏廣告(以時間換時間 利用廣告加載的時間初始化sdk)
ps:以上操作也僅僅是在應用層能做總有一些能到極限的操作
5、 通過利用多進程、修改類加載過程、dex壓縮等方向去實現
3. 頁面顯示速度
方案1: 頁面布局優化
如果父控件有顏色,也是自己需要的顏色,那么就不必在子控件加背景顏色
如果每個自控件的顏色不太一樣,而且可以完全覆蓋父控件,那么就不需要再父控件上加背景顏色
盡量減少不必要的嵌套
能用LinearLayout和FrameLayout,就不要用RelativeLayout,因為RelativeLayout控件相對比較復雜,測繪也想要耗時。
使用include和merge增加復用,減少層級
ViewStub按需加載,更加輕便
復雜界面可選擇ConstraintLayout,可有效減少層級
ps:開發者模式,查看是否存在過度繪制
方案2:繪制優化
onDraw中不要創建新的局部對象、也不要做耗時的任務
ps:我們平時感覺的卡頓問題最主要的原因之一是因為渲染性能,因為越來越復雜的界面交互,其中可能添加了動畫,或者圖片等等。我們希望創造出越來越炫的交互界面,同時也希望他可以流暢顯示,但是往往卡頓就發生在這里。
這個是Android的渲染機制造成的,Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,但是渲染未必成功,如果成功了那么代表一切順利,但是失敗了可能就要延誤時間,或者直接跳過去,給人視覺上的表現,就是要么卡了一會,要么跳幀。
View的繪制頻率保證60fps是最佳的,這就要求每幀繪制時間不超過16ms(16ms = 1000/60),雖然程序很難保證16ms這個時間,但是盡量降低onDraw方法中的復雜度總是切實有效的。
方案3:如果頁面就是比較復雜,層級再怎么縮減也達不到預取效果該怎么辦:
代碼動態創建View 通過addview的形式添加
方案4:主線程耗時操作排查
開啟strictmode,這樣一來,主線程的耗時操作都將以告警的形式呈現到logcat當中
StrictMode,嚴苛模式,是Android提供的一種運行時檢測機制,用于檢測代碼運行時的一些不規范的操作,最常見的場景是用于發現主線程的IO操作和網絡讀寫等耗時的操作。
方案5:viewStub&merge&ViewStub的使用
方案6:頁面刷新
item 刷新局部刷新
復雜Activity、fragment:利用LiveData等數據共享
二:穩定性
1. ANR:
以下四個條件都可以造成ANR發生:
InputDispatching Timeout:5秒內無法響應屏幕觸摸事件或鍵盤輸入事件
BroadcastQueue Timeout :在執行前臺廣播(BroadcastReceiver)的onReceive()函數時10秒沒有處理完成,后臺為60秒。
Service Timeout :前臺服務20秒內,后臺服務在200秒內沒有執行完畢。
ContentProvider Timeout :ContentProvider的publish在10s內沒進行完。
實際開發中,當一個進程發生了ANR后,系統會在 /data/anr目錄下創建一個文件 traces.txt,通過分析該文件可定位出ANR的原因
ps:總之記住這一點:盡量避免在主線程(UI線程)中作耗時操作,耗時操作都放到子線程
多注意一些常用庫出現ANR的情況:如:SharedPreferences等
1. Crash:
1:頻繁GC
內存抖動
1短時間內創建了大量對象同時又被快速釋放。比如在一個大循環里去不斷創建對象,會導致頻繁gc;
內存泄漏
2內存泄漏會導致可用內存逐漸變少,而且內存碎片加多,這也會增多gc次數,甚至可能發生OOM
3一次申請太大內存空間
由于內存碎片的存在,就算內存本身足夠,但由于碎片導致無法找到一塊大空間,這也會觸發gc;
2:在Android平臺上常見的OOM有如下幾種:
1、使用static修飾Context變量,Context被Hold住了導致Activity無法銷毀。比如 Thread
2、Bitmap沒有及時回收,調用recycle()函數并不能立即釋放Bitmap,讀取Bitmap到內存的時候沒有做采樣率的設置;
3、線程數超限,proc/pid/status中記錄的線程數超過proc/sys/kernel/threads-max中規定的最大線程數,場景如隨意創建線程,沒有使用線程池來管理;
4、文件描述符(fd)超限,proc/pid/fd下文件數目超過proc/pid/limits中的限制,場景如大量的Socket請求或者文件打開操作。
5、Cursor使用問題,使用完之后沒有及時關閉。
6、Java堆內存超限,申請的堆內存超過Runtime.getRuntime().maxMemory()。
7、Adapter沒使用緩存的convertView;
8、廣播注冊之后沒有反注冊;
9、WebView沒有銷毀,應該調用destroy()函數去銷毀;
10、數組內對象過多,沒有及時清理。
11、EventBus 等觀察者模式的框架忘記手動解除注冊
ps:分析工具:LeakCanary、Android Studio自帶工具Profilerd等
3:異常日志搜集:三方的奔潰搜集工具很多,不在贅述,android 崩潰日志收集以及上傳服務器 -自定義我自己實現的感興趣的可以看看
三:apk體積
1. 安裝包結構:
2. 優化方案:
一:最省(資源)
1. 內存:
部分上面OOM分析中已經提到了,下面做個簡單補充
1:SparseArray代替HashMap
2:使用Link優化項目
3:Bitmap壓縮:一個比較好的壓縮庫,Curzibn/Luban: Luban(魯班)—Image compression with efficiency very close to WeChat Moments/可能是最接近微信朋友圈的圖片壓縮算法 (github.com)
4:推薦使用match_parent,或者固定尺寸
5:線程優化(線程池)
2. 網絡:
Android 性能優化之網絡優化 這篇文章寫的挺詳細不在贅述
ps:網上有很多寫的比較好的網絡請求框架的封裝可以借鑒一下思路
Retrofit+OkHttp+Kotlin協程 網絡請求架構等等
1. 耗電量:
Battery Historian。
是由Google提供的Android系統電量分析工具,從手機中導出bugreport文件上傳至頁面,在網頁中生成詳細的圖表數據來展示手機上各模塊電量消耗過程,最后通過App數據的分析制定出相關的電量優化的方法。
谷歌推薦使用JobScheduler,來調整任務優先級等策略來達到降低損耗的目的。JobScheduler可以避免頻繁的喚醒硬件模塊,造成不必要的電量消耗。避免在不合適的時間(例如低電量情況下、弱網絡或者移動網絡情況下的)執行過多的任務消耗電量。
具體功能:
1、可以推遲的非面向用戶的任務(如定期數據庫數據更新);
2、當充電時才希望執行的工作(如備份數據);
3、需要訪問網絡或 Wi-Fi 連接的任務(如向服務器拉取配置數據);
4、零散任務合并到一個批次去定期運行;
5、當設備空閑時啟動某些任務;
6、只有當條件得到滿足, 系統才會啟動計劃中的任務(充電、WIFI...);
實現一個通過JobScheduler的簡單demo
public class JobSchedulerService extends JobService{
private String TAG = JobSchedulerService.class.getSimpleName();
@Override
public boolean onStartJob(JobParameters jobParameters) {
Log.d(TAG, "onStartJob:" + jobParameters.getJobId());
if(true) {
// JobService在主線程運行,如果我們這里需要處理比較耗時的業務邏輯需單獨開啟一條子線程來處理并返回true,
// 當給定的任務完成時通過調用jobFinished(JobParameters params, boolean needsRescheduled)告知系統。
//假設開啟一個線程去下載文件
new DownloadTask().execute(jobParameters);
return true;
}else {
//如果只是在本方法內執行一些簡單的邏輯話返回false就可以了
return false;
}
}
/**
* 比如我們的服務設定的約束條件為在WIFI狀態下運行,結果在任務運行的過程中WIFI斷開了系統
* 就會通過回掉onStopJob()來通知我們停止運行,正常的情況下不會回掉此方法
*
* @param jobParameters
* @return
*/
@Override
public boolean onStopJob(JobParameters jobParameters) {
Log.d(TAG, "onStopJob:" + jobParameters.getJobId());
//如果需要服務在設定的約定條件再次滿足時再次執行服務請返回true,反之false
return true;
}
class DownloadTask extends AsyncTask<JobParameters, Object, Object> {
JobParameters mJobParameters;
@Override
protected Object doInBackground(JobParameters... jobParameterses) {
mJobParameters = jobParameterses[0];
//比如說我們這里處理一個下載任務
//或是處理一些比較復雜的運算邏輯
//...
try {
Thread.sleep(30*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
//如果在onStartJob()中返回true的話,處理完成邏輯后一定要執行jobFinished()告知系統已完成,
//如果需要重新安排服務請true,反之false
jobFinished(mJobParameters, false);
}
}
}
public class MainActivity extends Activity{
private JobScheduler mJobScheduler;
private final int JOB_ID = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mai_layout);
mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE );
//通過JobInfo.Builder來設定觸發服務的約束條件,最少設定一個條件
JobInfo.Builder jobBuilder = new JobInfo.Builder(JOB_ID, new ComponentName(this, JobSchedulerService.class));
//循環觸發,設置任務每三秒定期運行一次
jobBuilder.setPeriodic(3000);
//單次定時觸發,設置為三秒以后去觸發。這是與setPeriodic(long time)不兼容的,
// 并且如果同時使用這兩個函數將會導致拋出異常。
jobBuilder.setMinimumLatency(3000);
//在約定的時間內設置的條件都沒有被觸發時三秒以后開始觸發。類似于setMinimumLatency(long time),
// 這個函數是與 setPeriodic(long time) 互相排斥的,并且如果同時使用這兩個函數,將會導致拋出異常。
jobBuilder.setOverrideDeadline(3000);
//在設備重新啟動后設置的觸發條件是否還有效
jobBuilder.setPersisted(false);
// 只有在設備處于一種特定的網絡狀態時,它才觸發。
// JobInfo.NETWORK_TYPE_NONE,無論是否有網絡均可觸發,這個是默認值;
// JobInfo.NETWORK_TYPE_ANY,有網絡連接時就觸發;
// JobInfo.NETWORK_TYPE_UNMETERED,非蜂窩網絡中觸發;
// JobInfo.NETWORK_TYPE_NOT_ROAMING,非漫游網絡時才可觸發;
jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
//設置手機充電狀態下觸發
jobBuilder.setRequiresCharging(true);
//設置手機處于空閑狀態時觸發
jobBuilder.setRequiresDeviceIdle(true);
//得到JobInfo對象
JobInfo jobInfo = jobBuilder.build();
//設置開始安排任務,它將返回一個狀態碼
//JobScheduler.RESULT_SUCCESS,成功
//JobScheduler.RESULT_FAILURE,失敗
if (mJobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) {
//安排任務失敗
}
//停止指定JobId的工作服務
mJobScheduler.cancel(JOB_ID);
//停止全部的工作服務
mJobScheduler.cancelAll();
}
最主要的一點
記得在Manifest文件內配置Service <service android:name=".JobSchedulerService" android:permission="android.permission.BIND_JOB_SERVICE"/>
總結
作為一個程序員,性能優化是常有的事情,不管是桌面應用還是web應用,不管是前端還是后端,不管是單點應用還是分布式系統,所以我們應該更加去注重性能優化的一個使用和技術上提升,綜上所述,對APP進行性能優化已成為開發者該有的一種綜合素質,也是開發者能夠完成高質量應用程序作品的保證。
以下文檔有關于單向具體優化的操作,感興趣的童鞋可以看一下
Android 性能優化 | Android 開源項目 | Android Open Source Project (google.cn)
Android深度性能優化--啟動優化(一篇就夠) - 知乎 (zhihu.com)
(28條消息) Android性能優化常見問題,與詳細解決思路方法!_chuhe1989的博客-CSDN博客