美文网首页
源码分析->布局如何显示出来

源码分析->布局如何显示出来

作者: 杨0612 | 来源:发表于2020-07-17 17:22 被阅读0次

    源码分析基于Android-23

    1.基本知识

    (1)图中黄色部分为Activity,绿色为Window,浅蓝色为DecorView;
    (2) 一个Activity对应一个Window,Window的唯一实现是PhoneWindow
    (3) 一个Window对应一个DecorView;
    (4)DecorView其实是一个FrameLayout,包含一个id为ID_ANDROID_CONTENT的控件;
    (5)我们通过setContentView设置的布局文件,是加载到ID_ANDROID_CONTENT当中的。


    关系图
    2.源码分析
    Activity.setContentView的发起流程如下:

    ActivityStackSupervisor.realStartActivityLocked->ApplicationThread.scheduleLaunchActivity->ActivityThread.handleLaunchActivity->Activity.setContentView,ActivityStackSupervisor是真正的发起者,
    我们重点关注:

    Step 1:Activity.setContentView

    主要工作:
    (1)getWindow()获取Activity的成员变量mWindow,执行
    mWindow.setContentView;
    (2)mWindow是在Activity.attch方法中构建的,具体看ActivityThread.performLaunchActivity;

        public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
    
    Step 2:Window.setContentView

    主要工作:
    (1)installDecor,主要构建DecorView以及mContentParent,我们一会跟进这个方法看看;
    (2)mLayoutInflater.inflate(layoutResID, mContentParent),将布局layoutResID添加到mContentParent中,mContentParent作为布局的父控件,而它自己是DecorView中的子控件;

     @Override
        public void setContentView(View view, ViewGroup.LayoutParams params) {
          ......
          installDecor();
          ......
           mLayoutInflater.inflate(layoutResID, mContentParent);
          ......
        }
    
    Step 3:Window.installDecor

    主要的工作:
    (1)generateDecor(-1),方法比较简单,就是构建DecorView;
    (2)generateLayout(mDecor),主要是根据配置属性来构建DecorView布局,内部contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT),这是系统定义的布局,并把它赋值给mContentParent ,有兴趣的同学可以跟进去看看,这里就不展开了;

    Tips:为什么一定要在setContentView之前调用requestWindowFeature呢?因为在setContentView之前,generateLayout()已经把Window属性都已经确定了;
    private void installDecor() {
             ......
            if (mDecor == null) {
                mDecor = generateDecor(-1);
              ......
            } 
             ......
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor);
            ......
       }
    

    我们可以看到,setContentView工作只是把要显示的内容准备好,还没有涉及到绘制。
    ActivityThread.handleLaunchActivity执行完以后紧接着就会执行ActivityThread.handleResumeActivity,这里面涉及发起绘制任务;
    我们重点关注:

    Step 4:ActivityThread.handleResumeActivity

    主要工作:
    (1)unscheduleGcIdler,从MessageQueue中移除GC任务,因为绘制任务准备要开始了;
    (2)performResumeActivity,会调用Activity的onResume方法;
    (3)wm.addView,通知WindowManager 添加DecorView,wm是WindowManageImpl类型。

     @Override
        public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
                String reason) {
                unscheduleGcIdler();
               ......       
              ActivityClientRecord r = performResumeActivity(token, clearHide);
             ......       
             if (a.mVisibleFromClient) {
                  a.mWindowAdded = true;
                   wm.addView(decor, l);
               }
               ......       
        }
    
    Step 5:WindowManageImpl.addView

    主要工作:
    调用mGlobal.addView,mGlobal是WindowManagerGlobal类型。WindowManagerGlobal是进程单例。

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

    主要工作:
    (1)new ViewRootImpl,构建ViewRootImpl,ViewRootImpl的作用很关键,负责管理渲染工作view树、负责连接view和window之间的联系、负责和WMS联系,一个window都会对应一个ViewRootImpl。
    (2)调用ViewRootImpl.setView方法,

     public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow) {
             ......
            ViewRootImpl root;.
             ......
             root = new ViewRootImpl(view.getContext(), display);
             ......
              root.setView(view, wparams, panelParentView);
              ......
        }
    
    Step 8:ViewRootImpl.setView

    主要工作:
    (1)触发requestLayout,这里并没有开始绘制,只是post了一个任务;
    (2)mWindowSession.addToDisplay,mWindowSession是Session类型,通过它通知WMS完成Window的添加,这里是完成窗口的添加,不是绘制,添加以后WMS就可以管理Window了,一个应用对应一个mWindowSession;
    (3)参数mWindow是W类型,不是PhoneWindow类型,它作为WMS与客户端通讯的通道,当WMS有事件需要通知Window,就是通过它来完成;
    (4)注意setView第一个参数就是DecorView;

    Tips:客户端发消息给WMS,ViewRootImpl->Session->WMS;
    Tips:WMS发消息给客户端,WMS->W->ViewRootImpl->DecorView;
     public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
            synchronized (this) {
                ......            
                requestLayout();
                ......
                try {
                     ......                  
                     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(),
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mInputChannel);
              ......
        }
    
    Step 9:ViewRootImpl.requestLayout

    主要工作:
    (1)checkThread,判断当前线程是否是创建ViewRootImpl的线程,也就是判断是否在主线程,否则就抛出大名鼎鼎的
    CalledFromWrongThreadException(
    "Only the original thread that created a view hierarchy can touch its views.")异常;

    Tips:面试可能会被问到,能不能在子线程更新UI?理论上是可以,让ViewRootImpl也在子线程创建,这样checkThread判断就会通过;

    (2)scheduleTraversals,发送一个SyncBarrier消息屏障(同步屏障),让渲染任务优先,以及把绘制任务放入任务队列,等到VSync信号触发;

    @Override
        public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                checkThread();
                mLayoutRequested = true;
                scheduleTraversals();
            }
        }
    
    Step 10:ViewRootImpl.scheduleTraversals

    主要工作:
    (1)mTraversalScheduled标志位防止多次requestLayout,只有在上一帧绘制完成了,下一帧才可以触发;
    (2)postSyncBarrier,发送一个消息屏障(同步屏障),让渲染任务优先,以及把绘制任务放入任务队列,等到VSync信号触发;mTraversalRunnable是消息触发的回调任务。

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

    如何接收VSync信号,可以参考https://www.jianshu.com/p/c1f6e1181e8e

    当VSync信号到来,mTraversalRunnable任务会被执行,它的run方法就会被触发
    Tips:TraversalRunnable是ViewRootImpl的内部类
    Step 11:TraversalRunnable.run

    主要是执行doTraversal方法;

        final class TraversalRunnable implements Runnable {
            @Override
            public void run() {
                doTraversal();
            }
        }
    
    Step 12:ViewRootImpl.doTraversal

    主要工作:
    (1)mTraversalScheduled 标志改为false,可以发送下一帧的绘制任务;
    (2)mTraversalScheduled ,移除消息屏障,让普通消息可以被执行;
    (3)触发performTraversals方法;

       void doTraversal() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;           
     mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
                ......
                performTraversals();
                ......
            }
        }
    
    Step 12:ViewRootImpl.performTraversals

    主要工作:
    开始DecorView开始measure、layout、draw,分别回调onMeasure,onLayout,onDraw

     private void performTraversals() {
            ......
             windowSizeMayChange |= measureHierarchy(host, lp, res,
                        desiredWindowWidth, desiredWindowHeight);
             ......
              performMeasure
              ......
              performLayout(lp, mWidth, mHeight);
              ......
              performDraw();
              ......
    }
    

    至此自定义的布局就会显示在屏幕上。

    总结作用:

    (1)setContentView,只是构造DecorView、初始化DecorView以及把自定义布局添加到mContentParent当中,绘制还没有开始;
    (2)handleResumeActivity,发送消息屏障,把绘制任务放入任务队列,等到VSync信号触发开始绘制。
    (3)Activity负责生命周期的控制以及事件处理;ViewRootImpl在WindowManager以及DecorView中起纽带作用,Activity是控制单元,Window是载体,View是内容;
    (4)PhoneWindow是一个抽象的存在(载体),是以view的形式存在的(真实存在),管理的是一组view,例如Activity的布局,Activity、Dialog、Toast都是依赖PhoneWindow的;

    (5)建议结合https://www.jianshu.com/p/c1f6e1181e8e
    ,才能更好的理解布局是如何显示出来的。

    以上分析有不对的地方,请指出,互相学习,谢谢哦!

    相关文章

      网友评论

          本文标题:源码分析->布局如何显示出来

          本文链接:https://www.haomeiwen.com/subject/ehtetktx.html