說到安卓性能優(yōu)化,這個(gè)話題實(shí)在是很廣动壤,之前在上上一家公司專門搞了一段時(shí)間的優(yōu)化锅风,發(fā)現(xiàn)APP的各個(gè)方面都得到性能上的提升酥诽,為此老板還給我獎(jiǎng)勵(lì)了1000塊錢。時(shí)間久了不去回顧皱埠,加上技術(shù)的日新月異肮帐,打算把自己積累的經(jīng)驗(yàn)重新整理回顧,不放在為知筆記了边器,太難找了训枢,暫且分幾個(gè)專題詳解:
1.啟動(dòng)的優(yōu)化
2.布局的優(yōu)化
3.內(nèi)存的優(yōu)化
4.電量的優(yōu)化
5.卡頓的優(yōu)化
6.流量的優(yōu)化
啟動(dòng)的優(yōu)化
啟動(dòng)緩慢導(dǎo)致的黑屏,白屏問題忘巧,大部分的答案都是做一個(gè)透明的主題恒界,或者是做一個(gè)Splash界面,這就是我在之前公司的臨時(shí)方案砚嘴。
APK啟動(dòng)原理
http://www.reibang.com/writer#/notebooks/11604270/notes/27710179/preview
APK的啟動(dòng)方式
1.冷啟動(dòng):后臺(tái)沒有該應(yīng)用的進(jìn)程十酣,這時(shí)系統(tǒng)會(huì)首先會(huì)創(chuàng)建一個(gè)新的進(jìn)程分配給該應(yīng)用
由于冷啟動(dòng)過程中,系統(tǒng)和APP的許多工作都要重新開始际长,所以一般而言這種啟動(dòng)方式是最慢且最具有挑戰(zhàn)性的耸采。除了創(chuàng)建和初始化Application和MainActivity之外,冷啟動(dòng)還要加載主題樣式Theme工育,inflate布局虾宇,setContentView ,測(cè)量如绸、布局嘱朽、繪制以后顯示旭贬,我們才看到了屏幕的第一幀
也就是說當(dāng)用戶點(diǎn)擊你的app那一刻到系統(tǒng)調(diào)用Activity.onCreate()之間的這個(gè)時(shí)間段內(nèi),WindowManager會(huì)先加載app主題樣式中的windowBackground做為app的預(yù)覽元素搪泳,然后再真正去加載activity的layout布局稀轨。
為什么會(huì)出現(xiàn)黑屏白屏呢?
系統(tǒng)進(jìn)程在創(chuàng)建Application的過程中會(huì)產(chǎn)生一個(gè)BackgroudWindow森书,等到App完成了第一次繪制靶端,系統(tǒng)進(jìn)程才會(huì)用MainActivity的界面替換掉原來的BackgroudWindow
很顯然,如果你的application或activity啟動(dòng)的過程太慢凛膏,導(dǎo)致系統(tǒng)的BackgroundWindow沒有及時(shí)被替換杨名,就會(huì)出現(xiàn)啟動(dòng)時(shí)白屏或黑屏的情況(取決于你的主題是Dark還是Light)
冷啟動(dòng)優(yōu)化
1.主題替換
我們?cè)趕tyle中自定義一個(gè)樣式Lancher,在其中放一張背景圖片猖毫,或是廣告圖片之類的
1. <style name="AppTheme.Launcher">
2. <item name="android:windowBackground">@drawable/bg</item>
3. </style>
把這個(gè)樣式設(shè)置給啟動(dòng)的Activity
1. <activity
2. android:name=".activity.SplashActivity"
3. android:screenOrientation="portrait"
4. android:theme="@style/AppTheme.Launcher"
5. >
然后在Activity的onCreate方法台谍,把Activity設(shè)置回原來的主題
1. @Override
2. protected void onCreate(Bundle savedInstanceState) {
3. //替換為原來的主題,在onCreate之前調(diào)用
4. setTheme(R.style.AppTheme);
5. super.onCreate(savedInstanceState);
6. }
這樣在啟動(dòng)時(shí)就通過給用戶看一張圖片或是廣告來防止黑白屏的尷尬吁断。
還一種方式趁蕊,就是把windowBackground屬性設(shè)為null,這樣在啟動(dòng)時(shí)仔役,backgroundWindow的背景就會(huì)變成透明的掷伙,給人的感覺就是點(diǎn)了應(yīng)用圖標(biāo)以后,延遲了一會(huì)兒然后加載第一個(gè)activity的界面又兵。
1. <style name="AppTheme.Launcher">
2. <item name="android:windowBackground">@null</item>
3. </style>
第二種方式可能會(huì)出現(xiàn)黑屏任柜,第一種的圖要選取好,不然也會(huì)有突兀感沛厨,所以很多公司都選擇就讓他白屏宙地,改變主題實(shí)際上是一種偽優(yōu)化,因?yàn)樗鼘?shí)質(zhì)上并沒有真正減少App啟動(dòng)的時(shí)間
另外還可以設(shè)置style
<style name="LaunchStyle" parent="AppTheme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
</style>
或者
<item name="android:windowDisablePreview">true</item>
這個(gè)就是直接甩鍋個(gè)系統(tǒng)
其他方法 就是進(jìn)入后直接動(dòng)畫逆皮,或者是MD模式的效果宅粥, GitHub 上的開源項(xiàng)目 saulmm/onboarding-examples-android
Application是程序的主入口
延遲初始化,后臺(tái)任務(wù)电谣,界面預(yù)加載
在Application的構(gòu)造器方法秽梅、attachBaseContext()、onCreate()方法中不要進(jìn)行耗時(shí)操作的初始化辰企,一些數(shù)據(jù)預(yù)取放在異步線程中风纠。
數(shù)據(jù)庫,IO操作牢贸,密集網(wǎng)絡(luò)請(qǐng)求不要放在Application的構(gòu)造方法中,能使用工作線程的盡量使用工作線程镐捧,不要在Application的onCreate中創(chuàng)建線程池潜索,因?yàn)槟菢訒?huì)有比較大的開銷臭增,可以考慮延后再創(chuàng)建。
第三方SDK如果主線程中沒有立即使用竹习,可以考慮延遲幾秒再初始化誊抛,總之一句話,盡早讓用戶看到應(yīng)用的界面整陌,其他操作都可以先讓路拗窃。
對(duì)于MainActivity,由于在獲取到第一幀前泌辫,需要對(duì)contentView進(jìn)行測(cè)量布局繪制操作随夸,盡量減少布局的層次,考慮StubView的延遲加載策略震放,當(dāng)然在onCreate宾毒、onStart、onResume方法中避免做耗時(shí)操作
- MultiDex以及Tinker的初始化殿遂,最先執(zhí)行诈铛;關(guān)于MultiDex的優(yōu)化本文不再贅述,參考我之前Multidex的系列文章墨礁。
- Application中主要做了各種三方組件的初始化幢竹;
項(xiàng)目中除聽云之外其余所有三方組件都搶占先機(jī),在Application主線程初始化恩静。這樣的初始化方式肯定是過重的:
- 考慮異步初始化三方組件焕毫,不阻塞主線程;
- 延遲部分三方組件的初始化蜕企;實(shí)際上我們粗粒度的把所有三方組件都放到異步任務(wù)里咬荷,可能會(huì)出現(xiàn)WorkThread中尚未初始化完畢但MainThread中已經(jīng)使用的錯(cuò)誤,因此這種情況建議延遲到使用前再去初始化轻掩;
- 而如何開啟WorkThread同樣也有講究幸乒,這個(gè)話題在下文詳談。
項(xiàng)目修改:
- 將友盟唇牧、Bugly罕扎、聽云、GrowingIO丐重、BlockCanary等組件放在WorkThread中初始化腔召;
- 延遲地圖定位、ImageLoader扮惦、自有統(tǒng)計(jì)等組件的初始化:地圖及自有統(tǒng)計(jì)延遲4秒臀蛛,此時(shí)應(yīng)用已經(jīng)打開;而ImageLoader
因?yàn)檎{(diào)用關(guān)系不能異步以及過久延遲,初始化從Application延遲到SplashActivity浊仆;而EventBus因?yàn)樵貯ctivity中使用所以必須在Application中初始化
2.溫啟動(dòng):臺(tái)已有該應(yīng)用的進(jìn)程客峭,但是啟動(dòng)的入口Activity被干掉了,比如按了back鍵抡柿,應(yīng)用雖然退出了舔琅,但是該應(yīng)用的進(jìn)程是依然會(huì)保留在后臺(tái)
3.熱啟動(dòng):后臺(tái)已有該應(yīng)用的進(jìn)程,比如按下home鍵洲劣,這種在已有進(jìn)程的情況下备蚓,這種啟動(dòng)會(huì)從已有的進(jìn)程中來啟動(dòng)應(yīng)用
應(yīng)用的啟動(dòng)時(shí)間統(tǒng)計(jì)
在現(xiàn)在的公司最近還在搞啟動(dòng)優(yōu)化,提供2種方式
1.查看啟動(dòng)時(shí)間adb 命令
adb shell am start -W [PackageName]/[PackageName.XX.XXXActivity]
這個(gè)PackageName 需要你到你自己項(xiàng)目的清單文件去自己找囱稽,輸入adb命令后郊尝,程序會(huì)等待你啟動(dòng)然后在統(tǒng)計(jì)這個(gè)時(shí)間,
ThisTime:最后一個(gè)啟動(dòng)的Activity的啟動(dòng)耗時(shí)粗悯; ThisTime:一般和TotalTime時(shí)間一樣虚循,我們的是中間開了一個(gè)
TotalTime:自己的所有Activity的啟動(dòng)耗時(shí);包括創(chuàng)建進(jìn)程+Application初始化+Activity初始化到界面顯示
WaitTime: ActivityManagerService啟動(dòng)App的Activity時(shí)的總時(shí)間(包括當(dāng)前Activity的onPause()和自己Activity的啟動(dòng))一般比TotalTime大點(diǎn)样傍,包括系統(tǒng)影響的耗時(shí)
2.使用Android Studio 的日志横缔,在過濾框輸入display
然后清空日志,然后啟動(dòng)如下:
Application開始到首頁顯示出來
Application的構(gòu)造器方法——>attachBaseContext()——>onCreate()——>Activity的構(gòu)造方法——>onCreate()——>配置主題中背景等屬性——>onStart()——>onResume()——>測(cè)量衫哥、布局茎刚、繪制顯示在界面上
上面這些階段全部都是在主線程中執(zhí)行的,任何不經(jīng)意的操作都可能拖慢應(yīng)用的啟動(dòng)速度撤逢。所以我們不應(yīng)在Application以及Activity的生命周期回調(diào)中做任何費(fèi)時(shí)操作膛锭,具體指標(biāo)大概是你在onCreate,onResume蚊荣,onStart等回調(diào)中所花費(fèi)的總時(shí)間最好不要超過400ms初狰,否則用戶在桌面點(diǎn)擊你的應(yīng)用圖標(biāo)后,將感覺到明顯的卡頓
針對(duì)上面的需求互例,一般處理是將任務(wù)分優(yōu)先級(jí)啟動(dòng)奢入,應(yīng)用啟動(dòng)開始加載 ;首頁渲染后加載媳叨;渲染后延遲加載
具體延遲多久 在性能好的手機(jī)上面啟動(dòng)非承裙猓快,很短的延遲就行了,但是在低端手機(jī)上缺很慢,還要為了兼容久手機(jī)覆旱,一般延長(zhǎng)很長(zhǎng)的時(shí)間,
第一種寫法:直接PostDelay 300ms.
myHandler.postDelayed(mLoadingRunnable, DEALY_TIME);
第二種寫法:優(yōu)化的DelayLoad
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});
第二種捉片,在窗口完成以后進(jìn)行加載,這里面的run方法是在onResume之后運(yùn)行的具體參考這個(gè):http://www.androidperformance.com/2015/11/18/Android-app-lunch-optimize-delay-load.html
啟動(dòng)優(yōu)化思路
1、UI渲染優(yōu)化界睁,去除重復(fù)繪制觉增,減少UI重復(fù)繪制時(shí)間兵拢,打開設(shè)置中的GPU過度繪制開關(guān)翻斟,各界面過度繪制不應(yīng)超過2.5x;也就是打開此調(diào)試開關(guān)后说铃,界面整體呈現(xiàn)淺色访惜,特別復(fù)雜的界面,紅色區(qū)域也不應(yīng)該超過全屏幕的四分之一腻扇;
2债热、根據(jù)優(yōu)先級(jí)的劃分,KoMobileApplication的一些初始化工作能否將任務(wù)優(yōu)先級(jí)劃分成3,在首頁渲染完成后進(jìn)行加載幼苛,比如:PaySDKManager窒篱。
3、主線程中的所有SharedPreference能否在非UI線程中進(jìn)行舶沿,SharedPreferences的apply函數(shù)需要注意墙杯,因?yàn)镃ommit函數(shù)會(huì)阻塞IO,這個(gè)函數(shù)雖然執(zhí)行很快括荡,但是系統(tǒng)會(huì)有另外一個(gè)線程來負(fù)責(zé)寫操作高镐,當(dāng)apply頻率高的時(shí)候,該線程就會(huì)比較占用CPU資源畸冲。類似的還有統(tǒng)計(jì)埋點(diǎn)等嫉髓,在主線程埋點(diǎn)但異步線程提交,頻率高的情況也會(huì)出現(xiàn)這樣的問題邑闲。
4算行、檢查BaseActivity,不恰當(dāng)?shù)牟僮鲿?huì)影響所有子Activity的啟動(dòng)。
5苫耸、對(duì)于首次啟動(dòng)的黑屏問題州邢,對(duì)于“黑屏”是否可以設(shè)計(jì)一個(gè).9圖片替換掉,間接減少用戶等待時(shí)間鲸阔。
6偷霉、對(duì)于網(wǎng)絡(luò)錯(cuò)誤界面,友好提示界面褐筛,使用ViewStub的方式类少,減少UI一次性繪制的壓力。
7渔扎、任務(wù)優(yōu)先級(jí)為2硫狞,3的,通過下面這種方式進(jìn)行懶加載的方式
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});
8、Multidex的使用残吩,也是拖慢啟動(dòng)速度的元兇财忽,必須要做優(yōu)化。后面有空專門寫一篇Multidex