Android應用啟動優化:一種DelayLoad的實現和原理

jopen 8年前發布 | 18K 次閱讀 安卓開發 Android開發 移動開發

上一篇文章我們使用第三種方法來實現延遲加載。不過上一篇寫的比較簡單,只是講解了如何去實現,這一篇就來講一下為何要這么做,以及這么做后面的原理。

其中會涉及到一些 Android 中的比較重要的類,以及 Activity 生命周期中比較重要的幾個函數。

其實這個其中的原理比較簡單,不過要弄清楚其實現的過程,還是一件蠻好玩的事情,其中會用到一些工具,自己加調試代碼等,一步一步下來,自己對 Activity 的啟動的理解又深了一層,希望大家讀完之后也會對大家有一定的幫助。

上一篇中我們最終使用的 DelayLoad 的核心方法是在 Activity 的 onCreate 函數中加入下面的方法 :

getWindow().getDecorView().post(new Runnable() {
    @Override
    public void run() {
        myHandler.post(mLoadingRunnable);
    }
});

我們一一來看涉及到的類和方法

1. Activity.getWindow 及 PhoneWindow 的初始化時機

Activity 的 getWindow 方法獲取到的是一個 PhoneWindow 對象:

public Window getWindow() {
    return mWindow;
}

這個 mWindow 就是一個 PhoneWindow 對象,其初始化的時機為這個 Activity attach 的時候:

  final void attach(Context context, ActivityThread aThread,
          Instrumentation instr, IBinder token, int ident,
          Application application, Intent intent, ActivityInfo info,
          CharSequence title, Activity parent, String id,
          NonConfigurationInstances lastNonConfigurationInstances,
          Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
      attachBaseContext(context);

      mFragments.attachActivity(this, mContainer, null);

      mWindow = PolicyManager.makeNewWindow(this);
      mWindow.setCallback(this);
      mWindow.setOnWindowDismissedCallback(this);
      mWindow.getLayoutInflater().setPrivateFactory(this);
      ........

  // PolicyManager.makeNewWindow(this) 最終會調用 Policy 的 makeNewWindow 方法
  public Window makeNewWindow(Context context) {
      return new PhoneWindow(context);
  }

}

這里需要注意 Activity 的 attach 方法很早就會調用的,是要早于 Activity 的 onCreate 方法的。

總結:

  • PhoneWindow 與 Activity 是一對一的關系,通過上面的初始化過程你應該更加清楚這個概念
  • Android 中對 PhoneWindow 的注釋是 :Android-specific Window ,可見其重要性
  • PhoneWindow 中有很多大家比較熟悉的方法,比如 setContentView / addContentView 等 ; 也有幾個重要的內部類,比如:DecorView ;

2. PhoneWindow.getDecorView 及 DecorView 的初始化時機

上面我們說到 DecorView是 PhoneWindow 的一個內部類,其定義如下:

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

那么 DecorView 是什么時候初始化的呢?DecorView 是在 Activity 的父類的 onCreate 方法中被初始化的,比如我例子中的 MainActivity 是繼承自 android.support.v7.app.AppCompatActivity ,當我們調用 MainActivity 的 super.onCreate(savedInstanceState); 的時候,就會調用下面的

protected void onCreate(@Nullable Bundle savedInstanceState) {
    getDelegate().installViewFactory();
    getDelegate().onCreate(savedInstanceState);
    super.onCreate(savedInstanceState);
}

由于我們導入的是 support.v7 包里面的AppCompatActivity, getDelegate() 得到的就是AppCompatDelegateImplV7 ,其 onCreate 方法如下:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mWindowDecor = (ViewGroup) mWindow.getDecorView();
    ......
}

就是這里的 mWindow.getDecorView() ,對 DecorView 進行了實例化:

public final View getDecorView() {
    if (mDecor == null) {
        installDecor();
    }
    return mDecor;
}

第一次調用 getDecorView 的時候,會進入 installDecor 方法,這個方法對 DecorView 進行了一系列的初始化 ,其中比較重要的幾個方法有:generateDecor / generateLayout 等,generateLayout 會從當前的 Activity 的 Theme 提取相關的屬性,設置給 Window,同時還會初始化一個 startingView,添加到 DecorView上,也就是我們所說的 startingWindow。

