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