android app殺死啟動專題及常見問題 - CSDN

2020-12-24 CSDN技術社區

一. 啟動優化的目的

APP啟動如果得到很好的優化,增強用戶體驗增加用戶流量;如果app啟動時間過長影響用戶體驗,從而會造成流失用戶。所以做啟動優化是有必須的。

二. 應用啟動類型

1. 冷啟動
  • 概念:當應用在設備開機或者系統主動 kill APP 進程之後,用戶點擊桌面icon圖標啟動,稱之為冷啟動。
  • 場景:開機後第一次啟動應用 或者 應用被殺死後再次啟動
  • 生命周期:Process.start->Application創建->attachBaseContext->onCreate->onStart->onResume->Activity生命周期
  • 啟動速度:在幾種啟動類型中最慢,也是我們優化啟動速度最大的攔路虎
2. 溫啟動
  • 概念:溫啟動是APP進程還存活,因為內存不足Activity被回收了,當再次啟動APP時就會重新執行Activity生命周期,布局繪製等操作。
  • 場景:應用已經啟動,返回鍵退出
  • 生命周期:onCreate->onStart->onResume->Activity生命周期
  • 啟動速度:較快
3. 熱啟動
  • 概念:當後臺存在該應用的進程或者服務時,用戶點擊icon圖標啟動,稱之為熱啟動。一般是用戶按了home鍵回到桌面,或者返回鍵沒有殺進程,或者app本身做了進程重啟的機制。
  • 場景:Home鍵最小化應用
  • 生命周期:onResume->Activity生命周期
  • 啟動速度:快

從上面的總結可以看出,在應用的啟動過程中,APP啟動所需的時間為:冷啟動時間>溫啟動時間>熱啟動時間,冷啟動是最慢最耗時的,系統以及應用本身都有大量的工作需要處理,所以,冷啟動對於應用的啟動速度是最具挑戰以及最有必要進行優化的。

三. 應用的啟動過程

冷啟動啟動流程——當點擊app的啟動圖標時,安卓系統會從Zygote進程中fork創建出一個新的進程分配給該應用,之後會依次創建和初始化Application類(attachBaseContext()–>OnCreate())、創建MainActivity類、加載主題樣式Theme中的 windowBackground等屬性設置給MainActivity以及配置Activity層級上的一些屬性、再inflate布局、當onCreate/onStart/onResume方法都走完了,最後才進行contentView的measure/layout/draw顯示在界面上,所以直到這裡,應用的第一次啟動才算完成,這時候看到的界面也就是首幀。

大致流程如下:

  • 點擊桌面圖標,Launcher會啟動程序默認的Acticity,之後再按照程序的邏輯啟動各種Activity。
  • 啟動Activity通過應用程式框架層的ActivityManagerService服務啟動(Service也是由ActivityManagerService來啟動的)。ActivityManagerService是一個非常重要的接口,它負責啟動和管理Activity和Service。
  • 無論是通過Launcher來啟動Activity,還是通過Activity內部調用startActivity接口來啟動新的Activity,都通過Binder進程間通信進入到ActivityManagerService進程中,並且調用ActivityManagerService.startActivity接口。
  • ActivityManagerService調用ActivityStack.startActivityMayWait來做準備要啟動的Activity的相關信息。
  • ActivityStack通知ApplicationThread要進行Activity啟動調度了,這裡的ApplicationThread代表的是調用ActivityManagerService.startActivity接口的進程,對於通過點擊應用程式圖標的情景來說,這個進程就是Launcher了,而對於通過在Activity內部調用startActivity的情景來說,這個進程就是這個Activity所在的進程了。
  • ApplicationThread不執行真正的啟動操作,它通過調用ActivityManagerService.activityPaused接口進入到ActivityManagerService進程中,看看是否需要創建新的進程來啟動Activity。
  • 對於通過點擊應用程式圖標來啟動Activity的情景來說,ActivityManagerService在這一步中,會調用startProcessLocked來創建一個新的進程,而對於通過在Activity內部調用startActivity來啟動新的Activity來說,這一步是不需要執行的,因為新的Activity就在原來的Activity所在的進程中進行啟動。
  • ActivityManagerServic調用ApplicationThread.scheduleLaunchActivity接口,通知相應的進程執行啟動Activity的操作。
  • ApplicationThread把這個啟動Activity的操作轉發給ActivityThreadActivityThread通過ClassLoader導入相應的Activity類,然後把它啟動起來。

