Android View的繪制機(jī)制

大家好缆八!我 胡漢三 又回來啦胞谭!由于之前的一段時間公司的項(xiàng)目比較忙碌恨溜,再加上我又全身心的投入到了 PMP 考證隊(duì)伍中符衔,導(dǎo)致博客停更了好久找前。
不得不說考取 PMP證書,真的需要付出很大的精力判族;首先是因?yàn)樾奶坼X躺盛,因?yàn)榭家淮尉偷酶冻?a href="" target="_blank">3900大洋,著實(shí)心疼靶伟铩槽惫!幸好付出和收獲成了正比,順利的拿到了 5A沃缘,結(jié)束了考試之旅躯枢。
好了廢話說完了,我們接下來就來上干貨啦槐臀!

View的繪制機(jī)制,究竟是怎么回事氓仲?

源碼八!源碼敬扛,你能否一股腦的鉆進(jìn)我的腦子拔鳌!這樣我就不用翻你千百遍啥箭,一回頭你還你我還我了=_=!!

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        ...
        //初始化WindowManageService 即初始化WMS
        WindowManagerGlobal.initialize();
        //創(chuàng)建Activity谍珊,并執(zhí)行onCreate生命周期方法
        Activity a = performLaunchActivity(r, customIntent);
         
        if (a != null) { //證明Activity被正常啟動
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

            if (!r.activity.mFinished && r.startsNotResumed) {
                
                performPauseActivityIfNeeded(r, reason);
                ...
            }
        } else {
            //若執(zhí)行出錯告訴ActivityManager,結(jié)束當(dāng)前Activity
            try {
                ActivityManager.getService()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }

小結(jié)

    1. 初始化WindowManagerService
    1. 生成Activity,并執(zhí)行Activity$onCreate(...)
    1. Activity被正常啟動 則 執(zhí)行 Activity$onResume(...)
    1. Activity未能正常啟動 則 結(jié)束當(dāng)前Activity即通過AMS執(zhí)行finishActivity(...)

分塊

  • [ 1 ] ActivityThread $performLaunchActivity(...)是整個Activity創(chuàng)建的入口
  • [ 2 ] 在ActivityThread $handleResumeActivity(...)方法執(zhí)行過程中傳遞給View類中常量mParent的屬性值類型

[ 1 ] ActivityThread $performLaunchActivity(...)

ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        Activity activity = null;
          //通過類加載的方式創(chuàng)建Activity
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            ...
        }

        try {
          ...
           // [1.1] 通過Instrumentation調(diào)取Activity的onCretae
          if (r.isPersistable()) {
               mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
           } else {
               mInstrumentation.callActivityOnCreate(activity, r.state);
           }
           ...
        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            ...
        }
        return activity;
    }

[ 1.1 ] Instrumentation$callActivityOnCreate

Instrumentation.java

 public void callActivityOnCreate(Activity activity, Bundle icicle) {
        prePerformCreate(activity);
        //至此進(jìn)入到`Activity$onCreate(...)`
        activity.performCreate(icicle);
        postPerformCreate(activity);
    }

至此進(jìn)入到Activity$onCreate(...)并在onCreate(...)中調(diào)用setContentView(int layoutResID)進(jìn)行View的繪制鹏秋,至此我們才開始正式流程:

[ 1.2 ] Activity$setContentView(int layoutResID)

Activity.java

 public void setContentView(@LayoutRes int layoutResID) {
        //[1.3] 獲取Window對象横朋,調(diào)取其setContentView設(shè)置視圖
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

[ 1.3 ] PhoneWindow$setContentView(int layoutResID)

PhoneWindow.java 由于Window是一個抽象類,而PhoneWindow是它唯一兒子压汪,所謂 父債子還 嘛 !所以找它沒錯的

@Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
           //安裝DecorView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
           ...
        } else {
            //解析Layout XML文件
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
       //請求視圖更新
        mContentParent.requestApplyInsets();
        ...
    }

小結(jié)

    1. mContentParentNull,表示當(dāng)前視圖無盛放的容器,即需要安裝頂級視圖DecorView
    1. 解析XML布局文件
    1. 請求視圖更新操作

[ 1.4 ] View$requestApplyInsets()

