Android Framework重要服务之WindowManagerService(一) 加载视图

WindowManagerService(简称WMS),是android中的一个重要的系统服务,用于窗口管理。从其内部实现来看,WMS主要包含窗口的添加与删除、启动窗口、窗口动画、控制窗口大小和层级等功能,本章将结合Android11源码分析WMS加载视图的过程。

一、视图间的关系

image.png
Android的视图平面如上图所示,实际上,Android的视图平面也是层叠渲染的,要了解视图间的关系实际上也就是要了解Activity 、Window和View间的关系,他们三个的关系可以总结如下:

  • Activity:本身不是窗口,也不是视图,它只是窗口的载体,一方面是key、touch事件的响应处理,一方面是对界面生命周期做统一调度。
  • Window: 一个顶级窗口查看和行为的一个抽象基类。它也是装载View的原始容器, 处理一些应用窗口通用的逻辑。使用时用到的是它的唯一实现类:PhoneWindow。Window受WindowManager统一管理。
  • DecorView: 顶级视图,一般情况下它内部会包含一个竖直方向的LinearLayout,上面的标题栏(titleBar),下面是内容栏。通常我们在Activity中通过setContentView所设置的布局文件就是被加载到id为android.R.id.content的内容栏里(FrameLayout)。

二、window的类型

window的类型较多,具体可见WindowManager的LayoutParams,这里给出几个比较重要的类型:

window类型特点层级范围典型window代表
system window独立存在2000~2999Toast 、警告提示window、键盘window等
application window属于应用的窗口1~99对应的就是activity的window
sub window需要依附于父window,不能独立存在1000~1999Dialog 、 popupWindow

在上表中需要说明的是,Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window上面。如果想要Window位于所有Window的最顶层,那么采用较大的层级即可。另外有些系统层级的使用是需要声明权限的。由此可以得出以下结论:

系统层级: 应用window < 子window < 系统window

三、创建Window

想要创建一个Window,则必须创建一个activity,activity的创建在本文的其他章节已有阐述,所以这里从handleLaunchActivity开始探究。 frameworks/base/core/java/android/app/ActivityThread.java

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    
    //获取WindowManagerService的Binder引用(proxy端)。
    WindowManagerGlobal.initialize();
    
    //创建activity,调用attach方法,然后调用Activity的onCreate,onStart,onResotreInstanceState方法
    Activity a = performLaunchActivity(r, customIntent);
    ...
    
} 

在这里主要关注performLaunchActivity方法,该方法调用了activity.attach方法:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    
    activity.attach(appContext, this, getInstrumentation(), r.token,
        r.ident, app, r.intent, r.activityInfo, title, r.parent,
        r.embeddedID, r.lastNonConfigurationInstances, config,
        r.referrer, r.voiceInteractor, window, r.configCallback,
        r.assistToken, r.shareableActivityToken);
    ...
    
} 

activity.attach方法的具体实现如下所示: frameworks/base/core/java/android/app/ActivityThread.java

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window) {
        //绑定上下文
        attachBaseContext(context);
        
        //创建Window, PhoneWindow是Window的唯一具体实现类
        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        ...
        
        //设置WindowManager
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        //创建完后通过getWindowManager就可以得到WindowManager实例
        mWindowManager = mWindow.getWindowManager();//其实它是WindowManagerImpl
        ...
        
    } 

上述代码创建了一个PhoneWindow对象,并且实现了Window的Callback接口,这样activity就和window关联在了一起,并且通过callback能够接受key和touch事件

此外,初始化且设置windowManager。每个 Activity 会有一个 WindowManager 对象,这个 mWindowManager 就是和 WindowManagerService 进行通信,也是 WindowManagerService 识别 View 具体属于哪个 Activity 的关键。
我们从window的setWindowManager方法出发,很容易找到WindowManager这个接口的具体的实现是WindowManagerImpl。

四、添加view

WindowManagerImpl实现了WndowManager的大部分接口,其中,添加view的部分就是由WindowManagerImpl.addView方法实现的,现在来看addView方法。

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

