来聊聊Activity的显示原理

355 阅读8分钟
原文链接: mp.weixin.qq.com

作者:Raina链接:https://juejin.cn/post/6844904086265937934已授权,著作权归作者所有。

我们本文要回答的问题:

  • setContentView原理是什么?

  • Activity在onResume之后才会显示的原因是什么?

  • ViewRoot是干嘛的,是ViewTree的rootview吗?

一、整体流程图

兄弟们,先拿上这份“地图”,毕竟“源码”这个地方吧,九曲十八弯的,看着地图走,才不会走丢。

大概说一下流程(本文最后也会再重复一次):

1、创建PhoneWindow对象,往PhoneWindow对象里面添加了一个DecorView2、为DecorView绑定一个ViewRootImpl,由这个ViewRootImpl负责View的绘制和刷新3、ViewRootImpl通过IWindowSession向WMS发起Binder调用,而WMS也会通过IWindow向应用端发起调用 4、ViewRootImpl会在WMS里面注册一个窗口,然后由WMS统一的管理所有窗口的大小,位置和层级5、在第一次绘制时,ViewRootImpl还会向WMS申请一块Surface(WMS向Surface申请),有了Surface之后,应用端就可以进行绘制了6、应用端绘制完之后,SurfaceFlinger就会按照WMS里面提供的层级等信息进行合成,最终显示

为了让大家在跟踪源码的时候不会迷路,先跟大家科普路下路上的这几个“门卫大哥”:

  • PhoneWindow:Window在手机端的唯一实现类,WMS管理的就是一个个Window,而不是View

  • DecorView:顶层的View,我们平时setContentView设置的View对应的就是蓝色的ContentView,

  • ViewRootImpl:这个哥们是View和WMS通信的桥梁,每次View想跟WMS通信要通过它;每次WMS想让View更新也要通过它;一个DecorView对应一个ViewRootImpl

  • SurfaceFlinger:负责Surface的合成,一块Surface就是一块画布,应用端其实都是在Surface上绘制图形

  • WindowManagerService:我们口中经常说的WMS,主要负责管理窗口,,并不负责view的绘制。

以下是WMS的主要作用:

对了,我采用的源码是Android 28的。

下面正式开始~ Action, go ~

二、源码分析

2.1 setContentView
// 类:---> View.javapublic void setContentView(int layoutResId){    getWindow().setContentView(layoutResId);    ...}//类:---->PhoneWindow.javapublic void setContentView(int layoutResID) {    if (mContentParent == null) {            installDecor();        }    ...     mLayoutInflater.inflate(layoutResID, mContentParent);    ...}

我们可以看到,通过mLayoutInflater.inflate(layoutResID, mContentParent)进行加载布局,这里有两个参数,一个是layoutId,另外一个是mContentParent,这个mContentParent又是什么?

可以看到mContentParent是通过installDecor()初始化的,继续跟吧。

