|
|
|
|
公众号矩阵

Activity显示界面历险记

在Activity显示View的过程中,有一些重要的角色总让人理不清,比如PhoneWindow、DecorView、ViewRootImpl。

作者:积木zz 来源:码上积木|2021-02-22 08:20

前言

在Activity显示View的过程中,有一些重要的角色总让人理不清,比如PhoneWindow、DecorView、ViewRootImpl。

也常常有面试题会问到,他们四者之间的关系?创建的时机?View第一次绘制的时机?等问题。

那么今天,就和大家一起从Activity启动开始 看看 到展示出View整个过程中,到底会经过哪些步骤,这之间各角色的关系又如何。

动画展示

为了方便大家理解,先通过动画的形式给大家展示这几位的关系:

源码解析

从小爱诞生说起

在Activity界面展示之前,它还是个我们看不到的Activity,我先给它起个爱称—小爱。

小爱是怎么诞生的呢?熟悉Activity启动流程的都知道,小爱的创建发生在performLaunchActivity中:

  1. //ActivityThread.java 
  2.     private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { 
  3.         //创建ContextImpl 
  4.         ContextImpl appContext = createBaseContextForActivity(r); 
  5.         Activity activity = null
  6.         try { 
  7.             java.lang.ClassLoader cl = appContext.getClassLoader(); 
  8.             //创建Activity 
  9.             activity = mInstrumentation.newActivity( 
  10.                     cl, component.getClassName(), r.intent); 
  11.         } 
  12.  
  13.         try { 
  14.             if (activity != null) { 
  15.                 //完成activity的一些重要数据的初始化 
  16.                 activity.attach(appContext, this, getInstrumentation(), r.token, 
  17.                         r.ident, app, r.intent, r.activityInfo, title, r.parent, 
  18.                         r.embeddedID, r.lastNonConfigurationInstances, config, 
  19.                         r.referrer, r.voiceInteractor, window, r.configCallback, 
  20.                         r.assistToken); 
  21.  
  22.                 //调用activity的onCreate方法 
  23.                 if (r.isPersistable()) { 
  24.                     mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); 
  25.                 } else { 
  26.                     mInstrumentation.callActivityOnCreate(activity, r.state); 
  27.                 } 
  28.             } 
  29.         } 
  30.  
  31.         return activity; 
  32.     } 

这个过程中,主要做了三件事:

  • Activity被实例化出来
  • 调用了attach方法进行初始化
  • 调用onCreate方法开始从布局文件加载布局,做View显示的准备工作。

给小爱找个和View交互的帮手(PhoneWindow)

大家也都知道,小爱在被创建后,事务繁忙,肯定不能亲力亲为得管理每个View,所以他就找了一个帮手,帮助她和View交互,管理View。

(Activity和View的解耦)

这个帮手是啥呢?就是窗口Window,也就是实现类PhoneWindow了。

这个过程发生在attach方法中:

  1. //Activity.java 
  2. final void attach() { 
  3.   //创建PhoneWindow 
  4.         mWindow = new PhoneWindow(this, window, activityConfigCallback); 
  5.         mWindow.setCallback(this); 
  6.         mWindow.setWindowManager( 
  7.                 (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), 
  8.                 mToken, mComponent.flattenToString(), 
  9.                 (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); 
  10.          
  11.     } 

为了方便记忆,我们管这个PhoneWindow管家叫做 窗管家。

加载布局文件(DecorView)

有了窗管家之后,就可以继续onCreate方法了,在onCreate方法中最重要的就是这个setContentView方法。

通过setContentView可以加载布局文件里的View。

之前说了,View相关的管理工作就交给窗管家,所以就直接调用到PhoneWindow的setContentView方法:

  1. //Activity.java 
  2.    public void setContentView(@LayoutRes int layoutResID) { 
  3.        getWindow().setContentView(layoutResID); 
  4.        initWindowDecorActionBar(); 
  5.    } 

然后就开始加载布局文件的工作了。

但是考虑到一点,Activity是有不同的主题的,不同主题就有不同的布局结构。所以得在加载我们自己设置的布局文件之前,设置一个最顶级的View,作为所有View的老大。

而这个顶层的View就是DecorView,为了方便,我管他叫做 最顶的小弟,简称小弟。

看看小弟DecorView是怎么被创建的:

  1. //PhoneWindow.java 
  2.     @Override 
  3.     public void setContentView(int layoutResID) { 
  4.         if (mContentParent == null) { 
  5.             installDecor(); 
  6.         }  
  7.  
  8.  
  9.         if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 
  10.             mLayoutInflater.inflate(layoutResID, mContentParent); 
  11.         } 
  12.     } 
  13.  
  14.  
  15.     private void installDecor() { 
  16.         if (mDecor == null) { 
  17.             mDecor = generateDecor(-1); 
  18.         } else { 
  19.             mDecor.setWindow(this); 
  20.         } 
  21.         if (mContentParent == null) { 
  22.             mContentParent = generateLayout(mDecor);        
  23.         } 
  24.     } 
  25.  
  26.  
  27.     protected DecorView generateDecor(int featureId) { 
  28.         return new DecorView(context, featureId, this, getAttributes()); 
  29.     } 

