从源码角度分析View的加载过程

 

本文是依据Android 8.1.0的相关源码进行分析。

一、setContentView的如何加载布局?

通常Activity的onCreate里面都需要写上setContentView,那么就从Activity入手,找到setContentView,如下:

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

从上面可以看出,调用的是Window的setContentView方法,这个Window是一个抽象类,他是子在attach方法中被创建的:

mWindow = new PhoneWindow(this, window, activityConfigCallback);

可以看出,PhoneWindow是Window的实现类,这里可以明确出他是Window唯一实现类,找到实现类的setContentView方法:

    @Override
    public void setContentView(int layoutResID) {

        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 {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

如果mContentParent是否为空,则执行installDecor,移除了一些不重要的方法:

    private void installDecor() {
        //创建DecorView,将DecorView添加至Window
        if (mDecor == null) {
            mDecor = generateDecor(-1);
        } else {
            mDecor.setWindow(this);
        }
        //创建mContentParent,并且添加至DecorView
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
        }
    }

创建DecorView:

    protected DecorView generateDecor(int featureId) {
        return new DecorView(context, featureId, this, getAttributes());
    }

这里需要知道的是DecorView继承自FrameLayout布局。

创建ContentParent,代码很长,只保留核心内容:

     protected ViewGroup generateLayout(DecorView decor) {
        //1.获取layoutparams
        WindowManager.LayoutParams params = getAttributes();
        //2.加载根布局
        if (根据设置主题加载对应的xml,很多逻辑判断) {
            layoutResource = R.layout.screen_simple;
        }
        //3.设置DecorView变化监听
        mDecor.startChanging();
        //4.将根布局加载到DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        //5.在Window中找到id ID_ANDROID_CONTENT,即下面的FrameLayout
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

        return contentParent;
     }

在上面第二步中加载的根布局如下,其中FrameLayout就是设置setContentView的根布局,后面会提到,ViewStub是与actionbar相关属性。

     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:fitsSystemWindows="true"
         android:orientation="vertical">
         <ViewStub android:id="@+id/action_mode_bar_stub"
                   android:inflatedId="@+id/action_mode_bar"
                   android:layout="@layout/action_mode_bar"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
                   android:theme="?attr/actionBarTheme" />
         <FrameLayout
              android:id="@android:id/content"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:foregroundInsidePadding="false"
              android:foregroundGravity="fill_horizontal|top"
              android:foreground="?android:attr/windowContentOverlay" />
     </LinearLayout>

在上面的WindowPhone中有段如下代码:

mLayoutInflater.inflate(layoutResID, mContentParent);

layoutResID就是setContentView的xml布局mContentParent就是上面创建的contentParent,这行代码就是将xml父布局FrameLayout中。

总结下:

为了验证上面的情况新建一个项目打开Android Studio 新建一个项目,XML页面如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.demo.myapplication.MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:text="啦啦啦"/>

</RelativeLayout>

这是运行项目,打开Android Device Monitor——>hierarchy viewer:

这时候我们可以更新清晰直观的了看到整个页面View的结构图:

二、DecorView如何添加View?

DecorView是通过Window的setContentView的mDecor.setWindow(this);添加至Window中,布局的细节后面在讲。这里主要说的是上面的布局文件(R.layout.screen_simple)如何加到DecorView中。在上面的generateLayout方法中第四个备注的onResourcesLoaded方法就是加载的View布局,在DecorView找到对应的方法:

    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {

         mDecorCaptionView = createDecorCaptionView(inflater);
         final View root = inflater.inflate(layoutResource, null);
         if (mDecorCaptionView != null) {

         } else {
             //直接添加到DecorView中
             addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
         }
         mContentRoot = (ViewGroup) root;
         initializeElevation();
     }

使用addView直接DecorView中,DecorView继承自FrameLayout,addView是ViewGroup中的方法,如下核心代码:

    public void addView(View child, int index, LayoutParams params) {
        //请求View重新绘制
        requestLayout();
        //刷新UI
        invalidate(true);
        //添加View动画和监听相关
        addViewInner(child, index, params, false);
    }

addView的详细部分下面再说明,现在对上面一、二部分进行一个大总结:

三、DecorView如何显示View?

这里按照上面的三步进行分析。

1、请求View重新绘制(requestLayout)

    public void requestLayout() {

        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            //获取ViewRootImpl,该对象是在内部类AttachInfo中被实例化
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

    这里判断当前View是否正在进行布局流程,并且设置标志位,该标志位用于标记当前的View是否需要重新布局,接着调用 mParent.requestLayout();方法,进行该View的父布局的布局请求,层层递归,直到最顶层的DecorView,而DecorView又会把事件传递给ViewRootImpl,这是一种责任链设计模式,即不断向上传递该事件,直到找到能处理该事件的上级,这里的最终上级就是ViewRootImpl。DecorView和ViewRootImpl的关系是ViewRootImpl是实现View的绘制的类,里面有三个方法是关键,DecorView 是一个顶层 View,是应用的整个界面。ViewRootImpl中找到对应的方法:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //检查是否在主线程
            checkThread();
            //设置标识位
            mLayoutRequested = true;
            //该方法如下
            scheduleTraversals();
        }
    }