所以相應的優化方案:

  • 減少 onCreate方法的工作量。
  • 不要讓Application參與業務邏輯。
  • 不要在Application中做耗時操作,一些初始化操作可以開啟子線程來完成。
  • 不要以靜態變量方式在Application中保存數據。
  • 布局優化/mainThread儘量延遲初始化。
  • 啟動畫面的初始化可以使用設置主題背景的方式,速度回更快。

四. 冷啟動流程

冷啟動指的是應用程式從進程在系統不存在,到系統創建應用運行進程空間的過程。冷啟動通常會發生在一下兩種情況:

  • 設備啟動以來首次啟動應用程式
  • 系統殺死應用程式之後再次啟動應用程式

在冷啟動的最開始,系統需要負責做三件事:

  • 加載以及啟動app
  • app啟動之後立刻顯示一個空白的預覽窗口
  • 創建app進程

一旦系統完成創建app進程後,app進程將要接著負責完成下面的工作:

  • 創建Application對象
  • 創建並且啟動主線程ActivityThread
  • 創建啟動第一個Activity
  • Inflating views
  • 布局屏幕
  • 執行第一次繪製

一旦app進程完完成了第一次繪製工作,系統進程就會用main activity替換前面顯示的預覽窗口,這個時候,用戶就可以正式開始與app進行交互了。

從冷啟動的流程看,我們無法幹預app進程創建等系統操作,我們能夠幹預的有:

  • 預覽窗口
  • Application生命周期回調
  • Activity生命周期回調

五. 優化分析測量工具

對研發人員來說,啟動速度是我們的「門面」,它清清楚楚可以被所有人看到,我們都希望自己應用的啟動速度可以秒殺所有競爭對手。

「工欲善其事必先利其器」,我們需要先找到一款適合做啟動優化分析的工具或者方式。

  • adb shell am start -W [packageName]/[ packageName. AppstartActivity]
    在統計 app 啟動時間時,系統為我們提供了 adb 命令,可以輸出啟動時間。系統在繪製完成後,ActivityManagerService 會回調該方法,但是能夠方便我們通過腳本多次啟動測量 TotalTime,對比版本間啟動時間差異。但是統計時間不如 Systrace 準確。

  • 代碼埋點
    通過代碼埋點來準確獲取記錄每個方法的執行時間,知道哪些地方耗時,然後再有針對性地優化。例如通過在 app 啟動生命周期中,關鍵位置加入時間點記錄,達到測量目的;又例如可以在 Application 的 attachBaseContext方法中記錄開始時間,然後在啟動的第一個 Activity 的 onWindowFocusChanged方法記錄結束時間。但是從用戶點擊 app Icon 到 Application 被創建,再到 Activity 的渲染,中間還是有很多步驟的,比如冷啟動的進程創建過程,而這個時間用此版本是沒辦法統計了,必須得承受這點數據的不準確性。

  • Nanoscope
    Nanoscope 非常真實,不過暫時只支持 Nexus 6 和 x86 模擬器。

  • Simpleperf
    Simpleperf 的火焰圖並不適合做啟動流程分析。

  • TraceView
    通過 TraceView 主要可以得到兩種數據:單次執行耗時的方法 以及 執行次數多的方法。但是TraceView 性能耗損太大,不能比較正確反映真實情況。

  • Systrace
    Systrace 能夠追蹤關鍵系統調用的耗時情況,如系統的 IO 操作、內核工作隊列、CPU 負載、Surface 渲染、GC 事件以及 Android 各個子系統的運行狀況等。但是不支持應用程式代碼的耗時分析。綜上所述,這幾種方式都各有各的優點以及缺點,我們都要掌握。