View.java

    /**
     * The parent this view is attached to.
     * {@hide}
     *
     * @see #getParent()
     */
    protected ViewParent mParent;
    //為mParent變量賦值
    void assignParent(ViewParent parent) {
        if (mParent == null) {
            mParent = parent;
        } else if (parent == null) {
            mParent = null;
        } else {
            throw new RuntimeException("view " + this + " being added, but"
                    + " it already has a parent");
        }
    }
   /*
    * 這是重點(diǎn):
    *        判定mParent是否為Null
    *        不為Null時,調(diào)取mParent的requestFitSystemWindows()
    */
    @Deprecated
    public void requestFitSystemWindows() {
        if (mParent != null) {
            mParent.requestFitSystemWindows();
        }
    }

   /**
     * 在PhoneWindow中調(diào)取的此方法,進(jìn)行視圖更新
     * 此方法無任何操作,而是直接調(diào)取了requestFitSystemWindows()
     */
    public void requestApplyInsets() {
        requestFitSystemWindows();
    }

小結(jié)

    1. PhoneWindow中調(diào)取了View$requestApplyInsets(),由上述代碼可知在此方法中沒有做任何操作求妹,而是直接調(diào)取了View$requestFitSystemWindows()
    1. 而在View$requestFitSystemWindows()方法中它判定了mParent在不為Null的情況下,調(diào)取了mParent$requestFitSystemWindows()
    1. 我們發(fā)現(xiàn)assignParent(ViewParent parent)是為mParent賦值的地方法竞,而mParent的類型為ViewParent岔霸,但可惜的是ViewParent是一個接口俯渤,并沒有實(shí)現(xiàn)代碼
public interface ViewParent {}

那這事就郁悶了,無法確定mParent真實(shí)的類型八匠,那么就無法找到 ViewParent$requestFitSystemWindows()真實(shí)實(shí)現(xiàn)的地方趴酣。那么我們就得再次回歸源碼了,繼續(xù) Read The Fucking Source Code . . . .

[ 2 ] ActivityThread$handleResumeActivity(...)

Activity被正常創(chuàng)建粗來了,也走完了ActivityonCreate生命周期岖寞,那么接下來就該走onResume生命周期了仗谆,我來看看在這里我們能不能找到想要的答案~

 final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ...
        r = performResumeActivity(token, clearHide, reason);
        if (r != null) {
            final Activity a = r.activity;
            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 (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        //重點(diǎn)來啦:這里將DecorView添加到WindowManager中
                        wm.addView(decor, l);
                    } else {
                        a.onWindowAttributesChanged(l);
                    }
                }
            } else if (!willBeVisible) {
                if (localLOGV) Slog.v(
                    TAG, "Launch " + r + " mStartedActivity set");
                r.hideForNow = true;
            }
            ...

        } else {
            ...
        }
    }

[ 2.1 ]WinodwManagerImpl$addView(...)

WindowManager是一個接口,而他的實(shí)現(xiàn)類是WindowManagerImpl捷绒,所以我們就 單刀直入啦!

WindowManagerImpl.java

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
  @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        //不做過多論述贯要,調(diào)取WindowManagerGlobal$addView(...)
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

[ 2.2 ] WindowManagerGlobal$addView(...)

WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {

            ....
           //創(chuàng)建ViewRootImpl對象
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            try {
                //重點(diǎn):
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                ...
            }
        }
    }

[ 2.3 ] ViewRootImpl$setView(...)

ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ...
                //我們要的就是它,原來是在這里為 View中mParent賦值的椭住,
               //為什么是 this 呢崇渗? 難道。京郑。宅广。
                view.assignParent(this);
               ...
            }
        }
    }

看看的類描述

public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {...}

原來如此,ViewRootImpl原來也是ViewParent接口的實(shí)現(xiàn)類之一啊~
那么至此我們就搞清楚ViewmParent的具體類型了些举,就是ViewRootImpl類型跟狱!

是時候解答疑惑了,讓我回過頭看看[ 1.3 ]中的代碼
   /*
    * 這是重點(diǎn):
    *        判定mParent是否為Null
    *        不為Null時户魏,調(diào)取mParent的requestFitSystemWindows()
    */
    @Deprecated
    public void requestFitSystemWindows() {
        if (mParent != null) {
            mParent.requestFitSystemWindows();
        }
    }

我們已經(jīng)確認(rèn)當(dāng)前的mParent是類型了驶臊,那么我們直接去ViewRootImpl中尋找這個方法

[ 1.4 ] ViewRootImpl$requestFitSystemWindows()

