應(yīng)用啟動(dòng)優(yōu)化:一種DelayLoad的實(shí)現(xiàn)和原理(下篇)

上一篇文章我們使用第三種方法來實(shí)現(xiàn)延遲加載肢娘。不過上一篇寫的比較簡(jiǎn)單,只是講解了如何去實(shí)現(xiàn)舆驶,這一篇就來講一下為何要這么做橱健,以及這么做后面的原理。
其中會(huì)涉及到一些 Android 中的比較重要的類沙廉,以及 Activity 生命周期中比較重要的幾個(gè)函數(shù)畴博。

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

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

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

1. Activity.getWindow 及 PhoneWindow 的初始化時(shí)機(jī)

Activity 的 getWindow 方法獲取到的是一個(gè) PhoneWindow 對(duì)象:

    public Window getWindow() {
        return mWindow;
    }

這個(gè) mWindow 就是一個(gè) PhoneWindow 對(duì)象,其初始化的時(shí)機(jī)為這個(gè) Activity attach 的時(shí)候:

    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) 最終會(huì)調(diào)用 Policy 的 makeNewWindow 方法
    public Window makeNewWindow(Context context) {
        return new PhoneWindow(context);
    }

  }

這里需要注意 Activity 的 attach 方法很早就會(huì)調(diào)用的蓝仲,是要早于 Activity 的 onCreate 方法的俱病。

總結(jié):

  • PhoneWindow 與 Activity 是一對(duì)一的關(guān)系官疲,通過上面的初始化過程你應(yīng)該更加清楚這個(gè)概念
  • Android 中對(duì) PhoneWindow 的注釋是 :Android-specific Window ,可見其重要性
  • PhoneWindow 中有很多大家比較熟悉的方法亮隙,比如 setContentView / addContentView 等 途凫; 也有幾個(gè)重要的內(nèi)部類,比如:DecorView ;

2. PhoneWindow.getDecorView 及 DecorView 的初始化時(shí)機(jī)

上面我們說到 DecorView是 PhoneWindow 的一個(gè)內(nèi)部類溢吻,其定義如下:

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

那么 DecorView 是什么時(shí)候初始化的呢维费?DecorView 是在 Activity 的父類的 onCreate 方法中被初始化的,比如我例子中的 MainActivity 是繼承自 android.support.v7.app.AppCompatActivity 促王,當(dāng)我們調(diào)用 MainActivity 的 super.onCreate(savedInstanceState); 的時(shí)候犀盟,就會(huì)調(diào)用下面的

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

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

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

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

就是這里的 mWindow.getDecorView() 阅畴,對(duì) DecorView 進(jìn)行了實(shí)例化:

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

第一次調(diào)用 getDecorView 的時(shí)候,會(huì)進(jìn)入 installDecor 方法迅耘,這個(gè)方法對(duì) DecorView 進(jìn)行了一系列的初始化 贱枣,其中比較重要的幾個(gè)方法有:generateDecor / generateLayout 等,generateLayout 會(huì)從當(dāng)前的 Activity 的 Theme 提取相關(guān)的屬性颤专,設(shè)置給 Window纽哥,同時(shí)還會(huì)初始化一個(gè) startingView,添加到 DecorView上栖秕,也就是我們所說的 startingWindow春塌。

總結(jié)

  • Decor 有裝飾的意思,DecorView 官方注釋為 “This is the top-level view of the window, containing the window decor” , 我們可以理解為 DecorView 是我們當(dāng)前 Activity 的最下面的布局簇捍。所以我們打開 DDMS 查看 Tree Overview 的時(shí)候只壳,可以發(fā)現(xiàn)最根部的那個(gè) View 就是 DecorView:


    DelayLoad1.png
  • 應(yīng)用從桌面啟動(dòng)的時(shí)候,在主 Activity 還沒有顯示的時(shí)候垦写,如果主題沒有設(shè)置窗口的背景吕世,那么我們就會(huì)看到白色(這個(gè)和手機(jī)的Rom也有關(guān)系),如果應(yīng)用啟動(dòng)很慢梯投,那么用戶得看好一會(huì)白色命辖。如果要避免這個(gè),則可以在 Application 或者 Activity 的 Theme 中設(shè)置 WindowBackground , 這樣就可以避免白色(當(dāng)然現(xiàn)在各種大廠都是SplashActivity+廣告我也是可以理解的)

3. Post

