View繪製源碼淺析(一)setContentView()和View顯示到Window

概述

View的繪製流程大致可以分爲兩大塊,一塊是setContentView()和View顯示在屏幕上這個整體流程的梳理,另外一塊是measure、layout、draw細節的實現,由於內容比較多所以我準備分兩篇博客講述。

那麼本篇先從整體入手,分析下setContentView()和View顯示到屏幕這個流程,掌握一個大體流程。測量、佈局、繪製的話將在第二篇介紹。

本文的源碼基於API27。

setContentView()

setContentView()見名知義,就是將我們的佈局設置到id爲content的佈局上並不包含View的繪製流程,接下來看下實現細節。

//MainActivity.java
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
    }
}

//AppCompatActivity.java
@Override
public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);
}

//AppCompatActivity.java
@NonNull
public AppCompatDelegate getDelegate() {
    if (mDelegate == null) {
        mDelegate = AppCompatDelegate.create(this, this);
    }
    return mDelegate;
}

//AppCompatDelegate.java
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
    return create(activity, activity.getWindow(), callback);//這裏需要注意他是把activity.getWindow()傳入到了後面,也就是PhoneWindow
}

//AppCompatDelegate.java
private static AppCompatDelegate create(Context context, Window window,
                                        AppCompatCallback callback) {
    if (Build.VERSION.SDK_INT >= 24) {
        return new AppCompatDelegateImplN(context, window, callback);
    } else if (Build.VERSION.SDK_INT >= 23) {
        return new AppCompatDelegateImplV23(context, window, callback);
    } else {
        return new AppCompatDelegateImplV14(context, window, callback);
    }
}

//AppCompatDelegateImplV9.java 
//最終是調到這個類的setContentView方法
@Override
public void setContentView(int resId) {
    ensureSubDecor();//確保DecorView相關佈局初始化完成
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);//找到id爲content的view
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);//通過LayoutInflater直接把我們的佈局添加到id爲content的佈局上
    mOriginalWindowCallback.onContentChanged();
}

主要的流程就是找到AppCompatDelegate的實現類,然後最終調到了AppCompatDelegateImplV9setContentView()方法,接下來分爲兩步。

  1. 通過ensureSubDecor()方法確保DecorView相關佈局初始化完成。
  2. 找到DecorView中id爲content的佈局,將我們自己的佈局inflater到content上。

這裏先說第一步ensureSubDecor()

//AppCompatDelegateImplV9.java
private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
        mSubDecor = createSubDecor();
        ...
    }
}

private ViewGroup createSubDecor() {
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
	//拿到各種Feature進行相應的標記處理
    if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);//這個標記熟悉吧no_title
    } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.
        requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
    }
    if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
        requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
    }
    if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
        requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
    }
    mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
    a.recycle();

    // Now let's make sure that the Window has installed its decor by retrieving it
    mWindow.getDecorView();//確保decorview創建了
    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;
    //省略了很多條件判斷,其實是根據各種Feature條件創建不同的subDecor
    subDecor = (ViewGroup) inflater.inflate(R.layout.xxx, null);
    // 將subDecor設置到Decorview上
    mWindow.setContentView(subDecor);
    return subDecor;
}

這裏先拿到各種Feature其中有個我們比較熟悉的FEATURE_NO_TITLE。平時調用requestWindowFeature()的時候我們都是在setContentView()前面,看到這裏你也應該知道爲啥了,因爲setContentView()方法中會處理相關邏輯,所以需要在他之前調用。

這裏的mWindow是之前通過activity.getWindow()方法傳進來的,具體實現類是PhoneWindow,調用getDecorView()確保DecorView初始化了,然後根據各種Feature條件創建不同的subDecor通過setContentView()方法添加到DecorView上。

接下來第二步inflate我們的佈局到id位content的view上。

    //LayoutInflater.java
    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 XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
           	...
            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 (TAG_MERGE.equals(name)) {//merge單獨處理
                    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);//創建xml中根佈局
                    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);//根據父佈局創建xml中根佈局的lp,因爲自己的lp是跟父佈局有關的。
                        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);//inflate temp的子佈局,具體實現就是遞歸的把view添加到temp上

                    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;
        }
    }


inflate比較簡單,根據xml解析拿到xml中根佈局temp,然後通過root.generateLayoutParams(attrs)創建lp,之所以要這樣是因爲每個view的lp的創建都是跟父佈局有關的,比如root是LinearLayout那麼創建的就是LinearLayout.LayoutParams並初始化weightgravity屬性,而如果root是RelativeLayout那麼創建的是RelativeLayout.LayoutParams並初始化跟各個佈局的約束關係。接下來再通過rInflateChildren(parser, temp, attrs, true)將temp的子佈局inflate到temp上,最後我們把temp添加到root上。

