Android渲染系列(5)之Window、Activity、View之间关系

本篇开始介绍渲染中贴近我们实际开发的概念。

本文是Android渲染系列的第五篇

🔥点 认识一下 加uestc_xsf(备注掘金进群)获取学习资料,小程序搜 面试官的小抄

Window概念

Android窗口主要分为两种:

  • 应用窗口:一个activity有一个主窗口,弹出的对话框也有一个窗口,Menu菜单也是一个窗口。在同一个activity中,主窗口、对话框、Menu窗口之间通过该activity关联起来。

应用相关的窗口表示类是PhoneWindow,其继承于Window,针对手机屏幕做了一些优化工作,里面核心的是mDecorView这个变量,mDecorView是一个顶层的View,窗口的添加就是通过调用getDecorView()获取到mDecorView并且调WindowManager.addView()把该View添加到WindowManager中。

但也有例外,比如悬浮窗口虽然与activity相关联,但并不是PhoneWindow,直接调用通过WindowManager.addView()添加。

如果我们想给所有的应用都加一个比如最大、最小化、关闭的导航条,那只需更改mDecorView即可(Android N为支持多窗口将DecorView从PhoneWindow中分离成一个单独的文件)。

  • 公共界面的窗口:如最近运行对话框、关机对话框、状态栏下拉栏、锁屏界面等。这些窗口都是系统级别的窗口,不从属于任何应用,和activity没有任何关系。这种窗口没有任何窗口类来封装,也是直接调用WindowManager.addView()来把一个view添加到WindowManager中。

先来张宏观概览的图

  • 每个 Activity 均包含一个 Window 对象,即 Activity 和 Window 是一对一的关系
  • Window 是一个抽象类,其唯一的实现类是 PhoneWindow
  • PhoneWindow 内部包含一个 DecorView,DecorView 是 FrameLayout 的子类,其内部包含一个 LinearLayout,LinearLayout 中又包含两个自上而下的 childView,即 ActionBar 和 ContentParent。

我们平时在 Activity 中调用的 setContentView 方法实际上就是在向 ContentParent 执行 addView 操作

windows是承载actvity和view的载体,下面以window为核心把actvity和view串联起来

  • Window:窗口,在代码层次上是一个抽象类,真正的实现类在PhoneWindow里,用来管理View的展示以及事件的分发,在概念上表示的是一个窗口。Android 中所有的视图都是通过 Window 来呈现的,例如 Activity、Dialog 和 Toast 等,它们实际上都是挂载在 Window 上的
  • Activity :Android四大组件之一,也是我们最常见的页面的宿主,通过setContentView将xml布局,解析并展示到页面上;一个 Activity 对应一个 Window 也就是 PhoneWindow,一个 PhoneWindow 持有一个 DecorView 的实例,DecorView 本身是一个 FrameLayout。
  • View:Android的视图,是各种炫酷控件的最终父类,维护了绘制流程以及事件的分发和处理;

Window的创建

Activity#onCreate

首先是actvity先启动,就是开启了PhoneWindow的创建

  1. 在 Activity 创建过程中执行 scheduleLaunchActivity() 之后便调用到了 handleLaunchActivity() 方法。
  2. Activity 里新建一个 PhoneWindow 对象

Activity 里新建一个 PhoneWindow 对象。在 Android 中,Window 是个抽象的概念, Android 中 Window 的具体实现类是 PhoneWindow,Activity 和 Dialog 中的 Window 对象都是 PhoneWindow。

同时window得到一个 WindowManager 对象,不过 WindowManager 是一个抽象类,这个 WindowManager 的具体实现是在 WindowManagerImpl 中,(类似 Context 和 ContextImpl)

typescript 复制代码
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { 
    ...    
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    ...
}