當(dāng)我們調(diào)用 DecorView 的 Post 的時(shí)候分蓖,其實(shí)最終會(huì)調(diào)用 View 的 Post 尔艇,因?yàn)?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 ,我們調(diào)用 post 是在 Activity 的 onCreate 中調(diào)用的么鹤,那么此時(shí) mAttachInfo 是否為空呢终娃?答案是 mAttachInfo 此時(shí)為空。

這里有一個(gè)點(diǎn)就是 Activity 的各個(gè)回調(diào)函數(shù)都是干嘛的蒸甜?是不是平時(shí)自己寫應(yīng)用的時(shí)候棠耕,貌似在 onCreate 里面搞定一切就OK了余佛, onResume ? onStart窍荧?沒怎么涉及到嘛辉巡,其實(shí)不然。
onCreate 顧名思義就是 Create 蕊退,我們?cè)谇懊婵吹浇奸梗珹ctivity 的 onCreate 函數(shù)做了很多初始化的操作,包括 PhoneWindow/DecorView/StartingView/setContentView等瓤荔,但是 onCreate 只是初始化了這些對(duì)象.
真正要設(shè)置為顯示則在 Resume 的時(shí)候净蚤,不過這些對(duì)開發(fā)者是透明了,具體可以看 ActivityThread 的 handleResumeActivity 函數(shù)输硝,handleResumeActivity 中除了調(diào)用 Activity 的 onResume 回調(diào)之外今瀑,還初始化了幾個(gè)比較重要的類: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聯(lián)系起來放椰,這句最終會(huì)調(diào)用到 WindowManagerGlobal 的 addView 函數(shù)作烟,

    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 系統(tǒng)的一個(gè)核心類愉粤,其定義如下:

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

ViewRootImpl 初始化的時(shí)候會(huì)對(duì) AttachInfo 進(jìn)行初始化,這就是為什么之前的在 onCreate 的時(shí)候 attachInfo 為空拿撩。ViewRootImpl 里面有很多我們比較熟悉也非常重要的方法衣厘,比如 performTraversals / performLayout / performMeasure / performDraw / draw 等。
我們繼續(xù) addView 中的root.setView(view, wparams, panelParentView); 傳入的 view 為 decorView压恒,root 為 ViewRootImpl 影暴,這個(gè)函數(shù)中將 ViewRootImpl 的mView 變量 設(shè)置為傳入的view,也就是 decorView探赫。
這樣來看型宙,ViewRootImpl 與 DecorView 的關(guān)系我們也清楚了。

扯了一圈伦吠,我們?cè)倩氐酱髽?biāo)題的 Post 函數(shù)上妆兑,前面有說這個(gè) Post 走的是 View 的Post 函數(shù),由于 在 onCreate 的時(shí)候 attachInfo 為空毛仪,所以會(huì)走下面的分支:ViewRootImpl.getRunQueue().post(action);
注意這里的 getRunQueue 得到的并不是 Looper 里面的那個(gè) MessageQueue搁嗓,而是由 ViewRootImpl 維持的一個(gè) RunQueue 對(duì)象,其核心為一個(gè) 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();
            }
        }

當(dāng)我們執(zhí)行了 Post 之后 箱靴,其實(shí)只是把 Runnable 封裝成一個(gè) HandlerAction 對(duì)象存入到 ArrayList 中腺逛,當(dāng)執(zhí)行到 executeActions 方法的時(shí)候,將存在這里的 HandlerAction 再通過 executeActions 方法傳入的 Handler 對(duì)象重新進(jìn)行 Post衡怀。
那么 executeActions 方法是什么時(shí)候執(zhí)行的呢棍矛?傳入的 Handler 又是哪個(gè) Handler 呢安疗?

4. PerformTraversals

我們之前講過,ViewRootImpl 的 performTraversals 方法是一個(gè)很核心的方法够委,每一幀繪制都會(huì)走一遍茂契,調(diào)用各種 measure / layout / draw 等 ,最終將要顯示的數(shù)據(jù)交給 hwui 去進(jìn)行繪制慨绳。
我們上一節(jié)講到的 executeActions 掉冶,就是在 performTraversals 中執(zhí)行的:

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

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

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

這里的 mHandler 是一個(gè) ViewRootHandler 對(duì)象:

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

我們注意到 ViewRootHandler 在創(chuàng)建的時(shí)候并沒有傳入一個(gè) Looper 對(duì)象脐雪,這意味著此 ViewRootHandler 的 Looper 就是 mainLooper厌小。

這下我們就清楚了,我們?cè)?onCreate 中 Post 的 runnable 對(duì)象战秋,最終還是在第一個(gè) performTraversals 方法執(zhí)行的時(shí)候璧亚,加入到了 MainLooper 的 MessageQueue 里面了。