這裏我們可以看下LinearLayout.generateLayoutParams()方法

	//LinearLayout.java
	@Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LinearLayout.LayoutParams(getContext(), attrs);
    }

	//LinearLayout.LayoutParams 內部類
    public LayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);//這裏還調用了父類MarginLayoutParams的構造方
        TypedArray a =
            c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);

        weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);//獲取xml中weight屬性
        gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);//獲取layout_gravity屬性

        a.recycle();
    }

    //ViewGroup.MarginLayoutParams 內部類 初始化margin屬性
    public MarginLayoutParams(Context c, AttributeSet attrs) {
        super();

        TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
        setBaseAttributes(a,
                          R.styleable.ViewGroup_MarginLayout_layout_width,
                          R.styleable.ViewGroup_MarginLayout_layout_height);//調用父類ViewGroup.LayoutParams的setBaseAttributes()讀取最基礎的寬高屬性
        //下面就是讀取xml各種margin屬性
        int margin = a.getDimensionPixelSize(
            com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
        if (margin >= 0) {
            leftMargin = margin;
            topMargin = margin;
            rightMargin= margin;
            bottomMargin = margin;
        } else {
            int horizontalMargin = a.getDimensionPixelSize(
                R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
            int verticalMargin = a.getDimensionPixelSize(
                R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1);

            if (horizontalMargin >= 0) {
                leftMargin = horizontalMargin;
                rightMargin = horizontalMargin;
            } else {
                leftMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
                    UNDEFINED_MARGIN);
                if (leftMargin == UNDEFINED_MARGIN) {
                    mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
                    leftMargin = DEFAULT_MARGIN_RESOLVED;
                }
                rightMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginRight,
                    UNDEFINED_MARGIN);
                if (rightMargin == UNDEFINED_MARGIN) {
                    mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
                    rightMargin = DEFAULT_MARGIN_RESOLVED;
                }
            }

            startMargin = a.getDimensionPixelSize(
                R.styleable.ViewGroup_MarginLayout_layout_marginStart,
                DEFAULT_MARGIN_RELATIVE);
            endMargin = a.getDimensionPixelSize(
                R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
                DEFAULT_MARGIN_RELATIVE);

            if (verticalMargin >= 0) {
                topMargin = verticalMargin;
                bottomMargin = verticalMargin;
            } else {
                topMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginTop,
                    DEFAULT_MARGIN_RESOLVED);
                bottomMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
                    DEFAULT_MARGIN_RESOLVED);
            }

            if (isMarginRelative()) {
                mMarginFlags |= NEED_RESOLUTION_MASK;
            }
        }

        final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
        final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
        if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
            mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
        }

        // Layout direction is LTR by default
        mMarginFlags |= LAYOUT_DIRECTION_LTR;

        a.recycle();
    }

	//ViewGroup.LayoutParams 內部類
    protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {//讀取寬高
        width = a.getLayoutDimension(widthAttr, "layout_width");
        height = a.getLayoutDimension(heightAttr, "layout_height");
    }

		

可以看到LinearLayout.LayoutParams有兩層繼承關係,LinearLayout.LayoutParams負責拿到LinearLayout的特有屬性,ViewGroup.MarginLayoutParams負責拿到margin屬性,ViewGroup.LayoutParams負責拿到寬高。一般的ViewGroup基本都是實現自己的lp然後繼承ViewGroup.MarginLayoutParams

那麼對activity.setContentView()總結成一句話就是完成DecorView相關佈局初始化,然後將我們的佈局inflater到DecorView中id爲content的ViewGroup上並初始化好對應的LayoutParams,到此佈局的添加和xml中相關屬性的初始化完成,接下來在看他是如何顯示到屏幕上的。

View顯示到屏幕上

View的顯示是Activity收到Resume事件以後,這個事件其實是AMS發送給客戶端的,在收到後會依次執行測量、佈局、繪製流程,並將PhoneWindow添加到屏幕上。

不過再說Resume事件前,我們先看下是如何監聽的。是在ActivityThread.attach()方法中添加的。