六. 啟動優化方法

在拿到整個啟動流程的全景圖之後,我們可以清楚地看到這段時間內系統、應用各個進程和線程的運行情況,現在我們要開始真正開始「幹活」了。具體的優化方式,我把它們分為預覽窗口優化、業務梳理、業務優化、多進程優化、線程優化、GC 優化和系統調用優化、布局優化

1. 預覽窗口優化

當用戶點擊應用桌面圖標啟動應用的時候,利用提前展示出來的 Window,快速展示出一個界面,用戶只需要很短的時間就可以看到「預覽頁」,這種完全「跟手」的感覺在高端機上體驗非常好,但對於中低端機,會把總的的閃屏時間變得更長。

如果點擊圖標沒有響應,用戶主觀上會認為是手機系統響應比較慢。所以比較推薦的做法是,只在 Android 6.0 或者 Android 7.0 以上才啟用「預覽窗口」方案,讓手機性能好的用戶可以有更好的體驗。

要實現預覽窗口的顯示,只需要在利用 activity 的windowBackground主題屬性提供一個簡單的自定義 drawable 給啟動的 activity,如下:

Layout XML file:

<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>

Manifest file:

<activity ...android:theme="@style/AppTheme.Launcher" />

這樣一個 activity 啟動的時候,就會先顯示一個預覽窗口,給用戶快速響應的體驗。當 activity想要恢復原來 theme,可以通過在調用super.onCreate() 和setContentView()之前調用 setTheme(R.style.AppTheme),如下:

public class MyMainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { setTheme(R.style.Theme_MyApp); super.onCreate(savedInstanceState); }}

2. 業務梳理

不要一股腦把全部初始化工作放在 Application 中做,需要梳理清楚當前啟動過程正在運行的每一個模塊,哪些是一定需要的、哪些可以砍掉、哪些可以懶加載。但是需要注意的是,懶加載要防止集中化,否則容易出現首頁顯示後用戶無法操作的情形。總的來說,用以下四個維度分整理啟動的各個點:

  • 必要且耗時:啟動初始化,考慮用線程來初始化。
  • 必要不耗時:首頁繪製。
  • 非必要但耗時:數據上報、插件初始化。
  • 非必要不耗時:不用想,這塊直接去掉,在需要用的時再加載。

把數據整理出來後,按需實現加載邏輯,採取分步加載、異步加載、延期加載策略,如下圖所示:

一句話概述,要提高應用的啟動速度,核心思想是在啟動過程中少做事情,越少越好。

3. 業務優化

通過梳理之後,剩下的都是啟動過程一定要用的模塊。這個時候,我們只能硬著頭皮去做進一步的優化。優化前期需要「抓大放小」,先看看主線程究竟慢在哪裡。最理想是通過算法進行優化,例如一個數據解密操作需要 1 秒,通過算法優化之後變成 10 毫秒。退而求其次,我們要考慮這些任務是不是可以通過異步線程預加載實現,但需要注意的是過多的線程預加載會讓我們的邏輯變得更加複雜。

業務優化做到後面,會發現一些架構和歷史包袱會拖累我們前進的步伐。比較常見的是一些事件會被各個業務模塊監聽,大量的回調導致很多工作集中執行,部分框架初始化「太厚」,例如一些插件化框架,啟動過程各種反射、各種 Hook,整個耗時至少幾百毫秒。還有一些歷史包袱非常沉重,而且「牽一髮動全身」,改動風險比較大。但是我想說,如果有合適的時機,我們依然需要勇敢去償還這些「歷史債務」。

4. 多進程優化

Android app 是支持多進程的,在 Manifest 中只要在組件聲明中加入android:process屬性就可以讓組件在啟動時運行在不同的進程中。舉個例子: 對於多進程 app ,可能擁有主進程,插件進程以及下載進程,但開發者只能在 Manifest 中聲明一個 Application 組件,如果對應不同進程的組件啟動時,系統會創建三個進程,創建三個 Application 對象,同時attachBaseContext、onCreate等生命周期回調方法也會被調用三次。