繞了一圈終于我們終于把文章最前面的那句話解釋清楚了脂信,當(dāng)然中間還有很多的廢話癣蟋,不過我估計(jì)能耐著性子看到這里的人會(huì)很少,所以如果你看到了這里狰闪,可以在底下的評(píng)論里面將 index ++ 疯搅;這里 index = 0 ;就是看看幾個(gè)人是真正認(rèn)真看了這篇文章的埋泵。

5. UpdateText

接著 performTraversals 我們繼續(xù)說幔欧,話說在第一篇文章 我們有講到,Activity 在啟動(dòng)時(shí)丽声,會(huì)在第二次執(zhí)行 performTraversals 才會(huì)去真正的繪制礁蔗,原因在于第一次執(zhí)行 performTraversals 的時(shí)候,會(huì)走到 Egl 初始化的邏輯雁社,然后會(huì)重新執(zhí)行一次 performTraversals 浴井。
所以前一篇文章的評(píng)論區(qū)有人問為何在 run 方法里面還要 post 一次,如果在 run 方法里面直接執(zhí)行 updateText 方法 霉撵,那么 updateText 就會(huì)在第一個(gè) performTraversals 之后就執(zhí)行磺浙,而不是在第一幀繪制完成后才去執(zhí)行,所以我們又 Post 了一次 喊巍。所以大概的處理步驟如下:

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

第二步:ViewRootImpl.performTraversals -->Runnable

第三步:Runnable --> ViewRootImpl.performTraversals

第四步:ViewRootImpl.performTraversals --> UpdateText

第五步:UpdateText

6. 總結(jié)

其實(shí)一路跟下來發(fā)現(xiàn)其實(shí)原理很簡(jiǎn)單屠缭,其實(shí) DelayLoad 其實(shí)只是一個(gè)很小的點(diǎn),關(guān)鍵是教大家如何去跟蹤一個(gè)自己不認(rèn)識(shí)的知識(shí)點(diǎn)或者優(yōu)化崭参,這里面主要用到了兩個(gè)工具:Systrace 和 Method Trace呵曹, 以及源碼編譯和調(diào)試。
關(guān)于 Systrace 和 Method Trace 的使用,之后會(huì)有詳細(xì)的文章去介紹奄喂,這兩個(gè)工具非常有助于理解源碼和一些技術(shù)的實(shí)現(xiàn)铐殃。

Systrace
Method Trace
源碼編譯與調(diào)試

代碼和博客

本文涉及到的代碼已經(jīng)上傳到了Github:
https://github.com/Gracker/DelayLoadSample

本文最先發(fā)布于本人的博客:
http://androidperformance.com/2015/12/29/Android%E5%BA%94%E7%94%A8%E5%90%AF%E5%8A%A8%E4%BC%98%E5%8C%96-%E4%B8%80%E7%A7%8DDelayLoad%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%92%8C%E5%8E%9F%E7%90%86-%E4%B8%8B%E7%AF%87.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市跨新,隨后出現(xiàn)的幾起案子富腊,更是在濱河造成了極大的恐慌,老刑警劉巖域帐,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赘被,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡肖揣,警方通過查閱死者的電腦和手機(jī)民假,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來龙优,“玉大人羊异,你說我怎么就攤上這事⊥希” “怎么了野舶?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)宰衙。 經(jīng)常有香客問我平道,道長(zhǎng),這世上最難降的妖魔是什么菩浙? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任巢掺,我火速辦了婚禮句伶,結(jié)果婚禮上劲蜻,老公的妹妹穿的比我還像新娘。我一直安慰自己考余,他們只是感情好先嬉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著楚堤,像睡著了一般疫蔓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上身冬,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天衅胀,我揣著相機(jī)與錄音,去河邊找鬼酥筝。 笑死滚躯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掸掏,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼茁影,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了丧凤?” 一聲冷哼從身側(cè)響起募闲,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎愿待,沒想到半個(gè)月后浩螺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仍侥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年年扩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片访圃。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡厨幻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腿时,到底是詐尸還是另有隱情况脆,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布批糟,位于F島的核電站格了,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏徽鼎。R本人自食惡果不足惜盛末,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望否淤。 院中可真熱鬧悄但,春花似錦、人聲如沸石抡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啰扛。三九已至嚎京,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間隐解,已是汗流浹背鞍帝。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留煞茫,地道東北人帕涌。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓岩臣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親宵膨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子架谎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容