App啟動優(yōu)化

1.操作系統(tǒng)啟動流程和Launcher點(diǎn)擊事件流程
2.啟動方式以及首次啟動
3.黑白屏優(yōu)化
4.啟動時間內(nèi)代碼優(yōu)化

操作系統(tǒng)啟動
1.打開電源 引導(dǎo)芯片代碼加載引導(dǎo)程序Boot Loader到RAM中去執(zhí)行。
2.BootLoader把操作系統(tǒng)拉起來。
3.Linux 內(nèi)核啟動開始系統(tǒng)設(shè)置,找到一個init.rc文件啟動初始化進(jìn)程。
4.init進(jìn)程初始化和啟動屬性服務(wù),之后開啟Zygote進(jìn)程。
5.Zygote開始創(chuàng)建JVM并注冊JNI方法,開啟SystemServer。
6.啟動Binder線程沲和SystemServiceManager,并啟動各種服務(wù)。
7.AMS啟動Launcher即android桌面。
BootLoader是在操作系統(tǒng)內(nèi)核運(yùn)行之前運(yùn)行。可以初始化硬件設(shè)備、建立內(nèi)存空間映射圖,從而將系統(tǒng)的軟硬件環(huán)境帶到一個合適狀態(tài),以便為最終調(diào)用操作系統(tǒng)內(nèi)核準(zhǔn)備好正確的環(huán)境。在嵌入式系統(tǒng)中,通常并沒有像BIOS那樣的固件程序(注,有的嵌入式CPU也會內(nèi)嵌一段短小的啟動程序),因此整個系統(tǒng)的加載啟動任務(wù)就完全由BootLoader來完成。