在这里可以看出,该方法又调用了mGlobal.addView,对于Window(或者可以说是View)的操作都是交由WindowManagerGlobal来处理,WindowManagerGlobal以工厂的形式向外提供自己的实例。这种工作模式是桥接模式,将所有的操作全部委托给WindowManagerGlobal来实现。

在WindowManagerImpl的全局变量中通过单例模式初始化了WindowManagerGlobal,也就是说一个进程就只有一个WindowManagerGlobal对象,该类中的addView方法如下: frameworks/base/core/java/android/view/WindowManagerGlobal.java

 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            //调整布局参数,并设置token
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    //如果待删除的view中有当前view,删除它
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                }
                // The previous removeView() had not completed executing. Now it has.
               //之前移除View并没有完成删除操作,现在正式删除该view
            }

            //如果这是一个子窗口个(popupWindow),找到它的父窗口。
            //最本质的作用是使用父窗口的token(viewRootImpl的W类,也就是IWindow)
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                    //在源码中token一般代表的是Binder对象,作用于IPC进程间数据通讯。并且它也包含着此次通讯所需要的信息,
                    //在ViewRootImpl里,token用来表示mWindow(W类,即IWindow),并且在WmS中只有符合要求的token才能让
                    //Window正常显示.
                        panelParentView = mViews.get(i);
                    }
                }
            }
            //创建ViewRootImpl,并且将view与之绑定
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);//将当前view添加到mViews集合中,mViews存储所有Window对应的View
            mRoots.add(root);//将当前ViewRootImpl添加到mRoots集合中,mRoots存储所有Window对应的ViewRootImpl
            mParams.add(wparams);//将当前window的params添加到mParams集合中,存储所有Window对应的布局参数
        }
          ...
          
            //通过ViewRootImpl的setView方法,完成view的绘制流程,并添加到window上。
            root.setView(view, wparams, panelParentView);
    } 

在该方法的最后,调用了root.setView方法,该方法一方面触发绘制流程,一方面把view添加到window上。

下面来探究root.setView方法。 frameworks/base/core/java/android/view/ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    synchronized (this) {
        if (mView == null) {
            ...
            
            // 在 Window add之前调用,确保 UI 布局绘制完成 --> measure , layout , draw
            requestLayout();
            ...
            
            if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    //创建InputChannel
                    mInputChannel = new InputChannel();
            }
            ...
            
         try {
                    //通过WindowSession进行IPC调用,将View添加到Window上
                    //mWindow即W类,用来接收WmS信息
                    //同时通过InputChannel接收触摸事件回调
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
            }
            ...
            
        }
    }
} 

总体而言,在ViewRootImpl的setView()方法里做了两件事情:
1.执行requestLayout()方法完成view的绘制流程
2.通过WindowSession将View和InputChannel添加到WmS中,从而将View添加到Window上并且接收触摸事件。这是一次IPC 过程。

以上就是WMS加载视图的全部过程,用一张图总结如下: image.png

总结起来:WMS中 addWindow流程有如下4个过程:
1.通过type和 token对window进行分类和验证,确保其有效性。
2.构造WindowState与Window一一对应。
3.通过token对window进行分组。
4.对window分配层级。

对应到实际执行过程中,Activity在attach的时候,创建了一个PhoneWindow对象,并且实现了Window的Callback接口,这样activity就和window绑定在了一起,通过setContentView,创建DecorView,并解析好视图树加载到DecorView的contentView部分,WindowManagerGlobal一个进程只有唯一一个,对当前进程内所有的视图进行统一管理,其中包括ViewRootImpl,它主要做两件事情,先触发view绘制流程,再通过IPC 把view添加到window上,下面给出WMS加载识图的全流程图: image.png

文末

要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

在这里插入图片描述

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

在这里插入图片描述

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

在这里插入图片描述

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

在这里插入图片描述

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
在这里插入图片描述

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

在这里插入图片描述

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

在这里插入图片描述

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

在这里插入图片描述

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取【保证100%免费】↓↓↓

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值