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

2020-07-17  本文已影响0人  杨0612

源码分析基于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
,才能更好的理解布局是如何显示出来的。

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

上一篇下一篇

猜你喜欢

热点阅读