android ui 绘制流程

有时候看源码的时候,一进来每个Class 的代码都是几万行,进来就感觉真的出不去了。
所以,每次读源码的时候,我总带着点问题,进来探索
今天,再次从setContentView 到底做了些什么?为什么调用后可以显示出我们想要的布局?
来,带上潜水镜,跳进源码的大海,来看看setContentView 

当前的activity 都是继承AppCompatActivity 的,AppCompatActivity到底做了什么呢?点击进入setContentView

@Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID); //代理,代理了一些封装的兼容类
    }
//做的兼容
 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 if (Build.VERSION.SDK_INT >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);

@Override
    public void setContentView(int resId) {
        ensureSubDecor();//这里面做了一件偷梁换柱的事情,很暴力的做兼容,不过也很巧妙
      //获取了一个android.R.id.content 
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
      //移除contentParent中所有的布局
        contentParent.removeAllViews();
     //现在这个布局已经被替换到了
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();

    }

//我们来看看google是如何暴力的替换布局的?

private ViewGroup createSubDecor() {
  //获取自己要替换的布局
  final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
 //获取原来的android.R.id.content  布局
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            // There might be Views already added to the Window's content view so we need to
            // migrate them to our content view
            while (windowContentView.getChildCount() > 0) {//把之前布局的第一个布局添加到需要替换的布局中
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }
            // Change our content FrameLayout to use the android.R.id.content id.
            // Useful for fragments.
            windowContentView.setId(View.NO_ID);//这里是偷梁换柱,把之前ID叫android.R.id.content的,设置成NO_ID

        contentView.setId(android.R.id.content); //contentView设置成了ID为content
        // Now set the Window's content view with the decor

        mWindow.setContentView(subDecor);//把subDecor设置进mWindow

1.PhoneWindos 到底是什么东西? window 和它是什么关系?
先看看window
* <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */

public abstract class Window {}
它是一个抽象类,主要就是提供了一些绘制窗口的通用API
看上面的注释,我们明白:它只有一个实现类,那就是phoneWindow,来我们进入PhoneWindow,找到setContentView
 @Override
    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.
       //mContentParent你点击它,它是一个ViewGroup,一个容器
        if (mContentParent == null) {
       //这里是初始化一个DecorView
          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 {
         // 我们的布局是如何生成,加载到decorView 上来的?这个讲完流程,后面补充
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        //先看看installDecor()里面是如何实例化DecorView的,这个DecorView是干什么用?和我们的布局有什么关系?看源码
private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
        //实例化decorView,进去看看最后new DecorView(context, featureId, this, getAttributes());
            mDecor = generateDecor(-1);
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {//重要的是这里,这个容器为空的时候,generateLayout(传入了decorView).
            mContentParent = generateLayout(mDecor);
2.generateLayout你到底要干什么?来,进去看看
protected ViewGroup generateLayout(DecorView decor) {
         //获取各种styleable
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
//设置Feature属性
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            requestFeature(FEATURE_ACTION_BAR);
        }
        //到这里,我们可以明白,requestFeature() 为什么要在setContentView 之前调用呢,这就是答案,继续。
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
      //这个布局就是我们decorView 的布局了,进去里面一看  ViewStub (这是android 懒加载机制的,默认不显示)
      和 FrameLayout  (id为 content)
        layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
        mDecor.startChanging();
      //重点,加载decorView 的布局
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
      //同时去获取 这个叫content 的布局,返回给我们(注意最上面我们分析过AppCompatActivity 他把之前的布局删除了,

       然 后添加了我们自己的布局进去ID_ANDROID_CONTENT这个布局,就是我们的布局)

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view"
       //最后返回的是 contentParent 

3.好了到这里,我们明白了:

decorView 是最外层的容器,他有自己的布局R.layout.screen_simple,里面有个ID叫content 的,这个是用来放我们自己的activity 的,所以说他跟我们decorView 是什么关系呢?很明显了吧?

4.我们的XML是如果生成加载到我们的decorView 上来的?看PhoneWindow
@Override
    public void setContentView(int layoutResID) {

          mLayoutInflater.inflate(layoutResID, mContentParent);}

进入我们 inflate看一下,到底是怎么生成的?怎么加载的?
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
// root != null ==true   attachToRoot的意思是:是不是要加到我们的root 中,这里是true 
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 
//拿一个XML的parser解析器
     final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);

  进入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);//拿到一些attrs 属性
            Context lastContext = (Context) mConstructorArgs[0];

            mConstructorArgs[0] = inflaterContext;
            View result = root;  
           //找根节点
           while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
         //找到根节点之后,判断是不是TAG_MERGE标签
         if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
         //merge 必须添加到ViewGroup里面的,如果不是,这里会提示错误
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    rInflate(parser, root, inflaterContext, attrs, false);//注意:这里当它为merge 标签的时候传的是false
                } else {
         //如果不是merge 标签,首先它会生成一个跟节点,比如你的布局是LinearLayout,它就生成LinearLayout
          final View temp = createViewFromTag(root, name, inflaterContext, attrs);
             if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // 调用FrameLayout,的generateLayoutParams解析根节点下的属性,添加到Fraegment里面来
                        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);//把解析出来的属性设置给LinearLayout
                        }                

            // 解析完根布局之后,在解析里面的布局

                    rInflateChildren(parser, temp, attrs, true);//注意:当它不为merge标签的时候传了true