總結

  • Decor 有裝飾的意思,DecorView 官方注釋為 “This is the top-level view of the window, containing the window decor” , 我們可以理解為 DecorView 是我們當前 Activity 的最下面的布局。所以我們打開 DDMS 查看 Tree Overview 的時候,可以發現最根部的那個 View 就是 DecorView:
    Android應用啟動優化:一種DelayLoad的實現和原理
  • 應用從桌面啟動的時候,在主 Activity 還沒有顯示的時候,如果主題沒有設置窗口的背景,那么我們就會看到白色(這個和手機的Rom也有關系),如果應用啟動很慢,那么用戶得看好一會白色。如果要避免這個,則可以在 Application 或者 Activity 的 Theme 中設置 WindowBackground , 這樣就可以避免白色(當然現在各種大廠都是SplashActivity+廣告我也是可以理解的)

3. Post

當我們調用 DecorView 的 Post 的時候,其實最終會調用 View 的 Post ,因為 DecorView 最終是繼承 View 的:

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
    // Assume that post will succeed later
    ViewRootImpl.getRunQueue().post(action);
    return true;
}

注意這里的 mAttachInfo ,我們調用 post 是在 Activity 的 onCreate 中調用的,那么此時 mAttachInfo 是否為空呢?答案是 mAttachInfo 此時為空。

這里有一個點就是 Activity 的各個回調函數都是干嘛的?是不是平時自己寫應用的時候,貌似在 onCreate 里面搞定一切就OK了, onResume ? onStart?沒怎么涉及到嘛,其實不然。

onCreate 顧名思義就是 Create ,我們在前面看到,Activity 的 onCreate 函數做了很多初始化的操作,包括 PhoneWindow/DecorView/StartingView/setContentView等,但是 onCreate 只是初始化了這些對象.

真正要設置為顯示則在 Resume 的時候,不過這些對開發者是透明了,具體可以看 ActivityThread 的 handleResumeActivity 函數,handleResumeActivity 中除了調用 Activity 的 onResume 回調之外,還初始化了幾個比較重要的類:ViewRootImpl / ThreadedRenderer。

ActivityThread.handleResumeActivity:

if (r.window == null && !a.mFinished && willBeVisible) {
    r.window = r.activity.getWindow();
    View decor = r.window.getDecorView();
    decor.setVisibility(View.INVISIBLE);
    ViewManager wm = a.getWindowManager();
    WindowManager.LayoutParams l = r.window.getAttributes();
    a.mDecor = decor;
    l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
    l.softInputMode |= forwardBit;
    if (a.mVisibleFromClient) {
        a.mWindowAdded = true;
        wm.addView(decor, l);
    }

主要是 wm.addView(decor, l); 這句,將 decorView 與 WindowManagerImpl聯系起來,這句最終會調用到 WindowManagerGlobal 的 addView 函數,

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ......
    ViewRootImpl root;
    View panelParentView = null;
    ......
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }

    // do this last because it fires off messages to start doing things
    try {
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
      ......
    }
}

我們知道 ViewRootImpl 是 View 系統的一個核心類,其定義如下:

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks

ViewRootImpl 初始化的時候會對 AttachInfo 進行初始化,這就是為什么之前的在 onCreate 的時候 attachInfo 為空。ViewRootImpl 里面有很多我們比較熟悉也非常重要的方法,比如 performTraversals / performLayout / performMeasure / performDraw / draw 等。

我們繼續 addView 中的root.setView(view, wparams, panelParentView); 傳入的 view 為 decorView,root 為 ViewRootImpl ,這個函數中將 ViewRootImpl 的mView 變量 設置為傳入的view,也就是 decorView。

這樣來看,ViewRootImpl 與 DecorView 的關系我們也清楚了。

扯了一圈,我們再回到大標題的 Post 函數上,前面有說這個 Post 走的是 View 的Post 函數,由于 在 onCreate 的時候 attachInfo 為空,所以會走下面的分支:ViewRootImpl.getRunQueue().post(action);

注意這里的 getRunQueue 得到的并不是 Looper 里面的那個 MessageQueue,而是由 ViewRootImpl 維持的一個 RunQueue 對象,其核心為一個 ArrayList :

