Android性能優化之啓動速度優化

Android性能優化之啓動速度優化

  Android app 啓動速度優化,首先談談爲何會走到優化這一步,若是一開始建立 app 項目的時候就把這個啓動速度考慮進去,那麼確定就不須要從新再來優化一遍了。這是由於在移動互聯網時代,你們都追求快,什麼功能都是先作出來再說,其餘的能夠先不考慮,先佔據先機,或者驗證是否值得作。那爲何要這麼作呢?我我的的觀點有如下幾點android

  • 若是 app 不能快速開發出來,先放出去驗證一下可行性,可能連是否值得作都不知道,若是花很長時間作了一個對用戶無價值的功能,那麼還不如不作
  • 若是 app 不能快速作出來,可能被競爭對手捕獲先機,那麼可能錯失最佳商業時機
  • 若是一開始就規定不能影響啓動速度的這個目標,那麼作功能的時候就會有束縛,快不起來
  • app 初期你們都忙着開發新功能,迭代新版本,沒有時間停下來作優化
  • 同類型 app 變多,競爭對手變多,你們纔開始關注啓動性能,纔開始作啓動速度優化(有主動出擊也有被動優化)

1、引發性能問題的緣由

  隨着項目不斷的快速迭代,每每會形成App啓動卡慢現象,由於可能在App主進程啓動階段或者在主界面啓動階段放了不少初始化其餘業務的邏輯,而這些業務落地可能一開始並不須要用到。本文從做者的親身經歷給你們闡述啓動速度優化相關的點點滴滴,爲啓動速度優化提供一種思路給你們參考。git

2、爲何要作啓動速度優化

  App啓動卡慢會影響一個App的卸載率和使用率。啓動速度快會給人一種輕快的感受,減小用戶等待時間。若是一個App從點擊桌面圖標到看到主界面花了10秒,請問你能接受麼?忍耐很差的估計直接就卸載了,或者沒等打開就直接Home鍵按出去,而後殺進程了。這樣一來App卸載率提高了,使用率降低了。因此對於有大量用戶的App來講,這些性能細節是很重要的,畢竟用戶就是錢啊。github

3、分析制定優化技術路線

3.1 分析啓動性能瓶頸

  在具體的優化以前,首先咱們得找到須要優化的地方,怎麼找?這就要求瞭解Android App的啓動原理,咱們要知道一個App從點擊桌面圖標到咱們看到App的主界面整個過程當中通過了哪些步驟,哪些地方是咱們能夠優化的地方。下圖是App啓動過程的一個大概描述。canvas

具體的代碼流程,分析關鍵的函數耗時
安全

  圖中onFirstDrawFinish和onWindowFocusChanged的先後順序可能會顛倒,可是時間差不大。性能優化

3.2 制定優化方向

  從上面的分析能夠看出,App啓動過程當中咱們優化的地方包括主進程啓動流程和主界面啓動流程,主進程啓動就是Application的建立過程,主界面啓動就是MainActivity的建立過程。只須要分別對這兩個部分進行優化便可。app

  1. Application中attachBaseContext最先被調用,隨後是onCreate方法,儘可能在這兩個方法中不要有耗時操做。
  2. MainActivity中重點關注onCreate,onResume,onWindowFocusChange,Activity啓動完成結束標誌這裏採用沒有使用生命週期函數,而是以主界面View的第一次繪製做爲啓動完成的標誌,View被第一次繪製證實View即將展現出來被咱們看到。因此咱們在Activity根佈局中加入一個自定義View,以它的onDraw方法第一次回調做爲Activity啓動完成的標誌。異步

    public class FirstDrawListenView extends View {
         private boolean isFirstDrawFinish = false;
    
         private IFirstDrawListener mIFirstDrawListener;
    
         public FirstDrawListenView(Context context, AttributeSet attrs) {
             super(context, attrs);
         }
    
         @Override
         protected void onDraw(Canvas canvas) {
             super.onDraw(canvas);
             if (!isFirstDrawFinish) {
                 isFirstDrawFinish = true;
                 if (mIFirstDrawListener != null) {
                     mIFirstDrawListener.onFirstDrawFinish();
                 }
             }
         }
    
         public void setFirstDrawListener(IFirstDrawListener firstDrawListener) {
             mIFirstDrawListener = firstDrawListener;
         }
    
         public interface IFirstDrawListener {
             void onFirstDrawFinish();
         }
     }

4、怎麼統計數據查看優化先後的數據對比

  經過上面的分析,咱們能夠統計進程啓動各個階段的耗時點,以及Activity啓動各個階段的耗時點(這個步驟須要額外在主佈局中加入一個自定義的空View,監聽它的onDraw方法的第一次回調),能夠經過埋點數據收集這些數據,在優化以前能夠先加入埋點數據,統計上報各個時間段的埋點,因此須要先發個版本驗證一下優化以前的狀況。統計數據的機制加入以後,就能夠着手優化了,一邊優化一邊對比,能夠很清楚看到優化先後的對比。ide

5、制定優化的目標

  因爲App啓動速度在不一樣是設備上差異很大,因此目標不太好定,可是作事情總得要有個目標吧。首先咱們使用你們都熟悉的一個概念「秒開」,其次是冷啓動熱啓動分開算,再次是分出不一樣的機型(高端機,中端機型,低端機型),最後是須要先看看沒優化以前的啓動數據。這樣就能夠定義出相似下面的目標:函數

  1. 高端機型1秒內打開(好比小米5,Android6.0以上)
  2. 中端機型1.5秒內打開
  3. 低端機型2.5秒內打開

  上面是終極目標,真正優化的時候,要結合App實際數據以及團隊實際狀況來定本身的優化目標。

