從源碼角度分析Activity拦耐、Window、View的關(guān)系

View依附于Window见剩,而Activity負(fù)責(zé)管理Window杀糯。為什么會產(chǎn)生這樣的關(guān)系呢?文章圍繞這個問題苍苞。將會從Activity加載View的整個流程去分析Activity固翰、Window、View三者之間的關(guān)系羹呵。

1骂际、在啟動Activity時,會在onCreate()方法中調(diào)用setContentView()去加載布局冈欢,在這一階段歉铝,只是把布局添加到了DecorView(根視圖)中,并沒有真正的依附于Window凑耻,那么是什么時候添加到Window的呢太示?這個問題先記著,接下來通過分析源碼揭開其廬山真面目香浩。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
      //......
    }

 public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        //初始化ActionBar
        initWindowDecorActionBar();
    }

上述代碼中类缤,可以發(fā)現(xiàn),直接把任務(wù)轉(zhuǎn)移到Activity的setContentView()方法中邻吭,調(diào)用了getWindow().setContentView()加載我們的布局資源文件呀非,然后initWindowDecorActionBar()初始化ActionBar,插個畫:


未命名文件.png

getWindow()會返回一個Window對象mWindow,實際指向了Window的唯一實現(xiàn)類PhoneWindow岸裙,調(diào)用setContentView()是把資源文件加載到圖中的Content部分猖败。initWindowDecorActionBar()初始化圖中的DecorActionBar。mWindow的初始化會在下一篇文章分析降允,在這里先提下恩闻,其實就是在啟動Activity的時候,調(diào)用了Activity的attach()方法剧董。

2幢尚、通過1的分析,這時把視線轉(zhuǎn)移到PhoneWindow的setContentView()中翅楼,看下面源碼:

 public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //對布局文件進(jìn)行解析
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }


 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);//加載一個DecorView
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);//獲取content部分
          //省略代碼....
        }  
 //省略代碼....

protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext().getResources());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

由于代碼量太大尉剩,所以選擇了一些關(guān)鍵性的代碼來進(jìn)行分析。接著分析毅臊,在上面的代碼中理茎,首先第一次啟動mContentParent 肯定為null,調(diào)用installDecor()管嬉,在installDecor()方法里面通過generateDecor()生成一個DecorView對象mDecor皂林,然后通過generateLayout(mDecor)獲取一個mContentParent,mContentParent也就是上圖的Content部分蚯撩。

3础倍、在2中分析了DecorView和mContentParent的初始化,下面將任務(wù)轉(zhuǎn)移到布局文件的解析mLayoutInflater.inflate(layoutResID, mContentParent)中胎挎,mLayoutInflater是一個LayoutInflater的對象沟启,進(jìn)入LayoutInflater的inflate()源碼看下:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

在上面代碼中,利用Resources的getLayout()獲取一個XmlResourceParser對象parser 犹菇,再調(diào)用inflate(),

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }
                //這里判斷是否是merge標(biāo)簽
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(parser.getPositionDescription()
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

先遍歷屬性美浦,然后判斷是否是merge標(biāo)簽,如果是项栏,則利用rInflate解析布局浦辨。否則,createViewFromTag()創(chuàng)建一個臨時的temp沼沈;然后通過rInflateChildren()解析布局文件流酬,內(nèi)容添加到temp中,再利用root.addView()把temp添加進(jìn)來列另,root是最初調(diào)用LayoutInflater的inflate(layoutResID, mContentParent)傳進(jìn)來的mContentParent芽腾,這個時候就完成了添加。

4页衙、在第3步中摊滔,我們已經(jīng)把布局文件添加進(jìn)了content中阴绢,那么視圖是怎么顯示出來的呢?下面接著分析艰躺,在源碼中可以發(fā)現(xiàn)mContentParent是一個ViewGroup對象呻袭,即調(diào)用了ViewGroup的addView()方法:

public void addView(View child, int index, LayoutParams params) {
        if (DBG) {
            System.out.println(this + " addView");
        }

        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }

        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
        // therefore, we call requestLayout() on ourselves before, so that the child's request
        // will be blocked at our level
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }

在上面代碼中,requestLayout()重新請求布局腺兴,會從父View開始左电,invalidate()會讓View重繪,調(diào)用onDraw()方法页响,addViewInner()是把之前加載的View添加進(jìn)ViewGroup中篓足,并且當(dāng)前ViewGroup會被當(dāng)成一個parent,在View進(jìn)行繪制的時候闰蚕,調(diào)用parent遍歷其中的childView栈拖。不管是requestLayout()還是invalidate()最終都會調(diào)用ViewParent接口,而ViewRootImpl是ViewParent的實現(xiàn)類没陡,反過來說涩哟,也就是調(diào)用了ViewRootImpl的requestLayout()和invalidate()。