但是每個進程需要初始化的內容肯定是不一樣的,所以,為了防止資源的浪費,我們需要在Application 中區分進程,對應進程只初始化對應的內容。

5. 線程優化

線程優化分兩方面:

第一,耗時任務異步化。子線程處理耗時任務,主線程做的事情越少,越早進入Acitivity繪製階段,界面越早展現。例如不在主線程做如 IO 、網絡等耗時操作。但是要注意,子線程不能阻塞主線程。

第二,線程池管理線程,控制線程的數量。線程數量太多會相互競爭 CPU 資源,導致分給主線程的時間片減少,從而導致啟動速度變慢。線程切換的數據我們可以通過卡頓優化中學到的 sched 文件查看,這裡特別需要注意 nr_involuntary_switches 被動切換的次數。

proc/[pid]/sched: nr_voluntary_switches:主動上下文切換次數,因為線程無法獲取所需資源導致上下文切換,最普遍的是 IO。 nr_involuntary_switches:被動上下文切換次數,線程被系統強制調度導致上下文切換,例如大量線程在搶佔 CPU。

第三,避免主線程與子線程之間的鎖阻塞等待。有一次我們把主線程內的一個耗時任務放到線程中並發執行,但是發現這樣做根本沒起作用。仔細檢查後發現線程內部會持有一個鎖,主線程很快就有其他任務因為這個鎖而等待。通過 Systrace 可以看到鎖等待的事件,我們需要排查這些等待是否可以優化,特別是防止主線程出現長時間的空轉。

特別是現在有很多啟動框架,會使用 Pipeline 機制,根據業務優先級規定業務初始化時機。比如微信內部使用的 mmkernel 、阿里最近開源的 Alpha 啟動框架,它們為各個任務建立依賴關係,最終構成一個有向無環圖。對於可以並發的任務,會通過線程池最大程度提升啟動速度。如果任務的依賴關係沒有配置好,很容易出現下圖這種情況,即主線程會一直等待 taskC 結束,空轉 2950 毫秒。

6. GC優化

在啟動過程,要儘量減少 GC 的次數,避免造成主線程長時間的卡頓,特別是對 Dalvik 來說,我們可以通過 Systrace 單獨查看整個啟動過程 GC 的時間。

啟動過程避免進行大量的字符串操作,特別是序列化跟反序列化過程。一些頻繁創建的對象,例如網絡庫和圖片庫中的 Byte 數組、Buffer 可以復用。如果一些模塊實在需要頻繁創建對象,可以考慮移到 Native 實現。

Java 對象的逃逸也很容易引起 GC 問題,我們在寫代碼的時候比較容易忽略這個點。我們應該保證對象生命周期儘量的短,在棧上就進行銷毀。

7. 系統調用優化

部分系統的API使用是阻塞性的,文件很小可能無法感知,當文件過大,或者使用頻繁時,可能造成阻塞。例如:SharedPreference.Editor 的提交操作建議使用異步的 apply,而不是阻塞的 commit。

通過 systrace 的 System Service 類型,我們可以看到啟動過程 System Server 的CPU 工作情況。在啟動過程,我們儘量不要做系統調用,例如 PackageManagerService 操作、Binder 調用等待。

在啟動過程也不要過早地拉起應用的其他進程,System Server 和新的進程都會競爭 CPU 資源。特別是系統內存不足的時候,當我們拉起一個新的進程,可能會成為「壓死駱駝的最後一根稻草」。它可能會觸發系統的 low memorykiller 機制,導致系統殺死和拉起(保活)大量的進程,從而影響前臺進程的 CPU。舉個例子,之前一個程序在啟動過程會拉起下載和視頻播放進程,改為按需拉起後,線上啟動時間提高了 3%,對於 1GB 以下的低端機優化,整個啟動時間可以優化 5%~8%,效果還是非常明顯的。

8. 布局優化

