前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android源码解读-View绘制流程之setContentView

Android源码解读-View绘制流程之setContentView

原创
作者头像
笔头
修改2022-01-11 16:37:47
6512
修改2022-01-11 16:37:47
举报
文章被收录于专栏:Android记忆

1.直接看下setContentVIew源码

代码语言:javascript
复制
activity.java
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);//
    initWindowDecorActionBar();
}

1.在activity.java源码中我们可以看到 mWindow = new PhoneWindow(this, window, activityConfigCallback);所以我们直接看PhoneWindow源码中的setContentView。

代码语言:javascript
复制
PhoneWindow.java
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.
    //-------------------------------------1
    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 {
    //------------------------------------2
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

1.首次setContentView会创建DecorView。

2.调用mLayoutInflater.inflate,把resId文件映射成各种view。

代码语言:javascript
复制
LayoutInflater.java
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 {
            advanceToRootNode(parser);
            final String name = parser.getName();

            if (DEBUG) {
                System.out.println("**************************");
                System.out.println("Creating root view: "
                        + name);
                System.out.println("**************************");
            }

            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.
                //----------------------------------------------------1
                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.
                //----------------------------------------------------2
                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(
                    getParserStateDescription(inflaterContext, attrs)
                    + ": " + 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;
    }
}

1.不断遍历xml节点,创建对应的view

2.这里的root就是decorView,root.addview(),实际是root的父类frameLayout的父类viewGroup来具体实现。

代码语言:javascript
复制
ViewGroup.java
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
    //---------------------------------------1
    requestLayout(); 
    invalidate(true);
    addViewInner(child, index, params, false);//-----------------------------------2
}

1.在addViewInner方向前调用requestLayout,invalidate不太了解。官方解释也一时不理解。

2.addViewInner 主要是把子view添加到childViews数组中。

代码语言:javascript
复制
private void addViewInner(View child, int index, LayoutParams params,
        boolean preventRequestLayout) {

    if (mTransition != null) {
        // Don't prevent other add transitions from completing, but cancel remove
        // transitions to let them complete the process before we add to the container
        mTransition.cancel(LayoutTransition.DISAPPEARING);
    }

    if (child.getParent() != null) {
        throw new IllegalStateException("The specified child already has a parent. " +
                "You must call removeView() on the child's parent first.");
    }

    if (mTransition != null) {
        mTransition.addChild(this, child);
    }

    if (!checkLayoutParams(params)) {
        params = generateLayoutParams(params);
    }

    if (preventRequestLayout) {
        child.mLayoutParams = params;
    } else {
        child.setLayoutParams(params);
    }

    if (index < 0) {
        index = mChildrenCount;
    }

    addInArray(child, index);

    // tell our children
    if (preventRequestLayout) {
        child.assignParent(this);
    } else {
        child.mParent = this;
    }
    if (child.hasUnhandledKeyListener()) {
        incrementChildUnhandledKeyListeners();
    }

    final boolean childHasFocus = child.hasFocus();
    if (childHasFocus) {
        requestChildFocus(child, child.findFocus());
    }

    AttachInfo ai = mAttachInfo;
    if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
        boolean lastKeepOn = ai.mKeepScreenOn;
        ai.mKeepScreenOn = false;
        child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
        if (ai.mKeepScreenOn) {
            needGlobalAttributesUpdate(true);
        }
        ai.mKeepScreenOn = lastKeepOn;
    }

    if (child.isLayoutDirectionInherited()) {
        child.resetRtlProperties();
    }

    dispatchViewAdded(child);

    if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
        mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
    }

    if (child.hasTransientState()) {
        childHasTransientStateChanged(child, true);
    }

    if (child.getVisibility() != View.GONE) {
        notifySubtreeAccessibilityStateChangedIfNeeded();
    }

    if (mTransientIndices != null) {
        final int transientCount = mTransientIndices.size();
        for (int i = 0; i < transientCount; ++i) {
            final int oldIndex = mTransientIndices.get(i);
            if (index <= oldIndex) {
                mTransientIndices.set(i, oldIndex + 1);
            }
        }
    }

    if (mCurrentDragStartEvent != null && child.getVisibility() == VISIBLE) {
        notifyChildOfDragStart(child);
    }

    if (child.hasDefaultFocus()) {
        // When adding a child that contains default focus, either during inflation or while
        // manually assembling the hierarchy, update the ancestor default-focus chain.
        setDefaultFocus(child);
    }

    touchAccessibilityNodeProviderIfNeeded(child);
}

重点看下addInArray,主要看index,如果index等于childView长度,则追加在后面,也就是该view显示在最上层。如果index小于数组长度,则插入其中index位置。

代码语言:javascript
复制
private void addInArray(View child, int index) {
    View[] children = mChildren;
    final int count = mChildrenCount;
    final int size = children.length;
    if (index == count) {
        if (size == count) {
            mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
            System.arraycopy(children, 0, mChildren, 0, size);
            children = mChildren;
        }
        children[mChildrenCount++] = child;
    } else if (index < count) {
        if (size == count) {
            mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
            System.arraycopy(children, 0, mChildren, 0, index);
            System.arraycopy(children, index, mChildren, index + 1, count - index);
            children = mChildren;
        } else {
            System.arraycopy(children, index, children, index + 1, count - index);
        }
        children[index] = child;
        mChildrenCount++;
        if (mLastTouchDownIndex >= index) {
            mLastTouchDownIndex++;
        }
    } else {
        throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
    }
}

到目前为止,处理OnCreated阶段,我们得到了DecorView以及childviews子view数组。等到OnResume阶段后,会走到ActivityThread的handleResumeActivity方法中,调用makeVisible方法

代码语言:javascript
复制
void makeVisible() {
  //1. 将`DecorView`添加到`Window`中(通过WindowManager)
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
  //2. 将DecorView显示出来
    mDecor.setVisibility(View.VISIBLE);
}

ViewManager实现者WindowManager addView()内部实际是由WindowManagerGlobal完成的。

代码语言:javascript
复制
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
            mContext.getUserId());
}

继续

代码语言:javascript
复制
WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
    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");
    }
    ..........................
    root = new ViewRootImpl(view.getContext(), display);
    ....................
    ....................
    // do this last because it fires off messages to start doing things
    try {
         root.setView(view, wparams, panelParentView, userId);
    } catch (RuntimeException e) {
       // BadTokenException or InvalidDisplayException, clean up. 
    }
}

对后调用了ViewRootImpl setView方法

代码语言:javascript
复制
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
            mAdded = true;
            int res; /* = WindowManagerImpl.ADD_OKAY; */

            // 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();
            
            ..............
            InputChannel inputChannel = null;
            if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                inputChannel = new InputChannel();
            }
   
}

setView方法很长,她实际调用了requestLayout()方法。

代码语言:javascript
复制
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
代码语言:javascript
复制
@UnsupportedAppUsage
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}
代码语言:javascript
复制
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
代码语言:javascript
复制
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

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

        performTraversals();

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

到了我们熟悉的performTraversals方法,接下来就是 measure、layout、draw这三大流程就不详细讲,这三个流程中会把childView数组中每个子view遍历放在DecorView中,呈现出屏幕上。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档