在这里,调用了scheduleTraversals方法,这个方法是一个异步方法,最终会调用到ViewRootImpl的performTraversals方法,这也是View工作流程的核心方法,在这个方法内部,分别调用measure、layout、draw来进行View的三大工作流程。详情如下:

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

这里重点关注mTraversalRunnable这个类,他implements Runnable这个类,执行doTraversal()方法,在该方法内部有调用了performTraversals()方法:

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

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

            performTraversals();

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

performTraversals代码十分长,核心方法如下:

    private void performTraversals() {
        performMeasure() ——> mView.measure
        performLayout() ——> mView.layout
        performDraw() ——> draw ——> drawSoftware ——> mView.draw ——> mView.draw(canvas);
    }

如下图:

总结:

requestLayout的流程如下:

requestLayout ——> scheduleTraversals ——> mTraversalRunnable ——> doTraversal ——> performTraversals ——> performMeasure ——> performLayout ——> performDraw

2、刷新UI(invalidate)

    public void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }

invalidateInternal核心代码如下,删除了其余代码:

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
                boolean fullInvalidate) {
                final ViewParent p = mParent;
                if (p != null && ai != null && l < r && t < b) {
                    //向父View进行进行刷新
                    p.invalidateChild(this, damage);
                }
            }
    }

由此可知,View的invalidate方法实质是将要刷新区域直接传递给了【父ViewGroup的invalidateChild方法】,这是一个从当前View向上级父View回溯的过程。找到ViewGroup的invalidateChild方法:

    public final void invalidateChild(View child, final Rect dirty) {
        ViewParent parent = this;
        do {
            //循环层层上级调用,直到ViewRootImpl会返回null
            parent = parent.invalidateChildInParent(location, dirty);
        } while (parent != null);
    }

这里会层层循环,直到parent为null就会跳出该循环,什么情况下为null呢?遍历到最顶层ViewGroup即ViewRootImpl,他最终返回null,如下:

    @Override
    public void invalidateChild(View child, Rect dirty) {
        invalidateChildInParent(null, dirty);
    }
    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    	if (dirty == null) {
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }
        //内部会调用scheduleTraversals();
        invalidateRectOnScreen(dirty);

    	return null;
    }

invalidateRectOnScreen内部会调用scheduleTraversals(),最终调用performTraversals执行重绘,上面分析有。所以,View调用invalidate方法的实质是:层层上传到父级,直到传递到ViewRootImpl后会触发scheduleTraversals方法,然后整个View树就开始重新按照View的绘制流程进行重绘任务。在invalidate的过程中,如果View的大小没有发生改变,就不会执行Draw方法,如果是在子线程中请求绘制刷新UI需要使用postInvalidate方法。

3、添加View相关属性(addViewInner)

addViewInner是addView的最后一步,主要设置了一些回调和动画、焦点、监听等。

     private void addViewInner(View child, int index, LayoutParams params,
                boolean preventRequestLayout) {
          //将View添加到数组中,获取mChildrenCount
          addInArray(child, index);
          //将View添加到Window上
          child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
          //当有View被添加进来时候,会回调onViewAdded方法,可用于View添加监听
          dispatchViewAdded(child);
     }

这样经过以上三步就玩成了View的添加显示,整个加载显示过程流程如下:

 

下一篇将分析View具体的绘制过程,也就是performTraversals的具体流程。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值