Launcher桌面圖標(biāo)點(diǎn)擊事件
E:\tools\android-src\android-6.0.1_r1\packages\apps\Launcher2\src\com\android\launcher2\Launcher.java
public final class Launcher extends Activity
執(zhí)行onClick(View view)方法,會把這個應(yīng)用的相關(guān)信息傳入
先獲取一個intent--->startActivitySafely(v, intent, tag)--->startActivity(v, intent, tag);
-->startActivity(intent);
startActivity(intent)會開一個APP進(jìn)程(fork zogyte進(jìn)程),通過systemserver--->調(diào)用
ActivityThread.main()。
ActivityThread.java做為入口,用attach開啟app,再加載application和activity
thread.attach(false);--->mgr.attachApplication(mAppThread)會通過遠(yuǎn)端進(jìn)程去回調(diào)private void handleBindApplication(AppBindData data)
Application app = data.info.makeApplication(創(chuàng)建Application對象
mInstrumentation.callApplicationOnCreate(app);---> app.onCreate();--->加載xml

冷啟動(Cold start)
冷啟動是指APP在手機(jī)啟動后第一次運(yùn)行,或者APP進(jìn)程被kill掉后在再次啟動。
可見冷啟動的必要條件是該APP進(jìn)程不存在,這就意味著系統(tǒng)需要創(chuàng)建進(jìn)程,通過ActivityThread.main()入口啟動進(jìn)程和主線程,創(chuàng)建activity和application并初始化。在這三種啟動方式中,冷啟動耗時最長。
????1. Loading and launching the app.
????2. Displaying a blank starting window for the app immediately after launch.
????3. Creating the app process.
????在創(chuàng)建APP進(jìn)程之后
????1. Creating the app object.
????2. Launching the main thread.
????3. Creating the main activity.
????4. Inflating views.
????5. Laying out the screen.
????6. Performing the initial draw.
????當(dāng)app進(jìn)程完成第一次繪制,系統(tǒng)進(jìn)程會用mainActivity替換當(dāng)前的背景窗口,這時用戶才能使用app。

溫啟動(Warm start)
場景就類似打開淘寶逛了一圈然后切到微信去聊天去了,過了很久淘寶的進(jìn)程存在,但是Activity可能被回收。
App進(jìn)程存在,當(dāng)時Activity可能因為內(nèi)存不足被回收。這時候啟動App不需要重新創(chuàng)建進(jìn)程,但是Activity的onCreate還是需要重新執(zhí)行的。可以從savedInstanceState獲取。

熱啟動(Hot start)
場景就類似你打開微信聊了一會天,這時候出去看了下日歷,再次打開微信 。
App進(jìn)程存在,并且所有的Activity對象仍然存在內(nèi)存中沒有被回收。可以重復(fù)避免對象初始化,布局填充和呈現(xiàn),如果有onTrimMemory()方法,就需要對已回收的對象重新創(chuàng)建。

首次啟動
首次啟動嚴(yán)格來說也是冷啟動,之所以把首次啟動單獨(dú)列出來,一般來說,首次啟動會比非首次啟動時間長,首次啟動會做一些系統(tǒng)初始化工作,如緩存目錄的生產(chǎn),數(shù)據(jù)庫的建立,SharedPreference的初始化,如果存在多 dex 和插件的情況下,首次啟動會有一些特殊需要處理的邏輯,而且對啟動速度有很大的影響,所以首次啟動的速度非常重要,畢竟影響用戶對 App 的第一印象。可以類似QQ做一個第一次安裝的初始化環(huán)境的進(jìn)度條。

在最近任務(wù)給App加鎖和啟動方式有什么關(guān)系
某些廠商為了用戶體驗提供了給APP上鎖的功能,目的就是讓用戶自己做主是上鎖的APP不被殺,啟動的時候不會處于冷啟動方式,但是加鎖也不是萬能的,Low memory killer在內(nèi)存極度吃緊的情況下也會殺死加鎖APP,在此啟動時也將以冷啟動方式運(yùn)行。

AI和啟動方式有什么關(guān)系
AI在進(jìn)程管理方面可謂是大有可為。MIUI10發(fā)布了進(jìn)程AI喚醒功能,是APP啟動速度遠(yuǎn)超友商。這其中的道理簡單說就是學(xué)習(xí)用戶的使用習(xí)慣,提前將App進(jìn)程創(chuàng)建好,當(dāng)用戶打開APP時不會出去冷啟動。比如你是微信重度用戶你發(fā)現(xiàn)用了MIUI10就再也見不到微信啟動頁面的那個地球了,這就是AI喚醒的功勞。

以下是Google官方不建議各種啟動時長,
Cold startup takes 5 seconds or longer.
Warm startup takes 2 seconds or longer.
Hot startup takes 1.5 seconds or longer.

黑白屏優(yōu)化(偽優(yōu)化)

白屏    <style name="AppTheme" parent="Theme.AppCompat.Light">
黑屏    <style name="AppTheme">(在5.0以前的老版本上有效,現(xiàn)在的版本默認(rèn)使用透明處理了)

????產(chǎn)生時間:Application.Oncreate() 到 Activity.onCreate()加載XML 到 Activity.onPostResume()之間;Activity.onPostResume后App的業(yè)務(wù)布局頁面可見。
????產(chǎn)生原因:系統(tǒng)顯示一個空白預(yù)覽窗口,其背景色是主題背景色顯示,在Activity.onResume添加View之前
????其他原因:手機(jī)性能 和 Application.Oncreate()到Activity.onPostResume()之間執(zhí)行方法耗時過的造成。
????因為手機(jī)性能我們無法決定,所以只能在背景色和方法性能上去做處理。通常建議黑白屏優(yōu)化和代碼優(yōu)化結(jié)合處理。

源碼位置

只是從體驗上優(yōu)化,不解決或者無法解決性能問題。
修改主題背景色為公司logo圖片,或者背景色透明,或者背景色空并且關(guān)閉預(yù)覽(節(jié)省一點(diǎn)點(diǎn)內(nèi)存);然后進(jìn)入廣告倒計時頁面,為初始化、廣告輪播圖下載、頁面顯示圖片或者頁面背景圖片下載以及各種線性加載提供時間。當(dāng)然要記得雙進(jìn)程保活更新廣告圖片,不然就要每次使用時更新圖片。
背景色透明,或者背景色空并且關(guān)閉預(yù)覽都給用戶一種延時體驗感官,官方也不建議關(guān)閉預(yù)覽。
對主題的更改建議通過代碼或者xml設(shè)置 給啟動activity
背景色空節(jié)省內(nèi)存可以在通過代碼getWindow().setBackgroundDrawable(null);
經(jīng)典APP: 京東、QQ、微信

  • 設(shè)置背景圖片
    利用<layer-list>設(shè)置android:windowBackground 時,
    <layer-list>的android:opacity=”opaque”,為了防止在啟動的時候出現(xiàn)背景的閃爍。
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="android:windowBackground">@drawable/bg</item>
</style>
  • 或者設(shè)置為透明。
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="android:windowIsTranslucent">true</item>
</style>
  • 可以看不見但是這里的view仍然存在,而且占的內(nèi)存依舊存在,沒有背景色且關(guān)閉預(yù)覽功能,改善上面的問題
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="android:windowBackground">@null</item>
    <item name="android:windowDisablePreview">true</item>
</style>
  • 上面直接在主題操作會對使每個activity都有效果。其實(shí)我們只需要對啟動activity設(shè)置。
<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
        <!-- Customize your theme here. -->
    </style>
    
    <style name="AppTheme.Laucher1">
        <item name="android:windowBackground">@null</item>
        <item name="android:windowDisablePreview">true</item>
    </style>
</resources>
<activity
    android:theme="@style/AppTheme.Laucher1"
    android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

MainActivity

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    setTheme(R.style.AppTheme_Laucher1);
}