从这里可以看出 每个 Activity 会有一个 WindowManager 对象,这个 mWindowManager 就是和 WindowManagerService 进行通信,也是 WindowManagerService 识别 View 具体属于那个 Activity 的关键,创建时传入 IBinder 类型的 mToken 这个 Activity 的 mToken是一个 IBinder,WindowManagerService 就是通过这个 IBinder 来管理 Activity 里的 View。

这样actvity创建时候通过wms获取到了一个window

下面再看看view是如何关联起来的

根View(DecorView)的创建

回调 Activity.onCreate() 后,会执行** setContentView()** 方法将我们写的 Layout 布局页面设置给 Activity。

less 复制代码
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);        
    initWindowDecorActionBar();    
}

最终到 PhoneWindow.installDecor():

csharp 复制代码
private void installDecor() {    
//根据不同的 Theme,创建不同的 DecorView,DecorView 是一个 FrameLayout 
}

创建了 PhoneWindow ,和DecorView ,但目前二者也没有任何关系

产生关系是在ActivityThread.performResumeActivity 中,因此我们经常谈到的一个问题 create阶段是无法测量view的宽高原因也是在此

小结

用一个图来展示本节内容,activity 创建后从wms拿到window然后创建DecorView

Windows显示

Activity#onResume

create之后调用 activity.performResume(),调用 r.activity.makeVisible,将 DecorView 添加到当前的 Window 上。

关注公众号 Android 茶话会,更多精彩等你探索

ActivityThread.handleResumeActivity():

ini 复制代码
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
    //执行到 onResume()
    ActivityClientRecord r = performResumeActivity(token, clearHide);

    if (r != null) {
        final Activity a = r.activity;
        boolean willBeVisible = !a.mStartedActivity;
        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            ......
            a.mDecor = decor;
            ......
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            }
        }
        ...
        if (!r.activity.mFinished && willBeVisible
                && r.activity.mDecor != null && !r.hideForNow) {
            ...
            mNumVisibleActivities++;
            if (r.activity.mVisibleFromClient) {
                //添加视图,详见下面分析
                r.activity.makeVisible(); 
            }
        }

        //resume 完成
        if (reallyResume) {
              ActivityManagerNative.getDefault().activityResumed(token);
        }
    } else {
        ...
    }
}
void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        //详见下面分析
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

WindowManager 的 addView 的具体实现在 WindowManagerImpl 中,而 WindowManagerImpl 的 addView 又会调用 WindowManagerGlobal.addView()。

ViewRootImpl

ViewRootImpl是View中的最高层级,属于所有View的根(但ViewRootImpl不是View,只是实现了ViewParent接口),实现了View和WindowManager之间的通信协议,实现的具体细节在WindowManagerGlobal这个类当中。

WindowManager和DecorView之间的桥梁

  • 创建ViewRootImpl

通过WindowManagerGlobal.addView():

scss 复制代码
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
    ...
    ViewRootImpl root = new ViewRootImpl(view.getContext(), display);        
    view.setLayoutParams(wparams);    
    mViews.add(view);    
    mRoots.add(root);    
    mParams.add(wparams);        
    root.setView(view, wparams, panelParentView);
    ...
}

这个过程创建一个 ViewRootImpl ,并将之前创建的 DecoView 作为参数传入,以后 DecoView 的事件都由 ViewRootImpl 来管理了, 比如,DecoView 上添加 View,删除 View。ViewRootImpl 实现了 ViewParent 这个接口,这个接口最常见的一个方法是 requestLayout()。 此时

  1. DecorView被渲染绘制到屏幕上显示
  2. DecorView可以接收屏幕触摸事件

此时view的宽高才可以被测量出来

ViewRootImpl 是个 ViewParent,在 DecoView 添加的 View 时,就会将 View 中的 ViewParent 设为 DecoView 所在的 ViewRootImpl,View 的 ViewParent 相同时,理解为这些 View 在一个 View 链上。所以每当调用 View 的 requestLayout()时,其实是调用到 ViewRootImpl,ViewRootImpl 会控制整个事件的流程。

可以看出一个 ViewRootImpl 对添加到 DecoView 的所有 View 进行事件管理。