就是这样,小弟DecorView就被创建出来了,然后就该小弟工作了。

上文说过,小弟DecorView被创建出来是要干啥的?

要根据不同的主题设置不同的布局结构,这个工作就发生在generateLayout方法中了,具体咱今天就不分析了。

看似小弟的工作也完成了?

等等,应用自己的布局还没加载呢嘛,重要的事情还没开始做呢。

再回到上面的setContentView方法中,在调用installDecor方法创建了小弟之后,还做了一件事:

  1. //加载xml布局文件 
  2. mLayoutInflater.inflate(layoutResID, mContentParent); 
  3.  
  4.  
  5.  
  6.    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { 
  7.        final Resources res = getContext().getResources(); 
  8.         
  9.        final XmlResourceParser parser = res.getLayout(resource); 
  10.        try { 
  11.            return inflate(parser, root, attachToRoot); 
  12.        } finally { 
  13.            parser.close(); 
  14.        } 
  15.    } 

而这个inflate就是我们熟知的加载布局文件的方法。传入xml布局文件,解析并结合我们传入的父view——mContentParent,将其转化为一个完整的树结构,最后返回顶层的View。

到这里,setContentView的工作算是完成了,

简单的说,就是创建了小弟DecorView,并且结合这个顶层的view和我们传入的xml布局文件,生成了一个多层结构的View。

显示出这个View(ViewRootImpl)

View有了,结构也定下来了。接下来就是怎么显示出这个View结构,让我们的手机展示出画面?

没错,就是绘制。

关于View的绘制工作交给谁做比较好呢?回忆下现在的成员:

  • 小爱Activity:大老板,负责统筹即可。
  • 窗管家PhoneWindow:负责管理各个View。
  • 小弟DecorView:最顶层的View,负责展示主题布局。

好像没有人选可以负责View绘制了?绘制这么重要,那就要再招一个朋友来了。

ViewRootImpl闪亮?登场,为了方便,我管他叫做 小薇。

小薇是什么时候创建的呢?

接着看Activity的调用过程,在onCreate调用完后,就会调用onResume方法,这又要从handleResumeActivity方法说起了。

  1. @Override 
  2.    public void handleResumeActivity() { 
  3.        //onResume 
  4.        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); 
  5.        //addView 
  6.        if (r.window == null && !a.mFinished && willBeVisible) { 
  7.            r.window = r.activity.getWindow(); 
  8.            View decor = r.window.getDecorView(); 
  9.            ViewManager wm = a.getWindowManager(); 
  10.            WindowManager.LayoutParams l = r.window.getAttributes() 
  11.            wm.addView(decor, l); 
  12.        } 

该方法主要做了两件事:

  • 调用onResume方法
  • 调用WM的addView方法。

小薇好像还没出来?

继续看addView方法:

  1. //WindowManagerGlobal.java 
  2.  public void addView() { 
  3.          
  4.         synchronized (mLock) { 
  5.              
  6.             root = new ViewRootImpl(view.getContext(), display); 
  7.  
  8.             view.setLayoutParams(wparams); 
  9.  
  10.             mViews.add(view); 
  11.             mRoots.add(root); 
  12.             mParams.add(wparams); 
  13.  
  14.  
  15.             try { 
  16.                 root.setView(view, wparams, panelParentView); 
  17.             }  
  18.         } 
  19.     } 
  20.  
  21.  
  22.  
  23.     public ViewRootImpl(Context context, Display display) { 
  24.         mContext = context; 
  25.         mWindowSession = WindowManagerGlobal.getWindowSession(); 
  26.         mThread = Thread.currentThread(); 
  27.     } 