2. 代碼優(yōu)化(真優(yōu)化)
優(yōu)化分析
????AM
????4.4以前 adb shell am start -W com.lqr.wechat/com.lqr.wechat.activity.SplashActivity
????????ThisTime:最后一個啟動的Activity的啟動耗時;
????????TotalTime:自己的所有Activity的啟動耗時;
????????WaitTime: ActivityManagerService啟動App的Activity時的總時間(包括當(dāng)前Activity的onPause()和自己Activity的啟動)。
????AM路徑:
????E:\tools\android-src\android-6.0.1_r1\frameworks\base\cmds\am\src\com\android\commands\am
????Am.java:946行開始打印啟動時間信息,其中一個result對象,
????初始化 :在871行result = mAm.startActivityAndWait(...)在這個初始化時就已經(jīng)進(jìn)行了時間的計算:
????計算時間:在android-src\android-6.0.1_r1\frameworks\base\services\core\java\com\android\server\am\ActivityRecord.java
????完成計算:void windowsDrawnLocked() --->reportLaunchTimeLocked(SystemClock.uptimeMillis())中完成時間的統(tǒng)計;
????Display
????4.4版本以后Logcat 輸入Display篩選系統(tǒng)日志 不過濾信息No Filters;AM在4.4依舊可以使用,只是display比較方便;
????display包含以下時間:
????1.Launch the process.
????2.Initialize the objects.
????3.Create and initialize the activity.
????4.Inflate the layout.
????5.Draw your application for the first time.
????不包含:未在layout中引用的資源和作為對象初始化中一部分的資源
如果整個過程中有其他activity但是未顯示就會顯示total時間,也就是總時間和單個activity之間存在差異時。

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

????reportFullyDrawn()
????用戶界面已完全加載,并繪制了一些文本,但尚未顯示應(yīng)用程序必須從網(wǎng)絡(luò)中獲取的圖像。調(diào)用reportFullyDrawn() 讓知道你的activity已完成lazy loading。有時也會包含total時間。

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms

DisplayedTime和reportFullyDrawn()區(qū)分,DisplayedTime是在onPostResume之后官圖這點(diǎn)是錯的

reportFullyDrawn使用

????TraceView分析
????Debug.startMethodTracing(filePath);
????中間為需要統(tǒng)計執(zhí)行時間的代碼
????Debug.stopMethodTracing();
????adb pull /storage/emulated/0/app1.trace把文件拉出來分析
把pull到電腦上的文件拖到AS中就可以分析了
????Android vitals
????????分析需要 Google Play Console,可以參照官方文檔
????Inspect CPU activity with CPU Profiler.
????????可以參照官方文檔,就是Androidstudio的Profiler的CPU模塊,可以記錄一段時間內(nèi)代碼執(zhí)行過程和執(zhí)行時間,個人建議使用也可以參考本人的文章
????Overview of system tracing.
????????可以參照官方文檔,短時間內(nèi)記錄設(shè)備活動稱為 system tracing,System Tracing app是一個android工具,可以將設(shè)備活動保存到跟蹤文件中。在運(yùn)行android 10(api級別29)或更高版本的設(shè)備上,跟蹤文件以perfetto格式保存。在運(yùn)行早期版本android的設(shè)備上,跟蹤文件以systrace格式保存。Systrace和Perfetto不會收集應(yīng)用程序進(jìn)程中有關(guān)代碼執(zhí)行的詳細(xì)信息。有關(guān)應(yīng)用程序正在執(zhí)行哪些方法以及使用了多少CPU資源的詳細(xì)信息。本人文章

