隨著項(xiàng)目版本的迭代占婉,App的性能問題會逐漸暴露出來泡嘴,啟動慢,啟動白屏黑屏逆济,包越來越大酌予,App耗電量高,而好的用戶體驗(yàn)與性能表現(xiàn)緊密相關(guān)奖慌。今天就來分析關(guān)于Android啟動優(yōu)化要怎么做抛虫?
為什么出現(xiàn)白屏或者黑屏
冷啟動白屏持續(xù)時間可能會很長瘟斜,這是個很不好的體驗(yàn)谣殊,一般由以下原因造成:
1、Application的onCreate流程荒叼,對于大型的APP來說涎劈,通常會在這里做大量的通用組件的初始化操作;
建議:很多第三方SDK都放在Application初始化阅茶,我們可以放到用到的地方才進(jìn)行初始化操作蛛枚。
2、Activity的onCreate流程脸哀,特別是UI的布局與渲染操作蹦浦,如果布局過于復(fù)雜很可能導(dǎo)致嚴(yán)重的啟動性能問題;
優(yōu)化APP啟動速度意義重大撞蜂,啟動時間過長盲镶,可能會使用戶直接卸載APP侥袜。
白屏或黑屏,具體是哪一個溉贿,取決于app的Theme使用的是dark還是light主題
一枫吧、啟動的三種方式
App啟動方式 冷啟動 熱啟動 溫啟動
冷啟動(Cold start)
冷啟動是指APP在手機(jī)啟動后第一次運(yùn)行,或者APP進(jìn)程被kill掉后在再次啟動宇色。
可見冷啟動的必要條件是該APP進(jìn)程不存在九杂,這就意味著系統(tǒng)需要創(chuàng)建進(jìn)程,APP需要初始化宣蠕。在這三種啟動方式中例隆,冷啟動耗時最長,對于冷啟動的優(yōu)化也是最具挑戰(zhàn)的抢蚀。因此本文重點(diǎn)談?wù)摰氖菍鋯酉嚓P(guān)的優(yōu)化镀层。
溫啟動(Warm start)
App進(jìn)程存在,當(dāng)時Activity可能因?yàn)閮?nèi)存不足被回收皿曲。這時候啟動App不需要重新創(chuàng)建進(jìn)程唱逢,但是Activity的onCrate還是需要重新執(zhí)行的。場景類似打開淘寶逛了一圈然后切到微信去聊天去了谷饿,過了半小時再次回到淘寶惶我。這時候淘寶的進(jìn)程存在,但是Activity可能被回收博投,這時候只需要重新加載Activity即可绸贡。
熱啟動(Hot start)
App進(jìn)程存在,并且Activity對象仍然存在內(nèi)存中沒有被回收毅哗√拢可以重復(fù)避免對象初始化,布局解析繪制虑绵。
場景就類似你打開微信聊了一會天這時候出去看了下日歷 在打開微信 微信這時候啟動就屬于熱啟動尿瞭。
二、啟動優(yōu)化時間統(tǒng)計
3.1adb命令 : adb shell am start -S -W 包名/啟動類的全限定名 翅睛, -S 表示重啟當(dāng)前應(yīng)用
E:\code\kfz-android>adb shell am start -S -W com.xxx.app/com.xxx.app.xxx.SplashActivity
Stopping: com.xxx.app
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.xxx.app/.business.SplashActivity }
Status: ok
Activity: com.xxx.app/.xxx.SplashActivity
ThisTime: 819
TotalTime: 819
WaitTime: 834
Complete
ThisTime : 最后一個 Activity 的啟動耗時(例如從 LaunchActivity - >MainActivity「adb命令輸入的Activity」 , 只統(tǒng)計 MainActivity 的啟動耗時)
TotalTime : 啟動一連串的 Activity 總耗時.(有幾個Activity 就統(tǒng)計幾個)
WaitTime : 應(yīng)用進(jìn)程的創(chuàng)建過程 + TotalTime .
3.2可以通過在代碼中增加log來計算啟動時間
long startTime = System.currentTimeMillis(); //獲取開始時間
doSomething(); //測試的代碼段
long endTime = System.currentTimeMillis(); //獲取結(jié)束時間
System.out.println("程序運(yùn)行時間:" + (endTime - startTime) + "ms"); //輸出程序運(yùn)行時
3.3 使用systrace
可能在源代碼經(jīng)成椋看到這種Debug.startMethodTracing(file.getAbsolutePath());統(tǒng)計的代碼,是官方提供捕发,分析trace文件
//把分析結(jié)果存在一個文件
File file = new File(Environment.getExternalStorageDirectory(), "kongfz.trace");
Log.e(TAG, "onCreate: " + file.getAbsolutePath());
Debug.startMethodTracing(file.getAbsolutePath());
需要統(tǒng)計的方法
Debug.stopMethodTracing();
可以看到具體哪個方法特別耗時疏旨。
3.4 使用profiler來查看方法耗時
關(guān)于3、4方案查看trace文件可以通過Hdmss或者直接通過AndroidStudio分析扎酷。
3.5 使用依賴庫 例如AOP 無侵入埋點(diǎn)插樁方式進(jìn)行時間統(tǒng)計檐涝,或者APT注解編譯統(tǒng)計,這個暫不舉例,可以自行百度使用方法谁榜。
3.6根據(jù)系統(tǒng)日志來統(tǒng)計啟動耗時幅聘,在Android Studio中查找已用時間,必須在logcat視圖中禁用過濾器(No Filters)窃植。因?yàn)檫@個是系統(tǒng)的日志輸出帝蒿,而不是應(yīng)用程序的。
比如我們可以通過過濾displayed輸出的啟動日志. 示例如下:
三撕瞧、原因分析
1.高耗時任務(wù)
數(shù)據(jù)庫初始化陵叽、某些第三方框架初始化、大文件讀取丛版、MultiDex加載等巩掺,導(dǎo)致CPU阻塞
2.復(fù)雜的View層級
使用的嵌套Layout過多,層級加深页畦,導(dǎo)致View在渲染過程中胖替,遞歸加深,占用CPU資源豫缨,影響Measure独令、Layout等方法的速度
3.類過于復(fù)雜
Java對象的創(chuàng)建也是需要一定時間的,如果一個類中結(jié)構(gòu)特別復(fù)雜好芭,new一個對象將消耗較高的資源燃箭,特別是一些單例的初始化,需要特別注意其中的結(jié)構(gòu)
4.主題及Activity配置
有一些App是帶有Splash頁的舍败,有的則直接進(jìn)入主界面招狸,由于主題切換,可能會導(dǎo)致白屏邻薯,或者點(diǎn)了Icon裙戏,過一會兒才出現(xiàn)主界面
四、優(yōu)化方案
冷啟動優(yōu)化分為可控階段和不可控階段
不可控階段
點(diǎn)擊app以后到初始化Application之間這段時間厕诡,系統(tǒng)接管累榜,從Zygote進(jìn)程中fork創(chuàng)建新進(jìn)程,GC回收等等一系列操作灵嫌,和我們app無關(guān)
可控階段
作為普通應(yīng)用壹罚,App進(jìn)程的創(chuàng)建等環(huán)節(jié)我們是無法主動控制的,可以優(yōu)化的也就是Application寿羞、Activity創(chuàng)建以及回調(diào)等過程猖凛。
初始化Application開始,如下圖:
從上圖可以看到稠曼,整個冷啟動流程中至少有兩處onCreate形病,分別是Application和Activity,整個流程都是可控的霞幅。所以漠吻,onCreate方法做的事情越多,冷啟動消耗的時間越長司恳。
關(guān)于啟動加速方案,Google給出的建議是:
1.利用提前展示出來的Window途乃,快速展示出來一個界面,給用戶快速反饋的體驗(yàn)扔傅;
2.避免在啟動時做密集沉重的初始化(Heavy app initialization)耍共;
3.定位問題:避免I/O操作、反序列化猎塞、網(wǎng)絡(luò)操作试读、布局嵌套等。
官方文檔給出的建議中,方向1屬于治標(biāo)不治本荠耽,只是表面上快钩骇;方向2、3可以真實(shí)的加快啟動速度铝量。接下來我們就在項(xiàng)目中實(shí)際應(yīng)用一下倘屹。
4.1透明主題優(yōu)化
為了解決啟動窗口白屏問題,許多開發(fā)者使用透明主題來解決這個問題慢叨,但是治標(biāo)不治本纽匙。
會出現(xiàn)點(diǎn)擊圖標(biāo)幾秒鐘沒有反應(yīng)情況,雖然解決了上面這個問題拍谐,但是仍然有些不足烛缔。
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowFullscreen">true</item>
<item name="android:windowIsTranslucent">true</item>
</style>
4.2設(shè)置閃屏圖片主題
為了更順滑無縫銜接我們的閃屏頁,可以在啟動 Activity 的 Theme中設(shè)置閃屏頁圖片赠尾,這樣啟動窗口的圖片就會是閃屏頁圖片力穗,而不是白屏。
其實(shí)只是良好體驗(yàn)气嫁,沒有做到真正的優(yōu)化当窗。大部分App都采用這種方式。
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@drawable/lunch</item> //閃屏頁圖片
<item name="android:windowFullscreen">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item><!--顯示虛擬按鍵寸宵,并騰出空間-->
</style>
4.3 優(yōu)化Application
從用戶點(diǎn)擊launcher圖標(biāo)到看到界面第一幀為應(yīng)用啟動過程崖面,主要會經(jīng)過以下這些方法:
main()->Application:attachBaseContext()->onCreate()->Activity:onCreate()->onStart()->onResume()
main->Activity創(chuàng)建的這個過程會經(jīng)過一系列framework層的操作,對于系統(tǒng)自動執(zhí)行的操作我們不易進(jìn)行優(yōu)化梯影,但是巫员,如果我們繼承Application自定義了自己的Application,可以做如下優(yōu)化:
1.盡量不將一些業(yè)務(wù)邏輯放于Application中甲棍;
2.不以靜態(tài)變量的方式在Application中保存應(yīng)用數(shù)據(jù)简识;
3.不要把文件、數(shù)據(jù)庫的操作放在Application
我們知道有很多第三方組件(包括App應(yīng)用本身)都在 Application 中完成初始化操作。但是在 Application 中完成繁重的初始化操作和復(fù)雜的邏輯就會影響到應(yīng)用的啟動性能七扰。
分析后發(fā)現(xiàn)影響冷啟動時間的常見問題如下:
復(fù)雜繁瑣的布局初始化
阻塞主線程 UI 繪制的操作奢赂,如 I/O 讀寫或者是網(wǎng)絡(luò)訪問.
其它占用主線程的操作
我們可以根據(jù)這些組件的輕重緩急之分,對初始化做一下分類 :
必要的組件一定要在主線程中立即初始化(入口 Activity 可能立即會用到)
組件一定要在主線程中初始化颈走,但是可以延遲初始化膳灶。
組件可以在子線程中初始化。
在進(jìn)行優(yōu)化的時候立由,需要注意以下幾種情況:
放在子線程的組件初始化建議延遲初始化轧钓,這樣就可以了解是否會對項(xiàng)目造成影響!
將需要在主線程中初始化但是可以不用立即完成的動作延遲加載(初始化放在 Application 中統(tǒng)一管理為妙锐膜,不建議放在Activity里面)
handler.postDelayed(new Runnable() {
@Override
public void run() {
// 設(shè)置數(shù)據(jù)庫的上下文
DBOpenHelper.setContext(context);
// 初始化GreenDao
initGreenDao();
// 加載分類數(shù)據(jù)
loadClassifyData();
//百度地圖初始化毕箍,百度地圖需要在Application初始化
GamActivityMap.init(Kongfz.this);
}
}, 3000);
可以嘗試將常見的組件庫,例如 Bugly道盏,x5內(nèi)核初始化霉晕,SP的讀寫,友盟等組件放到子線程中初始化捞奕。(子線程初始化不能影響到組件的使用)
new Thread() {
@Override
public void run() {
super.run();
//設(shè)置線程的優(yōu)先級牺堰,不與主線程搶資源
setPriority(1);
//子線程初始化第三方組件
try {
Thread.sleep(5000);//建議延遲初始化,可以發(fā)現(xiàn)是否影響其它功能颅围,或者是崩潰伟葫!
} catch (InterruptedException e) {
e.printStackTrace();
}
//配置umeng、百度統(tǒng)計信息
configAnalytic();
//ali 實(shí)人認(rèn)證sdk初始化
RPSDK.initialize(getContext());
//手機(jī)內(nèi)存初始化
Util.getMemoryLevel(getContext());
//第三方分享
ShareUtil.shareConfig(getContext());
//聽云
TingYuntils.init(Kongfz.this);
LogUtil.e("app", "onCreate Application");
LogUtil.e("app", "width=" + Util.getScreenWidth(getContext()) + ";height=" + Util.getScreenHeight(getContext()) + ";statusBarHeight=" + Util.getStatusBarHeight(getContext()) + ";dpi=" + Util.getDensityDpi(getContext()));
//推送初始化
Config.setMiniTest();
//推動初始化
PushMainUtils.initMergePushSDk(true, Kongfz.this);
}
}.start();
在優(yōu)化好啟動時間后院促,我們就可以在針對閃屏頁的時間筏养,進(jìn)行調(diào)整優(yōu)化,具體公式為:
閃屏頁展示總時間 = 組件初始化時間 + 剩余展示時間
4.4 優(yōu)化啟動頁Activity
啟動頁盡量不要網(wǎng)絡(luò)請求等耗時操作常拓。如果使用了請求網(wǎng)絡(luò)等操作在適當(dāng)?shù)臅r候應(yīng)該及時取消的耗時操作渐溶。例如,某些時候弄抬,當(dāng)用戶點(diǎn)擊了launcher圖標(biāo)茎辐,但馬上又想退出點(diǎn)擊了返回鍵,過了幾秒鐘用戶在使用其他APP掂恕,突然跳轉(zhuǎn)到我們的APP那就用戶體驗(yàn)感很不好了拖陆。所以可以在返回事件中取消掉耗時操作。
減少歡迎頁復(fù)雜邏輯懊亡,復(fù)雜布局依啰,onCreate()中過多邏輯。
4.5 開啟服務(wù)做處理
可以在后臺處理的店枣,放在后臺處理速警,而不用立馬處理叹誉。
4.6 MultiDex.install(this);優(yōu)化 當(dāng)項(xiàng)目方法超過65535,需要分包加載闷旧,Android5.0默認(rèn)實(shí)現(xiàn)桂对,Android5.0沒有實(shí)現(xiàn),可能出現(xiàn)崩潰鸠匀,或者第一次加載緩慢的情況。具體可以參考下面文章逾柿, 有詳細(xì)原理分析缀棍,以及解決
https://juejin.im/post/5d95f4a4f265da5b8f10714b
五、啟動頁適配
啟動頁面適配對于Android手機(jī)來說是一個很麻煩的問題机错,劉海屏爬范、底部虛擬導(dǎo)航欄、各種尺寸的手機(jī)屏幕弱匪,導(dǎo)致啟動頁被拉伸變形等出現(xiàn)青瀑,那么怎么進(jìn)行啟動頁適配呢?下面說兩種比較通用的適配方案:
5.1 切目前主流手機(jī)尺寸放入不同資源文件夾中進(jìn)行適配:
此種方式問題:
1.適配不能完全適配只能適配主流的機(jī)型萧诫。
2.文件資源過多斥难,導(dǎo)致包變大。
5.2 切部分圖進(jìn)行適配帘饶。
六哑诊、優(yōu)化效果
OPPO優(yōu)化前:
E:\code\kfz-android>adb shell am start -S -W com.xxx.app/com.xxx.app.xxx.SplashActivity
Stopping: com.xxx.app
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.xxx.app/.xxx.SplashActivity }
Status: ok
Activity: com.xxx.app/.xxx.SplashActivity
ThisTime: 1723
TotalTime: 1723
WaitTime: 1761
Complete
OPPO優(yōu)化后:
E:\code\kfz-android>adb shell am start -S -W com.xxx.app/com.xxx.app.xxx.SplashActivity
Stopping: com.xxx.app
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.xxx.app/.xxx.SplashActivity }
Status: ok
Activity: com.xxx.app/.xxx.SplashActivity
ThisTime: 717
TotalTime: 717
WaitTime: 733
Complete
華為優(yōu)化前:
E:\code\KongfzCode2\kfz-android>adb shell am start -S -W com.xxx.app/com.xxx.app.xxx.SplashActivity
Stopping: com.xxx.app
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.xxx.app/.xxx.SplashActivity }
Status: ok
Activity: com.xxx.app/.business.SplashActivity
ThisTime: 2613
TotalTime: 2613
WaitTime: 2644
Complete
華為優(yōu)化后:
E:\code\kfz-android>adb shell am start -S -W com.xxx.app/com.xxx.app.xxx.SplashActivity
Stopping: com.kongfz.app
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.xxx.app/.xxx.SplashActivity }
Status: ok
Activity: com.xxx.app/.xxx.SplashActivity
ThisTime: 1632
TotalTime: 1632
WaitTime: 1660
Complete