终于,小薇ViewRootImpl也被创建出来了,而这个ViewRootImpl中,有两个变量值得关注一下:

  • mWindowSession。类型为IWindowSession,是一个Binder对象,用于进程间通信。其在服务器端的实现为Session,可以通过它来完成WMS相关的工作。
  • mThread。设置了线程变量为当前线程,也就是实例化ViewRootImpl时候的线程。一般进行不同线程更新UI的时候,就会判断当前线程和mThread是否相等,如果不同,则会抛出异常。

接下来,就是调用ViewRootImpl的setView方法,这个方法自然就是小薇ViewRootImpl做事的方法了:

  1. //ViewRootImpl.java 
  2.     public void setView() { 
  3.         synchronized (this) { 
  4.          //绘制 
  5.          requestLayout(); 
  6.  
  7.          //调用WMS的addWindow方法 
  8.          res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, 
  9.                             getHostVisibility(), mDisplay.getDisplayId(), mWinFrame, 
  10.                             mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, 
  11.                             mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel); 
  12.  
  13.          //设置this(ViewRootImpl)为view(decorView)的parent 
  14.    view.assignParent(this); 
  15.         } 
  16.     } 

主要有三个功能:

  • 触发绘制(具体包括测量、布局、绘制)
  1. //ViewRootImpl.java 
  2.     @Override 
  3.     public void requestLayout() { 
  4.         if (!mHandlingLayoutInLayoutRequest) { 
  5.             checkThread(); 
  6.             mLayoutRequested = true
  7.             scheduleTraversals(); 
  8.         } 
  9.     } 
  10.  
  11.     ->scheduleTraversals() 
  12.     ->performMeasure() performLayout() performDraw() 
  13.     ->measure、layout、draw方法 
  • 通过Binder调用WMS的addWindow方法

addToDisplay方法最终会WMS所在进程的addWindow方法,为窗口分配Surface,而这个Surface就是负责显示最终的界面,并最终会绘制到屏幕上。

  • 设置ViewRootImpl为decorView的parent

这样设置之后,子view请求绘制的时候(requestLayout),就能一直通过parent最终找到ViewRootImpl,然后由ViewRootImpl来负责所有View的绘制工作。整个调用过程是:

  1. //View.java 
  2.     public void requestLayout() { 
  3.         if (mParent != null && !mParent.isLayoutRequested()) { 
  4.             mParent.requestLayout(); 
  5.         } 
  6.     } 

小结

到此,Activity终于完成了他的启动生命周期,界面也显示出来了,小爱也变为了成型的Activity。

其实不难发现,虽然这中间角色比较多,但是每个角色又不可或缺:

因为需要管理View,创建出了 PhoneWindow;

因为需要根据主题显示不同的布局结构,创建出了根View DecorView;

因为需要处理View的各种事件,包括绘制、事件分发,创建出了ViewRootImpl。

大家各忙各的,并听命于Activity。

本文转载自微信公众号「码上积木」,可以通过以下二维码关注。转载本文请联系码上积木公众号。

【编辑推荐】

  1. 前端: 轻松教你使用纯css实现水波动画
  2. 看动画粗暴 Python入门
  3. 【Web动画】SVG 实现复杂线条动画
  4. 【Web动画】SVG 线条动画
  5. HTML5+CSS3 玩转炫酷创意动画实战开发课程(2020年)
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

数据湖与数据仓库的分析实践攻略

数据湖与数据仓库的分析实践攻略

助力现代化数据管理:数据湖与数据仓库的分析实践攻略
共3章 | 创世达人

6人订阅学习

云原生架构实践

云原生架构实践

新技术引领移动互联网进入急速赛道
共3章 | KaliArch

33人订阅学习

数据中心和VPDN网络建设案例

数据中心和VPDN网络建设案例

漫画+案例
共20章 | 捷哥CCIE

220人订阅学习

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微