private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();

        void post(Runnable action) {
            postDelayed(action, 0);
        }

        void postDelayed(Runnable action, long delayMillis) {
            HandlerAction handlerAction = new HandlerAction();
            handlerAction.action = action;
            handlerAction.delay = delayMillis;

            synchronized (mActions) {
                mActions.add(handlerAction);
            }
        }

        void executeActions(Handler handler) {
            synchronized (mActions) {
                final ArrayList<HandlerAction> actions = mActions;
                final int count = actions.size();

                for (int i = 0; i < count; i++) {
                    final HandlerAction handlerAction = actions.get(i);
                    handler.postDelayed(handlerAction.action, handlerAction.delay);
                }

                actions.clear();
            }
        }

當我們執行了 Post 之后 ,其實只是把 Runnable 封裝成一個 HandlerAction 對象存入到 ArrayList 中,當執行到 executeActions 方法的時候,將存在這里的 HandlerAction 再通過 executeActions 方法傳入的 Handler 對象重新進行 Post。那么 executeActions 方法是什么時候執行的呢?傳入的 Handler 又是哪個 Handler 呢?

4. PerformTraversals

我們之前講過,ViewRootImpl 的 performTraversals 方法是一個很核心的方法,每一幀繪制都會走一遍,調用各種 measure / layout / draw 等 ,最終將要顯示的數據交給 hwui 去進行繪制。

我們上一節講到的 executeActions ,就是在 performTraversals 中執行的:

// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);

可以看到這里傳入的 Handler 是 mAttachInfo.mHandler ,上一節講到 mAttachInfo 是在 ViewRootImpl 初始化的時候一起初始化的:

mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);

這里的 mHandler 是一個 ViewRootHandler 對象:

final class ViewRootHandler extends Handler{
    ......
}
......
final ViewRootHandler mHandler = new ViewRootHandler();

我們注意到 ViewRootHandler 在創建的時候并沒有傳入一個 Looper 對象,這意味著此 ViewRootHandler 的 Looper 就是 mainLooper。

這下我們就清楚了,我們在 onCreate 中 Post 的 runnable 對象,最終還是在第一個 performTraversals 方法執行的時候,加入到了 MainLooper 的 MessageQueue 里面了。

繞了一圈終于我們終于把文章最前面的那句話解釋清楚了,當然中間還有很多的廢話,不過我估計能耐著性子看到這里的人會很少,所以如果你看到了這里,可以在底下的評論里面將 index ++ ;這里 index = 0 ;就是看看幾個人是真正認真看了這篇文章的。

5. UpdateText

接著 performTraversals 我們繼續說,話說在第一篇文章 我們有講到,Activity 在啟動時,會在第二次執行 performTraversals 才會去真正的繪制,原因在于第一次執行 performTraversals 的時候,會走到 Egl 初始化的邏輯,然后會重新執行一次 performTraversals 。

所以前一篇文章的評論區有人問為何在 run 方法里面還要 post 一次,如果在 run 方法里面直接執行 updateText 方法 ,那么 updateText 就會在第一個 performTraversals 之后就執行,而不是在第一幀繪制完成后才去執行,所以我們又 Post 了一次 。所以大概的處理步驟如下:

第一步:Activity.onCreate –> Activity.onStart –> Activity.onResume

第二步:ViewRootImpl.performTraversals –>Runnable

第三步:Runnable –> ViewRootImpl.performTraversals

第四步:ViewRootImpl.performTraversals –> UpdateText

第五步:UpdateText

6. 總結

其實一路跟下來發現其實原理很簡單,其實 DelayLoad 其實只是一個很小的點,關鍵是教大家如何去跟蹤一個自己不認識的知識點或者優化,這里面主要用到了兩個工具:Systrace 和 Method Trace, 以及源碼編譯和調試。關于 Systrace 和 Method Trace 的使用,之后會有詳細的文章去介紹,這兩個工具非常有助于理解源碼和一些技術的實現。

Android應用啟動優化:一種DelayLoad的實現和原理

Android應用啟動優化:一種DelayLoad的實現和原理

Android應用啟動優化:一種DelayLoad的實現和原理

來自: http://androidperformance.com/2015/12/29/Android應用啟動優化-一種DelayLoad的實現和原理-下篇.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!