6、優化具體步驟

  通常來講,快速優化最好的方式就是把沒必要要提早作的操做放到異步線程中去作,也就是咱們常常作的異步加載。除了異步加載,一些真正有性能影響的代碼須要作具體優化。下面依次介紹一些具體的優化實施步驟。

6.1 封裝一個打印耗時點日誌的輔助類

  優化的時候爲了快速定位耗時的代碼塊,咱們須要在耗時代碼塊的先後加上日誌,統計耗時具體的時間。這個能在Debug模式下幫助咱們快速分析定位到耗時的代碼塊,而後咱們在針對具體的耗時代碼塊去下手,看看怎麼優化。

6.2 異步加載一:Application中加入異步線程

  在Application中封裝兩個方法:onSyncLoad(同步加載)和 onAsyncLoad(異步加載,在Thread中執行),把不須要同步加載的部分所有放到onAsyncLoad方法,須要同步的方法放到onSyncLoad中去作,就這種簡單的分類就能夠帶來一個很好的優化效果。

public class StartUpApplication extends Application {

    @Override
    public void onCreate() {
        // 程序建立時調用,次方法應該執行應該儘可能快,不然會拖慢整個app的啓動速度
        super.onCreate();
        onSyncLoadForCreate();
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        onSyncLoad();
        onAsyncLoad();
    }

    private void onSyncLoadForCreate() {
        AppStartUpTimeLog.isColdStart = true;   // 設置爲冷啓動標誌
        AppLog.log("StartUpApplication onCreate");
        AppStartUpTimeLog.logTimeDiff("App onCreate start", false, true);
        BlockingUtil.simulateBlocking(500); // 模擬阻塞100毫秒
        AppStartUpTimeLog.logTimeDiff("App onCreate end");
    }

    private void onSyncLoad() {
        AppLog.log("StartUpApplication attachBaseContext");
        AppStartUpTimeLog.markStartTime("App attachBaseContext", true);
        BlockingUtil.simulateBlocking(200); // 模擬阻塞100毫秒
        AppStartUpTimeLog.logTimeDiff("App attachBaseContext end", true);
    }

    public void onAsyncLoad() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 異步加載邏輯
            }
        }, "ApplicationAsyncLoad").start();
    }
}

6.3 異步加載二:MainActivity中加入異步線程

  這一步驟與Application的優化思路同樣,也是封裝onSyncLoad和onAsyncLoad方法對現有代碼進行一個分類,可是這兩個方法的調用時機要晚一點,是在主界面首屏繪製完成的時候調用。這個步驟也須要new一個Thead,屬於額外的開銷,不過這不影響咱們總體性能。

6.4 延遲加載功能:首屏繪製完成以後加載

  還有些操做必需要在UI線程作,可是不須要那麼快速就作,這裏放到首屏繪製完成以後,咱們以前在主佈局中加入一個空的View來監聽它的第一次onDraw回調,咱們經過接口的方式把這個事件接到咱們的MainActivity中去(Activity中實現接口的onFirstDrawFinish方法)。爲了讓用戶儘快看到主界面,咱們就能夠把一些須要在UI線程執行,可是又不須要那麼快的執行的操做放到onFirstDrawFinish中去。

6.5 動態加載佈局:主佈局文件優化

  把主界面中不須要第一次就用到的佈局所有使用動態加載的方式來處理,使用ViewStub或者直接在使用時動態addView的方式。

6.6 主佈局文件深度優化

  若是作了上面這些優化仍是會發現進入主界面仍是有些慢,那麼須要重點關注主佈局文件了。主佈局文件的複雜度直接影響到了Activity的加載速度,這個時候須要對主佈局文件進行深度優化了。Activity在加載佈局的時候,會對整個佈局文件進行解析,測量(measure),佈局(layout)和繪製(draw),因此設計簡單合理的佈局尤其重要。佈局的優化不作詳細介紹,網上不少文章的。幾個重要的優化以下:

  1. 減小布局層級
  2. 減小首次加載View的數量
  3. 減小過分繪製

  若是須要看看主佈局加載具體用了多少時間,須要用自定ViewGroup做爲根佈局根元素,而後監控它的onInflateFinished,onMeasure,onLayout,onDraw方法,經過咱們以前寫好的打印時間日誌的輔助類,打印一些關鍵日誌,能夠分析出具體的耗時的步驟,還能夠定位哪一個View加載耗時最長。

6.7 功能代碼深度優化

  前面的優化步驟中,咱們有部分耗時操做放到了首屏繪製onFirstDrawFinish以後來作了,這裏會帶來一個體驗上的問題,雖然進入主界面變快了,可是可能進入以後短暫的時間類UI線程是阻塞的,若是有其餘的UI操做可能會卡主,由於onFirstDrawFinish中掛了不少耗時的操做,須要等這些作完以後UI線程才能空閒。因此咱們還須要對一些功能代碼進行優化,確保其真正用時少。另外咱們異步加載線程中的操做是有必定的安全風險的,若是有些操做很耗時,可能致使咱們進入主界面須要用到數據時尚未準備好,因此異步加載咱們要注意代碼塊的順序,若是有些很是耗時的操做考慮用單獨的線程去處理。

7、總結

  優化是一條持續之路,經過優化咱們能夠了解到影響啓動性能的因素有哪些,這樣咱們平時在編碼的過程當中就會多注意本身的代碼性能。本文從全局的角度去看待整個啓動性能優化,看起來好像還挺容易,可是可能實際過程當中優化並不會很順利,不一樣的設備上可能表現不同,有時候可能啓動一個服務都會耗時。因此要想真正的不耗時,那就是大招:刪除它吧。

8、項目地址

模擬耗時點,打印日誌觀察生命週期函數回調狀況

https://github.com/PopFisher/AppStartUpSpeedOpt

相關文章
相關標籤/搜索