前言
手把手講解系列文章,是我寫給各位看官,也是寫給我自己的。
文章可能過分詳細(xì),但是這是為了幫助到盡量多的人,畢竟工作5,6年,不能老吸血,也到了回饋開源的時(shí)候.
這個(gè)系列的文章:
1、用通俗易懂的講解方式,講解一門技術(shù)的實(shí)用價(jià)值
2、詳細(xì)書寫源碼的追蹤,源碼截圖,繪制類的結(jié)構(gòu)圖,盡量詳細(xì)地解釋原理的探索過程
3、提供Github 的 可運(yùn)行的Demo工程,但是我所提供代碼,更多是提供思路,拋磚引玉,請(qǐng)酌情cv
4、集合整理原理探索過程中的一些坑,或者demo的運(yùn)行過程中的注意事項(xiàng)
5、用gif圖,最直觀地展示demo運(yùn)行效果如果覺得細(xì)節(jié)太細(xì),直接跳過看結(jié)論即可。
本人能力有限,如若發(fā)現(xiàn)描述不當(dāng)之處,歡迎留言批評(píng)指正。
學(xué)到老活到老,路漫漫其修遠(yuǎn)兮。與眾君共勉 !
引子
app性能優(yōu)化,是每一個(gè)高階開發(fā)者必備技能,但是現(xiàn)在網(wǎng)絡(luò)上關(guān)于性能優(yōu)化的文章大多即沒有成文案例,又沒有知識(shí)體系介紹,讓需要了解此項(xiàng)技術(shù)的人無從下手學(xué)習(xí)。本系列文章,將先講解案例 ,讓你看到效果,再詳解細(xì)節(jié),讓你知曉原理。希望對(duì)大家有幫助。
案例
app性能優(yōu)化,自然是存在問題,然后才優(yōu)化,那么如何去診斷出這些問題呢?自然是有手段。
Q:我們?nèi)绾蔚弥覀冏约旱腶pp啟動(dòng)花費(fèi)了多少時(shí)間?
A:adb 命令。步驟如下:
- 確保設(shè)備連接到電腦;
- 啟動(dòng)cmd窗口
- 輸入如下命令:
adb shell am start -W [app包名]/[launcherActivity的全類名]
在android29模擬器上的結(jié)果為:image.png
vivo真機(jī),那么結(jié)果是:image.png
這里會(huì)出現(xiàn)3個(gè)time:
thisTime:am start 命令可能會(huì)啟動(dòng)多個(gè)Activity,如果啟動(dòng)多個(gè),thisTime則是指 最后一個(gè)Activity的啟動(dòng)時(shí)間,如果啟動(dòng)的是1個(gè),那么thisTime等于TotalTime.
TotalTime:新的應(yīng)用的Activity啟動(dòng)的耗時(shí)。
WaitTime: AMS將當(dāng)前Activity從onResume轉(zhuǎn)向onPause,再啟動(dòng)新應(yīng)用Activity的總時(shí)長,包含了TotalTime在內(nèi),所以WaitTime比TotalTime要長。
當(dāng)然你也可以加上-S -R 10 ,連續(xù)啟動(dòng)10次,然后自己計(jì)算平均啟動(dòng)時(shí)長。
adb shell am start -S -R 10 -W packagename/.MainActivity
這里的3個(gè)時(shí)間,我們大概可以看出自己的app啟動(dòng)具體花費(fèi)了多少時(shí)間。通常啟動(dòng)時(shí)間可以通過肉眼觀察得到,但是具體到確切數(shù)值,還是需要借助命令行的。那么接下來的問題,如果發(fā)現(xiàn)app啟動(dòng)耗時(shí)不理想,比如非常極端的情況,我們?cè)贏ctivity的onCreate中加入了一些耗時(shí)操作,
或者你把耗時(shí)操作放到onResume中:
那么:這里就是啟動(dòng)時(shí)間就會(huì)整個(gè)拖慢4000MS.
上面的這是我在模擬UI線程中執(zhí)行耗時(shí)操作的極端情況。在實(shí)際的項(xiàng)目中,可能這些拖慢Activity啟動(dòng)時(shí)間的一些耗時(shí)任務(wù)都是錯(cuò)綜復(fù)雜的,這時(shí)候應(yīng)該如何處理?
所有的啟動(dòng)任務(wù)按照兩個(gè)維度分為四類:
必要: 比如我們的launcher是WelcomeActivity,它會(huì)自動(dòng)結(jié)束,然后下一個(gè)是MainActivity,如果是MainActivity所必須的一些參數(shù)需要提前獲取,那么這個(gè)獲取這些參數(shù)的任務(wù)就是必要任務(wù)。
耗時(shí):按照消耗時(shí)間的長短做大致區(qū)分,不耗時(shí),一般是微量計(jì)算,很快能執(zhí)行完,不會(huì)耽誤主線程太多時(shí)間。 耗時(shí)長的,一般是第三方SDK的初始化,或者執(zhí)行網(wǎng)絡(luò)請(qǐng)求。
這4類分別有不同的處理方式:
- 必要且耗時(shí)
這種,第三方SDK初始化,比如Tinker,需要在歡迎頁面就知道要不要合并補(bǔ)丁包,這個(gè)是必須的,也是耗時(shí)的,或者 極光推送SDK初始化。類似這種,采用異步線程去處理,此處建議直接new Thread去執(zhí)行,而不是在啟動(dòng)Activity里面就用線程池,因?yàn)榫€程池的初始化也是要耗費(fèi)時(shí)間的,還不如 new Thread去執(zhí)行Runnable.
異步執(zhí)行的好處是,不會(huì)給UI線程帶來時(shí)間上的延遲,給用戶比較好的歡迎頁面的體驗(yàn)。- 必要不耗時(shí)
這種,放在onCreate或者onResume里面無妨。- 不必要不耗時(shí)
針對(duì)這種不必要的,但是不耗時(shí)的,我們可以容忍它在啟動(dòng)Activity,但是也還是放到 new Thread中去執(zhí)行比較好,啟動(dòng)時(shí)間,能節(jié)省就節(jié)省。- 不必要且耗時(shí)
比如,數(shù)據(jù)上報(bào)的SDK初始化,下一個(gè)Activity并不要求你一定要初始化完成。這個(gè)就不要放在啟動(dòng)Activity了。直接放在第一次數(shù)據(jù)上報(bào)的代碼里去初始化即可。
另外,經(jīng)驗(yàn)之談:針對(duì)必要且耗時(shí)的任務(wù),如果數(shù)量非常多,直接用一個(gè)線程去執(zhí)行,但是啟動(dòng)頁Activity同樣會(huì)停留很久,這個(gè)時(shí)候,可以給所有任務(wù)區(qū)分一個(gè)輕重緩急,在重要任務(wù)執(zhí)行完畢之后,就進(jìn)入下一個(gè)Activity,不必等待非重要任務(wù)的結(jié)果。(根據(jù)這個(gè)思路,相信各位大佬都能寫出自己的實(shí)現(xiàn)代碼,我就不多嘴了).
最后提一下, 有些情況下啟動(dòng)app,會(huì)出現(xiàn)明顯白屏,然后再出現(xiàn)我們寫的UI布局。 這是因?yàn)?AMS在啟動(dòng)我們的Activity的時(shí)候,會(huì)先顯示一個(gè)默認(rèn)白色的window,然后再出現(xiàn)我們寫的UI布局,如果因?yàn)槲覀冏陨韆pp的啟動(dòng)耗時(shí)過長,導(dǎo)致白屏?xí)r間太長,體驗(yàn)就非常不好。這種有兩種解決方案:
1、設(shè)置Activity的theme,將背景色設(shè)置為透明。
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
</style>
2、將Activity的theme設(shè)置為一張圖片,但是最好跟啟動(dòng)頁的背景圖一致,否則在出現(xiàn)啟動(dòng)頁真正Ui的時(shí)候會(huì)比較突兀。
<style name="splash" parent="AppTheme.NoActionBar">
<item name="android:windowBackground">@drawable/bg_splash</item>
<item name="android:windowFullscreen">true</item>
</style>
結(jié)語
啟動(dòng)速度優(yōu)化,是app體驗(yàn)優(yōu)化的第一道門,將它做好了將會(huì)直接提升到我們app的用戶體驗(yàn)。但是,真正能做的,遠(yuǎn)不止這么簡(jiǎn)單。比如: 如果啟動(dòng)頁的UI比較復(fù)雜,在UI繪制上也是耗費(fèi)一些時(shí)間導(dǎo)致體驗(yàn)不好,甚至?xí)D,但是這又是UI 美眉要求的,還不能改,咋辦? 只能自己在繪制上做一些優(yōu)化了。下一盤文章詳解。