布局越複雜,測量布局繪製的時間就越長。主要做到以下幾點:

  • 布局的層級越少,加載速度越快。
  • 一個控制項的屬性越少,解析越快,刪除控制項中的無用屬性。
  • 使用< ViewStub/>標籤加載一些不常用的布局,做到使用時在加載。
  • 使用 < merge/>標籤減少布局的嵌套層次。
  • 儘可能少用wrap_content,wrap_content會增加布局measure時的計算成本,已知寬高為固定值時,不用wrap_content。

總結

啟動優化,是一項長期的任務,任重而道遠。

開發者要未雨綢繆,在編碼過程中儘量減少給啟動帶來性能損耗的工作,主要注意以下幾個事項:

  • 儘量避免啟動時在主線程做密集繁重的工作,如:避免 I/O 操作、反序列化、網絡操作、鎖等待等。
  • 對模塊以及第三方庫按需加載,採取分步加載、異步加載、延期加載等策略。
  • 利用線程池管理線程,避免創建大量線程,造成 CPU 競爭,導致主線程時間片減少。
  • 啟動過程中,儘量避免頻繁創建的大量對象,減少 GC 給啟動性能帶來的卡頓影響。
  • 儘量避免在啟動過程中調用阻塞性的系統調用。

相關焦點

  • android啟動頁設計專題及常見問題 - CSDN
    轉載請註明出處:http://blog.csdn.net/wangjihuanghun/article/details/63255144啟動頁幾乎成為了每個app的標配,有些商家在啟動頁中增加了開屏廣告以此帶來更多的收入。
  • android通過代碼實現的多布局專題及常見問題 - CSDN
    但是這樣就遇到了很多問題。 首先是SeekBar設置setProgressDrawable問題。因為我們的是視頻播放器,所以這個SeekBar需要有背景、緩衝進度和播放進度,最好的方法就是用layer-list 的xml布局實現,類似這樣:<?xml version="1.0" encoding="utf-8"?
  • windows10卡啟動修復專題及常見問題 - CSDN
    在Windows 10上,安全模式允許加載一組基本功能和通用設備驅動程序,足以解決常見的軟體和硬體問題。例如,當計算機無法正常啟動、網絡連接問題以及應用程式或Windows Update無法下載更新時,可以使用安全模式對其進行故障排除。
  • Android TV開發簡介
    <activity    android:name="com.example.android.MainActivity"    android:label="@string/app_name" >    <intent-filter>      <action android:name="android.intent.action.MAIN" />      <
  • Android TV開發總結(一)構建一個TV app前要知道的事兒
    app上檢查電視設備如果您正在構建一個app運行在TV設備和其他設備,你也許需要去check你的app運行在什麼樣的設備上且可能將在你的app做何種操作。 例如,如果您有一個app被Intent啟動,你的應用應當被檢查設備屬性去確定是否能啟動在TV下的activity或者是在手機上的activity.
  • 雲平臺商業模式專題及常見問題 - CSDN
    使用的PaaS服務包括,MySQL雲資料庫、MongoDB雲資料庫,使用統一的數據訪問層,研發人員可以無需考慮資料庫的底層架構、可用性、擴展等問題,完全透明,將精力專注在項目研發即可。MaxWon實現 整體架構如圖,研發重點為藍色部分,MaxWon的API層和業務邏輯層。
  • android國際化注意專題及常見問題 - CSDN
  • c定時 linux專題及常見問題 - CSDN
    1.3 linux系統下定時任務軟體種類linux系統下的定時任務還真不少,例如:at,crontab,anacronat:適合僅執行一次就結束的調度任務命令,例如:某天晚上需要處理一個任務,僅僅是這一天的晚上,屬於突發性任務,要執行at命令,還需要啟動atd的服務才行[root@linzhongniao ~]# chkconfig
  • 識別當前session專題及常見問題 - CSDN
    對於靜態網站而言這並不是什麼問題,但是對於動態Web應用,無法識別用戶身份並保持用戶狀態則時致命的,因為根本無法提供服務。比如,用戶必須經過登錄之後才能使用Web應用提供的功能,用戶首次通過帳戶和密碼登錄成功後,對於HTTP而言是無法將認證成功的用戶狀態通過協議層面進行保存的。那麼當用戶再次再次請求時,Web伺服器如何得知該請求是哪個用戶發起的呢?
  • vue 渲染函數插槽專題及常見問題 - CSDN
    // app.vue 在開始和結束frame標記之間的內容將插入到插槽所在的frame組件中,替換slot標記。這是最基本的方法。還可以簡單地通過填充指定要放入槽中的默認內容// frame.vue所以現在如果我們這樣使用它:// app.vue「如果這裡沒有指定任何內容,這就是默認內容」是默認內容,但是如果像以前那樣使用它,默認文本將被img標記覆蓋。
  • github 商城 微信小程序專題及常見問題 - CSDN
    17:微信小程序: 音樂播放器https://github.com/eyasliu/wechat-app-music18:使用微信小程序實現分答這款APP的基礎功能https://github.com/davedavehong/fenda-mock19:微信小程序開發demo-地圖定位https://github.com/giscafer
  • Android簡易天氣App
    在app的build.gradle中添加要使用到的依賴。直接都添加好了,已經將後續所有用到的都添加了好了。順便添加compileOptions和dataBinding兩段代碼。android {    ...
  • 垂直行業雲計算專題及常見問題 - CSDN
    黃: 2015年年底啟動醫療雲項目,在之前也做了一些探索,但真正啟動是在2015年年底。問:那個時候金山醫療雲已經成型、可以接入商用了嗎?北京某三甲醫院用的就是這樣的一個服務,解決了安全問題。至於計算能力和存儲能力,這是我們雲廠商的強項,所以當把安全的問題解決之後,我們可以幫他們很大的忙,隨時擴充計算資源問:針對服務性能,從醫院的操作來說,也就是將從科研與臨床方面逐步實現上雲?
  • Android TV開發總結(六)構建一個TV app的直播節目實例
    今天將介紹構建一個TV app的直播節目實例,此實例上傳到Github: https://github.com/hejunlin2013/LivePlayback 喜歡可以star。下方"閱讀原文"可直接到該實例的github, 本文Agenda如下:先看下效果圖:主界面:
  • 夜神模擬器模擬APP+Appium+mitmdump數據抓取
    啟動界面如下:通過appium控制夜神模擬器內的app時與直接操作手機app類似,但是可能連接時會報錯。An unknown server-side error occurred while processing the command.
  • Android Gradle 常用使用場景實現方式的總結
    Gradle 作為一款靈活多變的構建插件,與 Android Studio 的結合,能夠解決過去使用 Eclipse 開發 App 時所遇到的諸多問題。同時,基於 Groove 這樣一款 DSL 語言的腳本特性,記住各種語法顯然又是一件比較困難的事情。
  • Android仿蝦米音樂新版Tab標籤頁
    ><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width
  • 雲存儲 北京專題及常見問題 - CSDN
    據王耀介紹,目前百度雲存儲產品體系主要是塊存儲和對象存儲,前者除了SSD,還提供普通雲硬碟和高性能CDS;在對象存儲方面,針對數據遷移成本問題,百度雲專門開發了一種存儲網關,客戶通過雲伺服器和網關訪問對象存儲,能大幅降低使用成本。據悉,該產品將在2017年第一季度正式發布。以下為王耀發言實錄:大家好。
  • Android藍牙自動配對攻略
    溫柔狠角色的博客地址:http://blog.csdn.net/qq_25827845其實藍牙我本人並沒有怎麼接觸過,不過該文對需要使用到藍牙的朋友應該有一定的幫助。藍牙自動配對,即搜索到其它藍牙設備之後直接進行配對,不需要彈出配對確認框或者密鑰輸入框。經過最近一段時間得研究,針對網上給出的案例。總結了一個親測好使的Demo。
  • Android安全防護之旅---幾行代碼讓Android應用變得更加安全
    我們在編碼美麗微信公眾號已經弄過了很多app了,不管是協議還是外掛,我們都是那麼一路走過來了,在操作的過程中也發現了很多問題就是應用不在乎安全問題帶來的後果