//进入rInflateChildren发现它也是调用了rInflate

void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            final String name = parser.getName();
            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {

                 if (parser.getDepth() == 0) { //当发现是include标签,depth ==0 就是没有东西,会抛出异常
                   //这里就可以知道一点结论:include是不能作为xml资源布局的根节点的
                    throw new InflateException("<include /> cannot be the root element");
                    }
                    parseInclude(parser, context, parent, attrs);
                   } else if (TAG_MERGE.equals(name)) { //当发现是merge 标签,会抛出异常
                    //这里就可以知道一点结论:merge 只能作为xml资源布局的根节点的
                        throw new InflateException("<merge /> must be the root element");
                     } else {

                finl View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);//一层层解析,这就是深度优先遍历
                viewGroup.addView(view, params);//添加到ViewGroup上面来
            }
        }
        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }
        if (finishInflate) {//如果它不是merge标签,添加回调
            parent.onFinishInflate();//这就是自定义加载完毕后回调方法
        }

    }

merge 标签的作用是:减少不必要的布局嵌套

小结:每一个Activity都有一个关联的Window对象,用来描述应用程序窗口。

每一个窗口内部又包含了一个DecorView对象,Decorview对象用来描述窗口的视图--xml布局

5.PhoneWindow 在哪里初始化了呢?

其实最后发现,它在activity 的

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
        mWindow = new PhoneWindow(this, window, activityConfigCallback);//这里

6.DecorView如何添加到Window?
这个要从程序入口activityThread类来讲起,程序启动的时候,会调用activityThread 的handleLaunchActivity 方法
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {

        Activity a = performLaunchActivity(r, customIntent);//这里面,activity 所有的生命周期方法调用

//执行完后
if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();//拿到Window

                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;
                        wm.addView(decor, l);
//这里把我们的decorView  添加到了我们的windowManager 里面
                    } else {
                        a.onWindowAttributesChanged(l);
                    }

                }

private Activity  performLaunchActivity(ActivityClientRecord r, Intent customIntent) 

                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); //调用activity的onCreate
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }

public void callActivityOnCreate(Activity activity, Bundle icicle,
            PersistableBundle persistentState) {
        prePerformCreate(activity);
        activity.performCreate(icicle, persistentState);//调用activity 的performCreate
        postPerformCreate(activity);
    }

final void performCreate(Bundle icicle) {
        restoreHasCurrentPermissionRequest(icicle);
        onCreate(icicle);//调用onCreate
        mActivityTransitionState.readState(icicle);
        performCreateCommon();
    }


if (a != null) {

            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward, //执行完后,在调用activity  的Resume 方法
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

来看看windowManager 是什么? 原来它是接口,他的实现类是windowManagerImpl,来到
windowManagerImpl的addView来看

@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);//调用的是WindowManagerGlobal 的addView ,看看里面做了什么

    } 