final ApplicationThread mAppThread = new ApplicationThread();
private void attach(boolean system) {
    ···
    sCurrentActivityThread = this;
    mSystemThread = system;
    final IActivityManager mgr = ActivityManager.getService();//拿到ams
    try {
        mgr.attachApplication(mAppThread);//將mAppThread設置給了ams 類似於setOnClickListener設置監聽
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
    ···
}

mAppThread其實就是個binder對象,通過ActivityManager.getService()拿到系統進程的AMS然後調用attachApplication()把客戶端的監聽mAppThread設置給他。

這裏我們看下ApplicationThread這個接受AMS事件的監聽

private class ApplicationThread extends IApplicationThread.Stub {
    ...//省略多個schedulexxxActivity()方法    
    public final void scheduleResumeActivity(IBinder token, int processState,
                                             boolean isForward, Bundle resumeArgs) {//收到AMS的Resume事件
        int seq = getLifecycleSeq();
        if (DEBUG_ORDER) Slog.d(TAG, "resumeActivity " + ActivityThread.this
                                + " operation received seq: " + seq);
        updateProcessState(processState, false);
        sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0, 0, seq);//發送事件
    }
    
    private void sendMessage(int what, Object obj, int arg1, int arg2, int seq) {
        if (DEBUG_MESSAGES) Slog.v(
            TAG, "SCHEDULE " + mH.codeToString(what) + " arg1=" + arg1 + " arg2=" + arg2 +
            "seq= " + seq);
        Message msg = Message.obtain();
        msg.what = what;
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = obj;
        args.argi1 = arg1;
        args.argi2 = arg2;
        args.argi3 = seq;
        msg.obj = args;
        mH.sendMessage(msg);//發送到主線程Handler
    }
    ...  
    
}

是有個scheduleResumeActivity()方法接受Resume事件,然後由於是進程間通信所以該方法運行在binder線程池上,因此通過sendMessage()到主線程Handler執行相應邏輯。

private class H extends Handler {
    ...
    case RESUME_ACTIVITY:
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
    SomeArgs args = (SomeArgs) msg.obj;
    handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
                         args.argi3, "RESUME_ACTIVITY");//真正處理Resume事件的方法
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    break;
    ...
}

所以Resume事件實際上調到了ActivityThread.handleResumeActivity()方法

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ···
        ActivityClientRecord r = mActivities.get(token);
        r = performResumeActivity(token, clearHide, reason);//執行activity的onResume方法
        if (r != null) {
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);//將decorview添加到wm 記住哦後面的view在這個流程中都是DecorView
                    }
                }
            }
        }
        ···
    }

最終是調到WindowManagerGlobal.addView()

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

        synchronized (mLock) {
            // 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);//創建ViewRootImpl

            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);//調用ViewRootImpl.setView()傳入DecorView
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

然後創建了一個ViewRootImpl調到ViewRootImpl.setView()

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();//進行measure、layout、draw
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);//將view添加到屏幕上顯示
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
                view.assignParent(this);//給decorView添加父對象ViewRootImpl
            }   
        }
    }

先調用了requestLayout()對佈局進行測量、佈局、繪製,然後通過mWindowSession.addToDisplay()將View顯示到window上完成顯示。

這裏我們看下requestLayout()

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();//檢查是不是主線程
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//運行mTraversalRunnable
        }
    }

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();//真正的traversals方法

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

最終走到了performTraversals()

private void performTraversals() {
    measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight)//實際內部也是performMeasure()
    performLayout(lp, mWidth, mHeight);
    performDraw();
}

    private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
                                     final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        int childWidthMeasureSpec;
        int childHeightMeasureSpec;
        childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
        childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        return windowSizeMayChange;
    }


看到這裏可以發現其實requestLayout()實際上是分別調用了performMeasure()performLayout()performDraw()去執行的測量、佈局、繪製,具體實現細節將會在第二篇分析。

這裏我們總結下view顯示到屏幕上的調用鏈

ActivityThread.handleResumeActivity()->WindowManager.addView()->WindowManagerImpl.addView()->WindowManagerGlobal.addView()->ViewRootImpl.setView()->IWindowSession.addToDisplay()

ViewRootImpl.setView()中完成了View的測量、佈局、繪製過程,然後通過IWindowSession.addToDisplay()使VIew顯示出來。

總結

梳理下整體流程

  1. setContentView完成DecorView相關佈局初始化並將我們的佈局通過LayoutInflater.inflate()方法添加到id爲Content的ViewGroup上。
  2. 在收到AMS的Resume事件,最終調到ViewRootImpl.setView()當中通過ViewRootImpl.requestLayout()完成View的測量、佈局、繪製,然後在通過IWindowSession.addToDisplay()顯示到屏幕上。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章