優(yōu)化方案:
????1. 開子線程,做初始化,比如第三方SDK
????適合不定義handler,不操作UI ,對異步要求不高(主線調(diào)用的方法,在子>線程中的初始化完成后才能調(diào)用),比如glide和greenDAO的初始化
????2. 懶加載,用到的時候再初始化
????如:網(wǎng)絡(luò)OKHttp,數(shù)據(jù)庫操作,這里的網(wǎng)絡(luò)和數(shù)據(jù)庫初始化比較快,如果像數(shù)據(jù)庫升級這種比較耗時的就不要放在這里了;全局靜態(tài)對象,建議使用singleton模式或者dagger;ContentProvider中在Static Block中初始化一些UriMatcher。
????3. 啟動頁(Spalsh Screen)或者啟動頁+廣告頁倒計時頁,啟動頁盡量和背景圖片一樣,多圖通過<layer-list>處理
????不能在子線程初始化的,可以在這里初始化,支持單例,application全局化提供與不提供都可以,還有首頁圖片資源下載。
????以下是廣告倒計時頁圖片下載流程,

4.圖片資源下載

????4. 升級引導(dǎo)頁
????下載插件后解壓和升級過程,包括數(shù)據(jù)庫升級也可以通過升級引導(dǎo)頁完成,類似QQ第一次安裝后啟動時的初始化環(huán)境頁面。
????5. 減少冗余布局或者減少嵌套布局來展示視圖層次結(jié)構(gòu)
????比如用ConstraintLayout替代linearlayout嵌套
????6. 使用viewstub替代不即時顯示部分
????使用viewstub對象作為子層次結(jié)構(gòu)的占位符,可以在更合適的時間對其進(jìn)行擴(kuò)展。
????7.dex問題處理
隨著代碼數(shù)量的膨脹,工程本身的代碼加上引用的第三方庫的代碼中方法數(shù)量會超過65536的限制。是由于DEX文件格式限制,一個DEX文件中method個數(shù)采用使用原生類型short來索引文件中的方法,也就是4個字節(jié)共計最多表達(dá)65536個method,field/class的個數(shù)也均有此限制。 Google為構(gòu)建超過65K方法數(shù)的應(yīng)用提供官方支持的方案:MultiDex。
但是在Dalvik下MultiDex有個問題:5.0以下某些低端機(jī)會出現(xiàn)ANR或者長時間卡頓不進(jìn)入引導(dǎo)頁,而罪魁禍?zhǔn)资荕ultiDex.install(Context context)的dexopt過程耗時過長。因此需要在初次啟動時做特別處理。
而5.0以上會使用ART,在ART下MultiDex是不存在這個問題的,這主要是因為ART下采用Ahead-of-time (AOT) compilation技術(shù),系統(tǒng)在APK的安裝過程中會使用自帶的dex2oat工具對APK中可用的DEX文件進(jìn)行編譯并生成一個可在本地機(jī)器上運(yùn)行的文件,這樣能提高應(yīng)用的啟動速度,只是在安裝過程中進(jìn)行了處理這樣會影響應(yīng)用的安裝速度。
4.2解決思路
1、在Application.attachBaseContext(Context base)中,判斷是否初次啟動,以及系統(tǒng)版本是否小于5.0,如果是,跳到2;否則,直接執(zhí)行MultiDex.install(Context context)。
2、開啟一個新進(jìn)程,在這個進(jìn)程中執(zhí)行MultiDex.install(Context context)。執(zhí)行完畢,喚醒主進(jìn)程,自身結(jié)束。主進(jìn)程在開啟新進(jìn)程后,自身是掛起的,直到被喚醒。
3、喚醒的主進(jìn)程繼續(xù)執(zhí)行初始化操作。
問題總結(jié):
布局太過復(fù)雜
I/O流、序列化和反序列化、網(wǎng)絡(luò)操作阻塞屏幕繪制
加載解碼位圖
光柵化矢量圖(VectorDrawable)
其他子系統(tǒng)初始化

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,699評論 2 370