2.2 installDecor
---> 类:PhoneWindow.java private void installDecor() {     if (mDecor == null) {     //1、new 一个 DecorView            mDecor = generateDecor(-1);     ....     }      if (mContentParent == null) {      //2、inflate 布局            mContentParent = generateLayout(mDecor);          ....      } }//1、new 一个 DecorView protected DecorView generateDecor(int featureId) {     ....     return new DecorView(context, featureId, this, getAttributes()); } //2、inflate 布局 protected ViewGroup generateLayout(DecorView decor) {     ....     // Inflate the window decor.     //3、inflate 布局,并添加到decorView中     mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);    //4、找到contentView,返回    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);    ...     return contentParent; } ///----->DecorView.java void onResourcesLoaded(LayoutInflater inflater, int layoutResource){      //3、      final View root = inflater.inflate(layoutResource, null);      ...      // Put it below the color views.      addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); }

我们理一下流程,以上代码主要做了:

  • 创建一个DecorView,这个DecorView本质是一个FrameLayout

  • inflate一个布局root

  • 把root 添加到decorView里面

  • 通过findViewById找到contentParent,其实就是id为R.id.content的View

提个小问题提提神,大家知道为什么在实现沉浸式布局的时候,通过getWindow().requestFeature(Window.FEATURE_NO_TITLE);等配置为什么要在setContentView()之前吗?看看installDecor()方法你就知道了~

到这一步,整个界面就加载完成了吗?too naive,这一步只是初始化了View Tree,整个界面还没有显示出来,界面真正要显示出来还要看onResume()这个生命周期里做了啥。继续跟吧~

2.3 handleResumeActivity(IBinder token)
---->类:ActivityThread.java@Override    public void handleResumeActivity(IBinder token,..){         // TODO Push resumeArgs into the activity for consideration        //1、        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);        final Activity a = r.activity;        if (r.window == null && !a.mFinished && willBeVisible) {            r.window = r.activity.getWindow();            View decor = r.window.getDecorView();            decor.setVisibility(View.INVISIBLE);            ViewManager wm = a.getWindowManager();            WindowManager.LayoutParams l = r.window.getAttributes();            a.mDecor = decor;            if (a.mVisibleFromClient) {                if (!a.mWindowAdded) {                    a.mWindowAdded = true;                    //2、                    wm.addView(decor, l);                } else {                ...            }        }        if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {                    ...          if (r.activity.mVisibleFromClient) {                //3、                r.activity.makeVisible();            }        }    }

整个流程分为三步:

  • 回调Activity的onResume()方法

  • 调用WindowManager的addView()方法

  • 设置Activity为visible

我们接下来要重点看的是addView()这个方法。为什么呢?因为第一步就是回调,第三步是让它可见,那就说明触发界面绘制的流程是在第二步。所以,我们接下来要跟踪第二步。

2.4 wm.addView(decor, l)
---->WindowManagerImpl.java    @Override    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {        applyDefaultToken(params);        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);    }----> WindowManagerGlobal.java    public void addView(View view, ViewGroup.LayoutParams params,            Display display, Window parentWindow) {                 ....                 ViewRootImpl root;                  synchronized (mLock) {                  root = new ViewRootImpl(view.getContext(), display);                  ...                  // do this last because it fires off messages to start doing things                  //注释大意:最后才用调用这个方法,因为它会发送消息开始干活                  try {                root.setView(view, wparams, panelParentView);            }            ....                  }            }

这个方法就做了两个操作:

1、创建了ViewRootImpl对象(所以,ViewRootImpl和Window的关系是一对一,第二次说明了)2、调用ViewRootImpl的setView方法

2.5 viewRootImpl.setView
 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {        synchronized (this) {        if (mView == null) {                mView = view;             // 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();          try{              ...              res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);             ...          }          ...        }        }

setContentView这个方法,重点就做了这么几件事:

1、给mView赋值2、调用requestlayout3、调用windowSession的addToDisplay

第一个分叉路口来了,我们要分开看requestlayout()方法和addToDisplay()方法。

分岔路 2.5.1 requestlayout()

----> ViewRootImpl.java @Override    public void requestLayout() {        if (!mHandlingLayoutInLayoutRequest) {        //检查是否是主线程。下次面试官问你ui不能在子线程更新        //的异常在哪里抛出的,勇敢的告诉面试官,在ViewRootImpl        //的requestLayout()            checkThread();            mLayoutRequested = true;            //1            scheduleTraversals();        }    }   //1    void scheduleTraversals() {        if (!mTraversalScheduled) {           ...            mChoreographer.postCallback(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);            ....        }    }

在这一步里面会post一个callback,这个callback在下一次Vsync信号到来的时候会被调用(关于下一次Vsync信号来了怎么回调到Java层,请看这篇文章)。Anyway,当Vsync信号到来后,最终会执行mTraversalRunnable的run方法,我们具体来看一下:

----> ViewRootImpl.java final class TraversalRunnable implements Runnable {        @Override        public void run() {            doTraversal();        }    }void doTraversal() {...    performTraversals();...}private void performTraversals() {    ...    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);    ...    //绘制三部曲:measure、layout、draw    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);    ...     performLayout(lp, mWidth, mHeight);    ...     performDraw();     ...}

measure、layout、draw这几个方法大家都很熟悉了,所以我们重点看relayoutWindow这个方法做了什么事情?为什么它是放在measure、layout、draw之前的?

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,            boolean insetsPending) throws RemoteException {        //看到RemoteException,掐指一算,估摸有ipc通信        ...        int relayoutResult = mWindowSession.relayout(..., mSurface);        ...    }

其实这一步的作用就一个————向WMS申请一块Surface。

这里不是有一个Surface了吗?不就是那个mSurface??(黑人问号脸❓)

其实,此时这个mSurface是一块空的Surface,它只是被创建了出来,但是还没有分配内存空间,还是空白的。

    public final Surface mSurface = new Surface();

应用端需要调用WMS的relayoutWindow给Surface进行赋值,关于Surface的传输和赋值过程我就不分析了,大家可以看文章。

https://juejin.cn/post/6844904084307181581

Surface赋值初始化成功后,客户端就可以进行绘制了。当绘制完成后,会调用unlockAndPostCanvas()来通知SurfaceFlinger进行合成。现在,requestLayout()这个方法就算看完了,接下来去另一个分岔路口。

分岔路 2.5.2 windowSession.addToDisplay()

这个WindowSession是一个AIDL,它的作用是用来给应用和WMS通信的,它真正的实现类是Session,具体找寻的流程如下:

 ---> Session.java@Override    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,...) {    //这里要注意这个IWindow,IWindow是WMS向应用端发起Binder调用的    //Session把它传给WMS是为了以后WMS可以主动向应用端发起调用        return mService.addWindow(this, window, seq, attrs, ...);    }

我们可以看到,最终调用的是WMS的addView方法,调用这个方法的作用是向WMS注册窗口对象,然后就会WMS统一去协调这些Window的层级、大小和位置。

对WMS来说,它并不关心我们应用本身的Window对象或者View对象,对它来说,它一个重要的功能就是给应用端的Window分配Surface,并且,控制这些Surface的显示顺序,位置和尺寸。

当应用端在Surface绘制完了之后,SurfaceFlinger就会按照这些图像数据,按照WMS提供的尺寸、层级和位置等等进行合成,最后写到屏幕缓冲区里面,绘制出来。

2.6 小结

handleResumeActivity()这个方法整体流程大概可以概括为以上几步。

整个绘制流程比较长,等源码看的差不多了,再考虑写一个系列吧~

三、总结

到这里其实整个流程就算完成了,来总结一下:

1、创建了PhoneWindow对象,往PhoneWindow对象里面添加了一个DecorView2、为DecorView绑定一个ViewRootImpl,由这个ViewRootImpl负责View的绘制和刷新3、ViewRootImpl通过IWindowSession向WMS发起Binder调用,而WMS也会通过IWindow向应用端发起调用 4、ViewRootImpl会在WMS里面注册一个窗口,然后由WMS统一的管理所有窗口的大小,位置和层级5、在第一次绘制时,ViewRootImpl还会向WMS申请一块Surface,有了Surface之后,应用端就可以进行绘制了6、绘制完之后,SurfaceFlinger就会按照WMS里面提供的层级等信息进行合成,最终显示

四、问题解答

1、setContentView的原理是什么?

setContentView()的原理是主要是:(1)创建DecorView和ViewRootImpl,并将它们两者进行绑定(2)通过inflate方法创建ViewTree,此时还没有显示

2、Activity在onResume之后才会显示的原因是什么?

在onResume方法中才会调用ViewRootImpl的performTraversal()方法进行界面绘制以及makeVisible方法进行界面显示;

3、ViewRoot是干嘛的,是ViewTree的RootView吗?

ViewRoot,或者说它的实现类ViewRootImpl,跟View没有任何关系。ViewRoot只是ViewTree的管理者,而不是ViewTree的根节点。真正的根节点是DecorView

推荐阅读

(点击标题可跳转阅读)

大白话讲解RxJava原理

记一个“隐藏”的内存泄露

超详细!ArrayList源码图文解析

Handler相关面试题你答对多少?子线程和主线程是如何切换的?

觉得本文对你有帮助?请分享给更多人


wx号:gulinhai531

顾林海公众号

不定期推出优质文

章,喜欢的朋友们

给我个好看。

好文章,我在看❤️