来到WindowManagerGlobal 的addView 来看
     public void addView(View view, ViewGroup.LayoutParams params,{
          //......
           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,ViewRootImpl这个到底是干什么的呢?

}

7.ViewRootImpl到底有什么用?ViewRootImpl它做了什么?

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

// 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();//这个熟悉吧?发起绘制流程

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,//发起了一个跨进程的消息,作用是把decroView 添加显示出来
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

                            mAttachInfo.mOutsets, mInputChannel);

view.assignParent(this);//最后还调用了一句重要的代码,这是干什么用的?  

所以我们的绘制流程设置view.requestLayout的时候,会不断地找Parent.requestLayout, 会找到我们的decorView.requestLayout ,最后调用ViewRootImpl的requestLayout();

为什么是这样子的,就是因为:上面这行神奇的代码view.assignParent(this)(它把自己设置成了我们的DecorView的Parent),

}

void assignParent(ViewParent parent) {//这个方法就是给我们的View设置我们的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");
        }
    }

来看看我们View  的requestLayout 方法

 @CallSuper
    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            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();//不断的调用mParent  的requestLayout
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }

    }

ViewRootImpl的requestLayout

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();// 遍历
        }

    }

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

    }

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

    }

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            performTraversals();//真正执行绘制的地方
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }

    }

private void performTraversals() {

//最终东西会画到我们的Surface 上面,代码就不贴出来了

 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
 performLayout(lp, mWidth, mHeight);

 performDraw();

都在里面调用了

}

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//最后调用了View的measure,View的 measure是个publiic 的方法
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

ONO,我弄完有点点晕,不过还是得整理一下的。

Ui 绘制流程
目前版本,我们的activity 都是继承的AppCompatActivity
为什么呢?因为我们google 工程师做了很多兼容的工作,
目前发现比较6的地方是:里面做了一件偷梁换柱的事情,如何偷梁换柱呢?
他准备了一个布局,然后把android.R.id.content下的布局改了名字,
把自己准备的布局改成了android.R.id.content,所以,我们最终会用这位大牛给的布局。
每一个Activity都有一个关联的Window对象,用来描述应用程序窗口。
window 它是一个抽象类,只要就是提供了一些绘制窗口的通用API
看里面的注释,我们明白,它只有一个实现类,那就是PhoneWindow 
每一个窗口内部又包含了一个DecorView对象,
Decorview对象用来描述窗口的视图--xml布局
Decorview中使用inflate 已经XmlPullParser 解析器 去找根节点
里面有很多判断,比如判断:是不是merge 标签,是不是include标签
如果不是merge 标签,它会生成一个根节点,然后调用generateLayoutParams
解析根节点下的属性,设置给生成的根节点,再添加到Fraegment
解析完根节点后,再解析根节点下面的布局,其实解析子布局也是调用了rinflate解析
就这样一层层解析,这就是传说中的:深度优先遍历
上面弄完之后,decorView就加载好了,下面把DecorView如何添加到Window
这里要从程序入口activityThread讲起,程序启动的时候调用会调用activityThread
 的handleLaunchActivity 方法然后里面有个performLaunchActivity,
 里面调用了一系列的activity 的生命周期方法。
 完后把decorView  添加到了我们的windowManager 里面。
 windowManager是个接口,它的实现类是 windowManagerImpl
 找到windowManagerImpl 的addView,它又调用了WindowManagerGlobal的addView 方法
原来最后调用的是ViewRootImpl.addView ,整个发起绘制流程的地方,原来是ViewRootImpl
里面调用了requestLayout,最隐蔽的代码是view.assignParent(this);
ViewRootImpl把自己设置成我们的Parent,所以在requestLayout的时候
不断的调用mParent.requestLayout,最后绘制的时候起了一个叫mTraversalRunnable 的Runnable 
不断的doTraversal,最后performTraversals();真正执行绘制的地方
里面调用了performMeasure,performLayout,performDraw,就是我们经常自定义View的时候常用方法



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值