ViewRootImpl.java

    @Override
    public void requestFitSystemWindows() {
         //檢查線程
        checkThread();
        mApplyInsetsRequested = true;
        //開始遍歷view
        scheduleTraversals();
    }

    void scheduleTraversals() {
        //當(dāng)繪制任務(wù)執(zhí)行期間,不允許有其他繪制任務(wù)執(zhí)行
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //向Looper的消息隊(duì)列發(fā)布一個同步障礙
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
           //重點(diǎn)來了: post一個消息叼丑,執(zhí)行mTraversalRunnable中任務(wù)
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

[ 1.5 ] mTraversalRunnable

 //它實(shí)現(xiàn)了Runnable接口
  final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            //執(zhí)行遍歷任務(wù)
            doTraversal();
        }
    }
   final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
   //執(zhí)行遍歷任務(wù)
   void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            //重點(diǎn)中的最后一擊
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

當(dāng)我們看到performTraversals()关翎,我們看到了黎明的曙光。鸠信。纵寝。沒錯就是它~~

[ 1.6 ] performTraversals()

 private void performTraversals() {
  ...
  //測量
  performMeasure();
  ...
  //布局
  performLayout();
  ...
  //繪制
  perfromDraw
  ...
}
執(zhí)行完此方法后,我們就完成了對View測量 ==> 布局 ==> 繪制 星立,也就完成了我們的整個流程的剖析K睢葬凳!

........................................................................................................................................................................................................................................................................................................................................................................................................

問題

  • 經(jīng)過上述文章論述,我們知道了DecorView.mParentViewRootImpl類型室奏,那么DecorView兒子的mParent是誰呢火焰?它兒子的兒子的mParent又是誰呢?總不能都是ViewRootImpl類型吧G戏堋荐健!

答案肯定是否定的。
mParent的類型永遠(yuǎn)同當(dāng)前View的父級一個類型琳袄。

來源碼走一圈江场,讓我們見真知!窖逗!

ViewGroup.java

  public void addView(View child, int index, LayoutParams params) {
        ...
        //設(shè)置新LayoutParams時址否,addViewInner()將調(diào)用child.requestLayout()
       //因此,我們之前調(diào)用requestLayout()在我們自己碎紊,以便孩子的請求
        requestLayout();
        invalidate(true);
         //添加內(nèi)部View
        addViewInner(child, index, params, false);
    }
  private void addViewInner(View child, int index, LayoutParams params,boolean preventRequestLayout) {
        ...
        // tell our children
        if (preventRequestLayout) {
            child.assignParent(this);
        } else {
            child.mParent = this;
        }
        ...
    }

通過以上的分析佑附,我們表述已經(jīng)很簡明了,父View將自己賦予了子ViewmParent

那么我可以清晰的總結(jié)一下:

  • DecorViewmParent的類型是ViewRootImpl
  • 其他ViewmParent的類型是Ta的父View仗考,即 Ta的老子R敉! 哈哈

That's all ! Thanks Everybody!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秃嗜,一起剝皮案震驚了整個濱河市权均,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锅锨,老刑警劉巖叽赊,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異必搞,居然都是意外死亡必指,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門恕洲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塔橡,“玉大人,你說我怎么就攤上這事研侣∑仔埃” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵庶诡,是天一觀的道長惦银。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么扯俱? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任书蚪,我火速辦了婚禮,結(jié)果婚禮上迅栅,老公的妹妹穿的比我還像新娘殊校。我一直安慰自己,他們只是感情好读存,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布为流。 她就那樣靜靜地躺著,像睡著了一般让簿。 火紅的嫁衣襯著肌膚如雪敬察。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天尔当,我揣著相機(jī)與錄音莲祸,去河邊找鬼。 笑死椭迎,一個胖子當(dāng)著我的面吹牛锐帜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播畜号,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼缴阎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了简软?” 一聲冷哼從身側(cè)響起驰怎,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤猜年,失蹤者是張志新(化名)和其女友劉穎绢片,沒想到半個月后焦履,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贸典,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡视卢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了廊驼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片据过。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妒挎,靈堂內(nèi)的尸體忽然破棺而出绳锅,到底是詐尸還是另有隱情,我是刑警寧澤酝掩,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布鳞芙,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏原朝。R本人自食惡果不足惜驯嘱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望喳坠。 院中可真熱鬧鞠评,春花似錦、人聲如沸壕鹉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晾浴。三九已至负乡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間怠肋,已是汗流浹背敬鬓。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留笙各,地道東北人钉答。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像杈抢,于是被迫代替她去往敵國和親数尿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359