5诗鸭、在第4步中已經(jīng)找到了View的繪制入口了染簇,由于這部分內(nèi)容也比較多参滴,所以另寫了一篇文章【從源碼角度分析View的繪制流程】强岸。在前面我們分析完了View的加載過程,并沒有涉及到Window這個東西砾赔,只是把View添加進(jìn)了DecorView的content部分蝌箍。下面我們看一段Acitivity的啟動流程中的源碼【從源碼探索Activity的啟動過程】:

 final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
         //省略代碼.........
            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(); //1
                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;
                    // Normally the ViewRoot sets up callbacks with the Activity
                    // in addView->ViewRootImpl#setView. If we are instead reusing
                    // the decor view we have to notify the view root that the
                    // callbacks may have changed.
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);    //2
                    } else {
                        // The activity will get a callback for this {@link LayoutParams} change
                        // earlier. However, at that time the decor will not be set (this is set
                        // in this method), so no action will be taken. This call ensures the
                        // callback occurs with the decor set.
                        a.onWindowAttributesChanged(l);
                    }
                }  
    }


上面是Activity在onResume時執(zhí)行的代碼,注意下注釋1的部分暴心,ViewManager wm = a.getWindowManager();獲取一個wm對象妓盲,a.getWindowManager()實際返回的是WindowManagerImpl對象(WindowManager(繼承了ViewManager )的實現(xiàn)類),专普,在注釋2中悯衬,那么我們就能知道實際是調(diào)用了WindowManagerImpl的addView()方法。

6檀夹、經(jīng)過第5步分析筋粗,接下來看下WindowManagerImpl的addView()方法。

  @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

//WindowManagerGlobal類中的addView方法
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {    //通過window調(diào)整參數(shù)
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }

            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) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

根據(jù)上面代碼可以發(fā)現(xiàn)娜亿,任務(wù)轉(zhuǎn)移到了WindowManagerGlobal的addView方法中,內(nèi)部創(chuàng)建了一個ViewRootImpl對象root 蚌堵,然后調(diào)用root.setView()买决。同時也把WindowManager.LayoutParams傳到了ViewRootImpl中沛婴。

總結(jié): Activity中利用了WindowManager管理Window,而Window和View中間是通過ViewRootImpl建立關(guān)聯(lián)督赤。 流程分析到這里就結(jié)束了嘁灯,文章中并沒有深入旁仿,只是表層去探究了Activity枯冈、Window尘奏、View的關(guān)系炫加。深層次還涉及到了AMS俗孝、WMS等IPC機(jī)制赋铝。后面再另起一篇文章來分析。最后看一張圖析恋,對上面流程的簡述助隧。

未命名文件.png

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末并村,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蔫浆,更是在濱河造成了極大的恐慌瓦盛,老刑警劉巖原环,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡患膛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人愿卸,你說我怎么就攤上這事。” “怎么了波闹?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵歹篓,是天一觀的道長庄撮。 經(jīng)常有香客問我毡庆,道長,這世上最難降的妖魔是什么乖坠? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任甸昏,我火速辦了婚禮,結(jié)果婚禮上缸沃,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好哄芜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布锄奢。 她就那樣靜靜地躺著师坎,像睡著了一般蕊温。 火紅的嫁衣襯著肌膚如雪盟萨。 梳的紋絲不亂的頭發(fā)上制轰,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天丈屹,我揣著相機(jī)與錄音彩库,去河邊找鬼鞭达。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的梳玫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谦纱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起跳纳,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤斗塘,失蹤者是張志新(化名)和其女友劉穎茧吊,沒想到半個月后瞄桨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡唉工,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年橄唬,在試婚紗的時候發(fā)現(xiàn)自己被綠了犬庇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡宠漩,死狀恐怖雕崩,靈堂內(nèi)的尸體忽然破棺而出懦铺,到底是詐尸還是另有隱情急前,我是刑警寧澤澡刹,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜澎办,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一扶欣、第九天 我趴在偏房一處隱蔽的房頂上張望髓绽。 院中可真熱鬧来涨,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽射众。三九已至欣福,卻和暖如春责球,著一層夾襖步出監(jiān)牢的瞬間焦履,已是汗流浹背拓劝。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘉裤,地道東北人郑临。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像屑宠,于是被迫代替她去往敵國和親厢洞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

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