ViewRootImpl,它是WindowManager和DecorView之间的桥梁,View的三大流程(测量,布局,绘制)都是通过ViewRootImpl来完成的。

  • ViewRootImpl的setView

ViewRootImpl的setView方法最终会将View添加到WindowManagerService中。

在ViewRoomImpl的setView中做了很多事情:

  • 通过requestLayout开始performTraversals那套测量、布局、绘制流程,这会让关联的View也执行了measure、layout、draw流程。
  • 创建InputChannel,在注释3中将InputChannel添加到WindowManagerService中创建socketpair(一对socket)用于发送和接收事件
  • 添加View到WindowManagerService,这里是通过mWindowSession来完成的,它的定义是final IWindowSession mWindowSession;,它其实是WindowManagerGlobal中的单例对象

小结

常见面试问题

Actvity、Window、DecorView创建时机

  • ActivityThread中执行performLaunchActivity,从而生成了Activity的实例
  • performLaunchActivity中,在创建Activity实例的同时,会调用Activity的内部方法attach.在该方法中完成window的初始化
  • 用户执行Activity的setContentView方法,内部是调用PhoneWindow的setContentView方法,在PhoneWindow中完成DecorView的创建
  1. Activity中的setContentView
  2. PhoneWindow中的setContentView
  3. PhoneWindow中的installDecor

Dialog为什么不能使用Application的Context

  • Dialog窗口类型是TYPE_APPLICATION,与Activity一样
  • TYPE_APPLICATION要求Token不能为null,Application没有AppWindowToken

Activity中的mDecor和Window里面的mDecor有什么关系?

  • 两者指向同一个对象,都是DecorView
  • Activity中的mDecor是通过ActivityThread中的handleResumeActivity方法来赋值的

ViewRoot是什么?ViewRootImpl又是什么?

  • ViewRoot的实现类是ViewRootImpl,是WindowManagerService和DecorView的纽带
  • ViewRoot不是View的根节点
  • View的绘制是从ViewRootImpl的performTraversals方法开始的

ViewRootImpl何时创建?

当window被装进WindowManager时,完成ViewRootImpl的创建,最终是通过WindowManagerGlobal.addView方法中进行创建的(onResume阶段)

Activity中的Window何时被装进WindowManager?

  • 发生在Activity的onResume阶段
  • 执行顺序
    • ActivityThread中的handleResumeActivity
    • Activity中的makeVisible

总结

从表面上看Activity参与度比较低,大部分View的添加操作都被封装到Window中进行实现,而Activity相当于是提供给开发人员的一个管理类,通过它能更简单地实现Window和View的操作逻辑。

  • 一个Activity中有一个Window,也就是PhoneWindow对象,在PhoneWindow中有一个DecorView,在setContentView中会将layout填充到此DecorView中
  • 一个应用进程中只有一个WindowManagerGlobal对象,单例
  • 每一个PhoneWindow对应一个ViewRootImpl对象
  • WindowManagerGlobal通过ViewRootImpl的setView方法,完成Window的添加过程
  • ViewRootImpl的setView方法中主要完成两件事情:View渲染以及接收触摸事件
  • Dialog和Toast都有自己的Window,而PopupWindow没有只是而是将view加到DecorView

🔥点 认识一下 加uestc_xsf(备注掘金进群)获取学习资料,小程序搜 面试官的小抄

参考

相关推荐
数据猎手小k5 分钟前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小1040 分钟前
JavaWeb项目-----博客系统
android
风和先行1 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.2 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
测试19983 小时前
2024软件测试面试热点问题
自动化测试·软件测试·python·测试工具·面试·职场和发展·压力测试
似霰3 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶3 小时前
Android——网络请求
android
干一行,爱一行3 小时前
android camera data -> surface 显示
android
断墨先生3 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
马剑威(威哥爱编程)4 小时前
MongoDB面试专题33道解析
数据库·mongodb·面试