第8章 理解Window和WindowManager

有时候我们需要在桌面上显示一个类似悬浮窗的东西,这种效果就需要用 Window
来实现,Window 是一个抽象类,表示一个窗口,它的具体实现类是
PhoneWindow,实现位于 WindowManagerService 中。相信看到
WindowManagerService 你会有点眼熟,刚接触 Android
时几乎所有人都看到过这样一张图:

Window表示一个窗口的概念,Window是一个抽象类,它的具体实现是PhoneWindow。创建一个Window,需要通过WindowManager即可完成,WindowManager是外界访问Window的入口,Window具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC的过程。Android中,所有的视图都是通过Window来呈现,不管是Activity、Dialog、还是Toast,它们的视图实际上都是附加在Window上,因此Window是实际View的直接管理者,单击事件由Window传递给DecorView,然后再由DecorView传递给我们的View,就连Activity的设置视图方法setContentView在底层也是通过Window来完成的。

Window是一个抽象类,它的具体实现是PhoneWindow。创建一个Window是很简单的事,只需要通过WindowManager即可完成。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。

概述

  1. Window是一个抽象类,具体实现是PhoneWindow。
  2. 创建一个Window,通过WindowManger就可以完成。WindowMangager是外界访问Window的入口
  3. Window的具体实现位于WindowMangerService(WMS),WindowManager和WMS的交互是一个IPC过程。
  4. Activity/Dialog/Toast,他们的视图都是附加在Window上的。Window是View的直接管理者。

Window是一个抽象的类,它的具体实现是PhoneWindow
Window有三种类型 应用window ,子window 系统window
window是分层的,每个window对应着z-ordered
WindowManager所提供的功能很简单,添加view,更新view,和删除view

图片 1这里写图片描述

Window和WindowManager

添加一个Window的过程,重点代码是:

mWindowManager.addView(mFLoatingButton,mLayoutParams);

WindowManager.LayoutParams中有两个flags和type参数。

Flags参数有三个Window属性

  • FLAG_NOT_FOCUSABLE。表示Window不需要获取焦点,也不需要接收各种输入事件,最终事件会直接传递给下层的具有焦点的Window
  • FLAG_NOT_TOUCH_MODAL。在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理,这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法收到单击事件
  • FLAG_SHOW_WHEN_LOCKED。开启此模式可以让Window显示在锁屏的界面上。

Type参数表示Window的类型,有三种类型,分别是应用Window,子Window和系统Window,应用类Window对应一个Activity,子Window不能单独存在,它需要附属在特定的父Window之中,比如常见的Dialog就是一个子Window,系统Window是需要声明权限在能创建的Window,比如Toast和系统状态栏这些都是系统Window。

Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window的上面,在三类Window中,应用类的Window的层级范围是1-99,子Window的层级范围是1000-1999,系统Window的层级的范围是2000-2999,这些层级范围对应着WindowManager.LayoutParams的Type参数。如想要Window位于所有Window的最顶层,那么采用较大的层级即可。很显然系统Window层级是最大的,而且系统层级有很多值。

WindowManager所提供的功能很简单,常用有三个方法,即添加View,更新View和删除View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager。

一、Window和WindowManager

通过WindowManager添加Window:

图片 2

图片 3

将一个Button添加到屏幕为(100,300)的位置上

Flags参数表示Window的属性:

8.1 Window和WindowManager

  1. 通过WindowManager添加Window的过程

mFloatingButton = new Button(this);
mFloatingButton.setText("test button");
mLayoutParams = new WindowManager.LayoutParams(
        LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0,
        PixelFormat.TRANSPARENT);
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
        | LayoutParams.FLAG_NOT_FOCUSABLE
        | LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mFloatingButton.setOnTouchListener(this);
mWindowManager.addView(mFloatingButton, mLayoutParams);
  1. flags参数解析:

    1. FLAG_NOT_FOCUSABLE:表示window不需要获取焦点,也不需要接收各种输入事件。
    2. FLAG_NOT_TOUCH_MODAL:系统会将window区域外的单击事件传递给底层的window,一般都需要开启这个标记;
    3. FLAG_SHOW_WHEN_LOCKED:开启此模式可以让Window显示在锁屏的界面上。
  2. type参数表示window的类型,window共有三种类型:

    1. 应用window。应用window对应着一个Activity,层级范围是1~99
    2. 子window。子window不能独立存在,需要附属在特定的父window之上,比如Dialog就是子window。层级范围是1000~1999.
    3. 系统window。系统window是需要声明权限才能创建的window,比如Toast和系统状态栏这些都是系统window,需要声明的权限是<uses-permission
      android:name=”android.permission.SYSTEM_ALERT_WINDOW”
      />。层级范围是2000~2999
  3. WindowManager继承自ViewManager,常用的只有三个方法:

    1. addView
    2. updateViewLayout
    3. removeView

8.2Window的内部机制
Window是一个抽象的概念,每一个window都对应一个View和一个ViewRoootImpl。View才是Window存在的实体

WindowManagerService 就是位于 Framework
层的窗口管理服务,它的职责就是管理系统中的所有窗口。窗口的本质是什么呢?其实就是一块显示区域,在
Android 中就是绘制的画布:Surface,当一块 Surface
显示在屏幕上时,就是用户所看到的窗口了。WindowManagerService
添加一个窗口的过程,其实就是 WindowManagerService 为其分配一块 Surface
的过程,一块块的 Surface 在 WindowManagerService
的管理下有序的排列在屏幕上,Android
才得以呈现出多姿多彩的界面。于是根据对 Surface 的操作类型可以将 Android
的显示系统分为三个层次,如下图:

Window的内部机制

Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,说明View才是Window存在的实体,在实际使用中无法直接访问Window,对Window的访问必须通过WindowManager。

Window的添加过程

Window的添加过程需要通过WindowManager的addView来实现,WindowManager是一个接口,它的真正实现是WindowManagerImpl类。

@Override
public void addView(View view,ViewGroup.LayoutParams params){
  mGlobal.addView(view,params,mDisplay,mParentWindow);
}

@Override
public void updateViewLayout(View view,ViewGroup.LayoutParams params){
  mGlobal.updateViewLayout(view,params);
}

@Override
public void removeView(View view){
  mGlobal.removeView(view,false);
}

可以看到,WindowManagerImpl并没有直接实现Window的三大操作,而是全部交给了WindowManagerGlobal来处理,WindowManagerGlobal以工厂的形式向外提供自己的实例。WindowManagerGlobal的addView方法主要分为如下几步:

  • 检查参数是否合法,如果是子Window那么还需要调整一些布局参数
  • 创建ViewRootImpl并将View添加到列表中
  • 通过ViewRootImpl来更新界面并完成Window的添加过程

Window的删除过程

Window的删除过程和添加过程一样,都是先通过WindowManagerImpl后,在进一步通WindowManagerGlobal来实现的。里面用到一个dispatchDetachedFromWindow方法内部实现,这个方法主要做了四件事:

  • 垃圾回收相关的工作,比如清除数据和消息、移除回调
  • 通过Session的remove方法删除Window
  • 调用View的dispatchDetachedFromWindow方法,在内部会调用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()
  • 调用WindowManagerGlobal的doRemoveView方法刷新数据

Window的更新过程

主要是用到updateViewLayout方法,首先它需要更新View的LayoutParams并替换掉老的LayoutParams,接着再更新ViewRootImpl中的LayoutParams,这一步是通过ViewRootImpl的setLayoutParams方法来实现的。在ViewRootImpl中会通过scheduleTraversals方法对View进行重新布局,包括测量、布局、重绘这三个过程。

FLAG_NOT_FOCUSABLE

表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的Window。

8.2 Window的内部机制

  1. Window是一个抽象的概念,每个Window都对应一个View和ViewRootImpl,Window和View通过ViewRootImpl联系,Window是以View的形式存在。
  2. WindowManger是一个接口,真正实现是WindowManagerImpl类,WindowManagerImpl交给WindowMangerGlobal。WindowMangerGlobal以工厂的形式提供自己的实例。
  3. 此处是桥接模式,WindowManagerImple将所有的操作全部委托给WindowManagerGlobal实现。

8.2.1Window的添加过程

图片 4这里写图片描述

Window的创建过程

View是Android中的视图呈现方式,但是View不能单独存在,它必须附着在Window这个抽象的概念上面,因此有视图的地方就有Window。

Activity的Window创建过程

如何创建,需要了解Activity启动过程,比较复杂,但它最终由ActivityThred中的perfromLaunchActivity()来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用其attach方法为其关联运行过程中所依赖的一系列上下文环境变量。

在Activity的attach方法里,系统会创建Activity所属的Window对象并为其设置回调接口,Window对象的创建是通过PolicyManager的makeNewWindow方法实现的,对于Activity的setContentView的实现可以看出,Activity将具体实现交给了Window处理,而Window的具体实现是PhoneWindow,所以只需要看PhoneWindow相关逻辑即可,大致以下几个步骤:

  • 如果没有DecorView,那么就创建它。DecorView是一个FrameLayout,是Activity的顶级View,一般来说它的内部包含标题栏和内部栏。
  • 将View添加到DecorView的mContentParent中。
  • 回调Activity的onContentChanged方法通知Activity视图已经发生改变。Activity的onContentChanged是一个空实现。

经过上面三个步骤,DecorView已经被创建初始化完毕,Activity的布局文件已经成功添加到了DecorView的mContentParent中,但是这个时候DecorView还没有被WindowManager正式添加到Window中,真正被视图调用是在Activity的onResume方法,接着会调用Activity的makeVisible(),正是在makeVisible方法中,DecorView真正地完成了添加和显示这两个过程。

Dialog的Window创建过程

Dialog的Window的创建过程和Activity类似,有以下几个步骤:

  • 创建Window。同样是通过PolicyManager的makeNewWindow方法来完成的。
  • 初始化DecorView并将Dialog的视图添加到DecorView中。
  • 将DecorView添加到Window中并显示。在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中。

普通的Dialog有一个特殊之处,那就是必须采用Activity的Context,如果采用Application的Context,就会报错。

Toast的Window创建过程

Toast和Dialog不同,它的工作过程稍微复杂。首先Toast也是基于Window来实现的,但是由于Toast具有定时取消这一功能,所以系统采用了Handler。在Toast的内部有两类的IPC过程,第一类是Toast访问NotificationManagerService,第二类是NotificationManagerService回调Toast的TN接口。

Toast属于系统Window,它内部的视图有两种方式指定,一种是系统默认的样式,另一种是通过setView方法来指定一个自定义View,不管如何,它们都对应Toast的一个View类型的内部成员mNextView。Toast提供了show和cancel分别用于显示和隐藏Toast,它们的内部是一个IPC过程。

FLAG_NOT_TOUCH_MODAL

在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法收到单击事件。

8.2.1 Window的添加过程

  1. 检查参数是否合法
  2. 创建ViewRootImpl将View添加到列表中
  3. 通过ViewRootImple更新界面并完成Window的添加过程。
    1. 在这里的setViez中会通过requesetLayout完成异步刷新请求。
    2. 会通过WindowSession对WMS进行Binder调用,由WMS实现Window的添加。WMS会对每个应用保留一个单独的Session.

Windowmanager是一个接口,它的真正实现是WindowManagerImpl。WindowmanagerImpl将操作交给了windowManagerGlobal来处理。
WindowmanagerGlobal的addView方法主要分为以下几步

一般的开发过程中,我们操作的是 UI 框架层,对 Window 的操作通过
WindowManager 即可完成,而 WindowManagerService
作为系统级服务运行在一个单独的进程,所以 WindowManager 和
WindowManagerService 的交互是一个 IPC 过程。

阅读扩展

源于对掌握的Android开发基础点进行整理,罗列下已经总结的文章,从中可以看到技术积累的过程。
1,Android系统简介
2,ProGuard代码混淆
3,讲讲Handler+Looper+MessageQueue关系
4,Android图片加载库理解
5,谈谈Android运行时权限理解
6,EventBus初理解
7,Android
常见工具类
8,对于Fragment的一些理解
9,Android 四大组件之 ” Activity

10,Android 四大组件之” Service

11,Android 四大组件之“ BroadcastReceiver

12,Android 四大组件之” ContentProvider

13,讲讲 Android
事件拦截机制
14,Android
动画的理解
15,Android
生命周期和启动模式
16,Android IPC 机制
17,View 的事件体系
18,View 的工作原理
19,理解 Window 和
WindowManager
20,Activity
启动过程分析
21,Service
启动过程分析
22,Android 性能优化
23,Android 消息机制
24,Android
Bitmap相关
25,Android
线程和线程池
26,Android 中的 Drawable
和动画
27,RecylerView
中的装饰者模式
28,Android
触摸事件机制
29,Android
事件机制应用
30,Cordova
框架的一些理解
31,有关 Android
插件化思考
32,开发人员必备技能——单元测试

FLAG_SHOW_WHEN_LOCKED

开启此模式可以让Window显示在锁屏的界面上。

type参数表示Window的类型,Window有三种类型,分别是应用Window、子Window和系统Window。应用类Window对应着一个Activity。子Window不能单独存在,需要附属在特定的父Window之中,比如常见的Dialog。系统Window是需要声明权限在能创建的Window,比如Toast和系统状态栏。

Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window的上面。

应用Window的层级范围时1~99,子Window的层级范围时1000~1999,系统Window的层级范围是2000~2999,这些层级范围对应着WindowManager.LayoutParams的type参数

WindowManager所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager。

图片 5

8.2.2 Window的删除过程

  1. 通过findViewLocked查找待删除的View的索引
  2. 调用removeViewLocked删除
  3. 真正删除View的逻辑在dispatchDetachedFromWindow
    1. 垃圾回收
    2. 通过Session的remove删除Window,也是一个IPC过程
    3. onDetachedFromWindow(内部资源回收,比如停止线程、终止动画)
    4. 刷新mViews、mRoots、mDyingViews等数据

1.检查view是否存在,如果是子View还需要调整一些布局。
2.创建VIewmpl并将view添加到列表
mView存储所有的window所对应的view,mRoo存储的是所有Window所对应的ViewRootImpl,mParam存储的是所有Window所对应的布局参数。mDyingViews则存储正在被删除的view
3通过ViewRootImpl类似更新界面并完成Window的添加过程
这个步骤是通过viewRootImpl的setView方法来完成的,requestLayout,scheduleTraversals().接着通过windowSession来最终完成Window的添加过程mWindowSession.addToDpisplay().最后WindowManagerService来实现Window的添加。

Window 有三种类型,分别是应用 Window子 Window系统
Window
。应用类 Window 对应一个 Acitivity,子 Window
不能单独存在,需要依附在特定的父 Window 中,比如常见的一些 Dialog
就是一个子 Window。系统 Window是需要声明权限才能创建的 Window,比如
Toast 和系统状态栏都是系统 Window。

二、Window的内部机制

每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此Window并不是实际存在的,是以View的形式存在。

8.2.3 Window的更新过程

  1. 更新View的LayoutParams
  2. 更新ViewRootImpl的LayoutParams
  3. 对View重新布局,包括measure、layout、draw三个过程

8.3Window的创建过程
Activity启动后 ActivityThread中的performLaunchActivity()
来完成整个启动过程。并调用attach方法为其关联运行过程中所依赖的一系列上下文环境变量

Activity的attach方法里,window的创建是通过PolicyManager的makenewWindow创建的,activity实现了window的callback接口。policymanager的真正实现类是policy
makeNewWindow 方法中
使用了phoneWindow来创建window。、window创建完毕后,需要附着到activity上。这里看activity的setContentView方法其中调用了getWindow().setContentView(layoutid)

Window 层级
应用 Window 1~99
子 Window 1000~1999
系统 Window 2000~2999

2.1 Window的添加过程

Window的添加过程需要通过WindowManager的addView来实现,WindowManager是一个接口,它的真正实现是WindowManagerImplement类。其三大操作:

图片 6

可以发现,WindowManagerImpl并没有直接实现Window的三大操作,而是全部交给了WindowManagerGlobal来处理,WindowManagerGlobal以工厂的形式向外提供自己的实例。

WindowManagerGlobal的addView方法主要分为如下几步:

8.3 Window的创建过程

setConentView方法步骤如下
1.如果没有decorView就创建它
2.将View添加到DecorView的mContentParent中
3.回调activity的onContentChange方法通知Activity视图已经发生改变
4.在ActivityThread的handleResumeActivity方法中,首先调用了Activity的onresume方法,接着会调用Activity的makeVisible()在makeVieible方法中,DecorView真正的完成了添加和显示这两个过程

Window 是分层的,每个 Window 都有对应的
z-ordered,层级大的会覆盖在层级小的 Window 上面,这和 HTML 中的 z-index
概念是完全一致的。在三种 Window 中,应用 Window 层级范围是 1~99,子
Window 层级范围是 1000~1999,系统 Window 层级范围是
2000~2999,我们可以用一个表格来直观的表示:

1、检查参数是否合法,如果是子Window那么还需要调整一些布局参数

图片 7

8.3.1 Activity的window创建过程

  1. Activity的启动过程最终会由ActivityThread中的performLaunchActivity来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用它的attach方法为其关联运行过程中所依赖的一系列上下文环境变量;
  2. Activity实现了Window的Callback接口,当window接收到外界的状态变化时就会回调Activity的方法,例如onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent等;
  3. Activity的Window是由PolicyManager来创建的,它的真正实现是Policy类,它会新建一个PhoneWindow对象,Activity的setContentView的实现是由PhoneWindow来实现的;
  4. Activity的顶级View是DecorView,它本质上是一个FrameLayout。如果没有DecorView,那么PhoneWindow会先创建一个DecorView,然后加载具体的布局文件并将view添加到DecorView的mContentParent中,最后就是回调Activity的onContentChanged通知Activity视图已经发生了变化;
  5. 让WindowManager能够识别DecorView,在ActivityThread调用handleResumeActivity方法时,首先会调用Activity的onResume方法,然后会调用makeVisible方法,这个方法中DecorView真正地完成了添加和显示过程。
Window 层级
应用 Window 1~99
子 Window 1000~1999
系统 Window 2000~2999

2、创建ViewRootImpl并将View添加到列表中

图片 8

在上面的声明中,mViews存储的是所有Window所对应的View,mRoots所存储的是所有Window所对应的ViewRootImpl,mParams存储的是所有Window所对应的布局参数,而mDyingViews则存储了那些正在被删除的View对象,或者说是那些已经被调用removeView方法但是删除操作还未完成的Window对象。在addView中通过如下方式将Window的一系列对象添加到列表中:

图片 9

8.3.2 Dialog的Window创建过程

  1. 创建Window 。通过PolicyManager的makeNewWindow完成
  2. setContentView,初始化DecorView,并将Dialog视图放到DecorView中
  3. 将DecorView放到Window中显示
  4. 当Dialog关闭,通过WindowManger移除DecorView
  5. 普通Dialog必须才有Activity的Context,应用Token只有Activity拥有。系统window不需要应用Token

这些层级范围对应着 WindowManager.LayoutParams 的 type 参数,如果想要
Window 位于所有 Window 的最顶层,那么采用较大的层级即可,很显然系统
Window 的层级是最大的,当我们采用系统层级时,需要声明权限。

3、通过ViewRootImpl来更新界面并完成Window的添加过程

这个步骤由ViewRootImpl的setView方法来完成:

图片 10

接着会通过WindowSession最终来完成Window的添加过程。

图片 11

mWindowSession的类型是IWindowSession,它是一个Binder对象,真正的实现类是Session

在Session内部会通过WindowManagerService来实现Window的添加:

图片 12

8.3.3 Toast的Window创建过程

  1. Toast属于系统Window,它内部的视图由两种方式指定:一种是系统默认的演示;另一种是通过setView方法来指定一个自定义的View。
  2. Toast的显示和隐藏是IPC过程,都需要NotificationManagerService来实现。在Toast和NMS进行IPC过程时,NMS会跨进程回调Toast中的TN类中的方法,TN类是一个Binder类,运行在Binder线程池中,所以需要通过Handler将其切换到当前发送Toast请求所在的线程,所以Toast无法在没有Looper的线程中弹出。
  3. 对于非系统应用来说,mToastQueue最多能同时存在50个ToastRecord,这样做是为了防止DOS(Denial
    of
    Service,拒绝服务)。因为如果某个应用弹出太多的Toast会导致其他应用没有机会弹出Toast。

我们对 Window 的操作是通过 WindowManager 来完成的,WindowManager
是一个接口,它继承自只有三个方法的 ViewManager 接口:

2.2 Window的删除过程

Window的删除过程和添加过程一样,都是先通过WindowManagerImpl后,再进一步通过WindowManagerGlobal来实现的。下面是WindowManagerGlobal的removeView的实现:

图片 13

removeView通过findViewLocked来查找待删除的View的索引,这个查找过程就是建立的数组遍历,然后再调用removeViewLocked来做进一步的删除:

图片 14

图片 15

removeViewLocked是通过ViewRootImpl来完成删除操作的。在WindowManager中提供了两种删除接口removeView和removeViewImmediate,它们分别表示异步删除和同步删除。

removeView是由ViewRootImpl的die方法来完成。而die方法只是发送了一个请求删除的消息后就立刻返回了,这个时候View并没有完成删除操作,所以最好会将其添加到mDyingViews中,mDyingViews表示待删除的View列表。

图片 16

图片 17

在de方法内部只是做了简单的判断,如果是异步删除,那么就发送一个MSG_DIE的消息,ViewRootImpl中的Handler会处理此消息并调用doDie方法,如果是同步删除(立即删除),那么久不乏消息直接调用doDie方法,这就是两种删除方式的区别。在doDie内部会调用dispatchDetachedFromWindow方法,这个方法主要做四件事:

1、垃圾回收相关的工作,比如清除数据和消息、移除回调

2、通过Session的remove方法删除Window:mWindowSession.remove(mWindow),这同样是一个IPC过程,最终会调用WindowManagerService的removeWindow方法

3、戴傲勇View的dispatchDetachedFromWindow方法,在内部会调用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。当View从window中移除时,会调用onDetachedFromWindow,可在这个方法内部做一些资源回收的工作。

4、调用WindowManagerGlobal的doRemoveView方法刷新数据,,包括mRoots、mParams以及mDyingViews,需要将当前Window所关联的这三类对象从列表中删除。

public interface ViewManager{ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view);}

2.3 Window的更新

查看WindowManagerGlobal的updateViewLayout方法:

图片 18

图片 19

首先需要更新View的LayoutParams并替换掉老的LayoutParams,接着在更新ViewRootImpl中的LayoutParams。接着再更新ViewRootImpl中的LayoutParams,这一步是通过ViewRootImpl的setLayoutParams方法来实现的。在ViewRootImpl中会通过scheduleTraversals方法来对View重新布局,包括测量、布局、重绘这三个过程。在通过WindowSession来更新Window的视图,这个过程是有WindowManagerService的relayoutWindow来具体实现。

这三个方法其实就是 WindowManager 对外提供的主要功能,即添加 View、更新
View 和删除 View。接下来来看一个通过 WindowManager 添加 Window
的例子,代码如下:

三、Window的创建过程

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button floatingButton = new Button; floatingButton.setText; WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT ); // flag 设置 Window 属性 layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; // type 设置 Window 类别 layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; layoutParams.gravity = Gravity.CENTER; WindowManager windowManager = getWindowManager(); windowManager.addView(floatingButton, layoutParams); }}

3.1 Activity的Window创建过程

在Activity的启动过程中,会调用attach方法,这个方法会创建Activity所属的Window对象并为其设置回调接口,Window对象的创建时通过PolicyManager的makeNewWindow方法实现的,由于Activity实现了Window的Callback接口,所以当Window接收到外界的状态改变时就会回调Activity的方法。代码如下:

图片 20

从上面可看出,Activity的Window是通过PolicyManager的一个工厂方法来创建的,但是从PolicyManager的类名可以看出,他不是一个普通类,它是一个策略类。PolicyManager中实现的几个工厂方法全部在策略接口中IPolicy中声明,IPolicy的定义如下:

图片 21

而方法makeNewWindow实现如下:

图片 22

分析Activity的视图是怎么附属在Window上:

由于Activity的视图由setContentView方法提供,我们只需要看setContentView方法的实现即可:

图片 23

可看出Activity将具体实现交给了window处理,而Window的具体实现是PhoneWindow,所以只需看PhoneWindow的相关逻辑即可,其setContentView方法遵循如下步骤:

1、如果没有DecorView,那么就创建它

DecorView是Activity的顶级View,一般来说它的内部包含标题栏和内部栏,但是这个会随着主题的变换而发生改变,不管怎么样,内容栏是一定要存在的,并且内容来具体固定的id,那就是“content”,它的完整id是android.R.id.content。DecorView的创建过程由installDecor方法来完成,在内部会通过generateLayout方法来直接创建DecorView。

图片 24

为了初始化DecorView的结构,PhoneWindow还需通过generateLayout方法来加载具体的布局文件到DecorView中,具体的布局文件和系统版本以及主题有关:

图片 25

其中ID_ANDROID_CONTENT的定义如下,这个id所对应的ViewGroup就是mContentParent:

图片 26

2、将View添加到DecorView的mContentParent中

直接将Activity的视图添加到DecorView的mContentParent中即可:没LayoutInflater.inflate(layoutResID,MContentParent)。

3、回调Activity的onContentChanged方法通知Activity视图已经发生改变

可以直接在Activity的onContentChanged方法是个空实现,可在子Activity中处理这个回调:

图片 27

经过上面三个步骤,activity的布局文件已经成功添加到了DecorView的mContentParent中,但是这个时候DecorView还没有被WindowManager正式添加到Window中。只有在ActivityThread的handleResumeActivity方法中,首先会调用Aactivity的onResume方法,接着会调用Activity的makeVisible(),正是在makeVisible方法中,DecorView真正完成了添加和显示这两个过程:

图片 28

代码中并没有调用 Activity 的 setContentView 方法,而是直接通过
WindowManager 添加 Window,其中设置为系统 Window,所以应该添加权限:

3.2 Dialog的Window创建过程

Dialog的Window创建过程和Activity类似,有如下步骤:

1、创建Window

Dialog中Window的创建同样是通过PolicyManager的makeNewWindow方法来完成的:

图片 29

2、初始化DecorView并将Dialog的视图添加到DecorView中

图片 30

3、将DecorView添加到Window中并显示

在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中,如下所示:

图片 31

当Dialog被关闭时,它会通过WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor).

普通的Dialog有一个特殊之处,那就是必须采用Activity的Context,如果采用Application的Context,那么就会报错。

图片 32

图片 33

 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

3.3 Toast的Window创建过程

Toast也是基于Window来实现的,但是由于Toast具有定时取消这一功能,所以系统采用了Handler。

Toast内部有两类IPC过程,第一类是Toast访问NotificationManagerService(NMS),第二类是NotificationManagerService回调Toast里的TN接口。

Toast属于系统Window,它内部的视图由两种方式指定,一种是系统默认的样式,另一种是通过setView方法来指定一个自定义View,不管如何,他们都对应Toast的一个View类型的内部成员mNextView。其show与cancel方法实现如下:

图片 34

显示和隐藏Toast都需要通过NMS来实现,由于NMS运行在系统的进程中,所以只能通过远程调用的方式来显示和隐藏Toast。而TN这个类,它是一个Binder类,在Toast和NMS进行IPC的过程中,当NMS处理Toast的显示或隐藏请求时会跨进程回调TN中的方法,这个时候由于TN运行在Binder线程中,所以需要通过Handler将其切换到当前线程中。

Toast的显示过程:

图片 35

NMS的enqueueToast方法的第一个参数表示当前应用的包名,第二个参数tn表示远程回调,第三个参数表示Toast的时长。enqueueToast首先将Toast请求封装为ToastRecord对象并将其添加到一个名为mToastQueue的队列中。

当ToastRecord被添加到mToastQueue中后,NMS就会通过showNextToastLocked方法来显示当前的Toast。其中,Toast的显示是由TsatRecord的callback来完成的,这个callback实际上就是Toast中的TN对象的远程Binder,通过callback来访问TN中的方法是需要跨进程来完成的,最终被调用的TN中的方法虎运行在发起Toast请求的应用的Binder线程池中。

图片 36

Toast显示以后,NMS还会通过scheduleTimeoutLocked方法来发送一个延时消息,具体的延时取决于Toast的时长:

图片 37

在上面额代码证,LONG_DELAY是3.5s,而SHORT_DELAY是2s。延迟相应的时间后,NMS会通过cancelToastLocked方法来隐藏Toast并将其从mToastQueue中移除,这个时候如果mToastQueue中还有其他Toast,那么NMS就继续显示其他Toast。

Toast的隐藏也是通过ToastRecord的callback来完成:

图片 38

Toast的显示和隐藏过程实际上是通过Toast中的TN这个类来实现的,它有两个方法show和hide,分别对应Toast的显示和隐藏。由于这两个方法是被NMS以跨进程的方式调用,因此它们运行在线程池中:

图片 39

mShow和mHide是两个Runnable,分别调用了handleShow和handleHide方法,TN的handleShow中会将Toast的视图添加到Window中:

图片 40

而NT的handleHide中会将Toast的视图从Window中移除:

图片 41

效果如下:

图片 42这里写图片描述

第二个界面是锁屏界面,由于按钮是处于较大层级的系统 Window
中的,所以可以看到 button。

在实际使用中无法直接访问 Window,对 Window 的访问必须通过
WindowManager。WindowManager 提供的三个接口方法
addView、updateViewLayout 以及 removeView 都是针对 View 的,这说明 View
才是 Window 存在的实体,上面例子实现了 Window 的添加,WindowManager
是一个接口,它的真正实现是 WindowManagerImpl 类:

 @Override public void addView(View view, ViewGroup.LayoutParams params){ mGlobal.addView(view, params, mDisplay, mParentWindow); } @Override public void updateViewLayout(View view, ViewGroup.LayoutParams params){ mGlobal.updateViewLayout(view, params); } @Override public void removeView(View view){ mGlobal.removeView(view, false); }

可以看到,WindowManagerImpl 并没有直接实现 Window 的三大操作,而是交给了
WindowManagerGlobal 来处理,下面以 addView 为例,分析一下
WindowManagerGlobal 中的实现过程:

1、检查参数合法性,如果是子 Window 做适当调整

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){ parentWindow.adjustLayoutParamsForSubWindow;}

2、创建 ViewRootImpl 并将 View 添加到集合中

在 WindowManagerGlobal 内部有如下几个集合比较重要:

private final ArrayList<View> mViews = new ArrayList<View>();private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();private final ArraySet<View> mDyingViews = new ArraySet<View>();
集合 存储内容
mViews Window 所对应的 View
mRoots Window 所对应的 ViewRootImpl
mParams Window 所对应的布局参数
mDyingViews 正在被删除的 View 对象

其中 mViews 存储的是所有 Window 所对应的 View,mRoots 存储的是所有
Window 所对应的 ViewRootImpl,mParams 存储的是所有 Window
所对应的布局参数,mDyingViews 存储了那些正在被删除的 View
对象,或者说是那些已经调用了 removeView 方法但是操作删除还未完成的
Window 对象,可以通过表格直观的表示:

集合 存储内容
mViews Window 所对应的 View
mRoots Window 所对应的 ViewRootImpl
mParams Window 所对应的布局参数
mDyingViews 正在被删除的 View 对象

addView 操作时会将相关对象添加到对应集合中:

root = new ViewRootImpl(view.getContext(),display);view.setLayoutParams;mViews.add;mRoots.add;mParams.add;

3、通过 ViewRootImpl 来更新界面并完成 Window 的添加过程

在学习 View 的工作原理时,我们知道 View 的绘制过程是由 ViewRootImpl
来完成的,这里当然也不例外,具体是通过 ViewRootImpl 的 setView
方法来实现的。在 setView 内部会通过 requestLayout
来完成异步刷新请求,如下:

public void requestLayout(){ if(!mHandingLayoutInLayoutRequest){ checkThread(); mLayoutRequested = true; scheduleTraversals(); }}

可以看到 scheduleTraversals 方法是 View 绘制的入口,继续查看它的实现:

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mInputChannel);

mWindowSession 的类型是 IWindowSession,它是一个 Binder
对象,真正的实现类是 Session,这也就是之前提到的 IPC 调用的位置。在
Session 内部会通过 WindowManagerService 来实现 Window 的添加,代码如下:

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams, attrs, int viewVisibility, int displayId, Rect outContentInsets, InputChannel outInputChannel){ return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outInputChannel);}

终于,Window 的添加请求移交给 WindowManagerService 手上了,在
WindowManagerService 内部会为每一个应用保留一个单独的 Session,具体
Window 在 WindowManagerService
内部是怎么添加的,就不对其进一步的分析,因为到此为止我们对 Window
的添加这一从应用层到 Framework 的流程已经清楚了,下面通过图示总结一下:

图片 43这里写图片描述

理解了 Window 的添加过程,Window
的删除过程和更新过程都是类似的,也就容易理解了,它们最终都会通过一个 IPC
过程将操作移交给 WindowManagerService 这个位于 Framework
层的窗口管理服务来处理。

View 是 Android 中的视图的呈现方式,但是 View 不能单独存在,它必须附着在
Window 这个抽象的概念上面,因此有视图的地方就有
Window。哪些地方有视图呢?Android 可以提供视图的地方有
Activity、Dialog、Toast,除此之外,还有一些依托 Window
而实现的视图,比如 PopUpWindow、菜单,它们也是视图,有视图的地方就有
Window,因此 Activity、Dialog、Toast 等视图都对应着一个
Window。这也是面试中常问到的一个知识点:一个应用中有多少个
Window?下面分别分析 Activity、Dialog以及 Toast 的 Window 创建过程。

1、 Activity 的 Window 创建过程

在了解了 Window 的概念及意义后,我们自然就清楚 Activity 的 Window
创建时机,Window 本质就是一块显示区域,所以关于 Activity 的 Window
创建应该发生在 Activity 的启动过程,Activity 的启动过程很复杂,最终会由
ActivityThread 中的 performLaunchActivity()
来完成整个启动过程,在这个方法内部会通过类加载器创建 Activity
的实例对象,并调用其 attach
方法为其关联运行过程中所依赖的一系列上下文环境变量。

Activity 的 Window 创建就发生在 attach 方法里,系统会创建 Activity
所属的 Window 对象并为其设置回调接口,代码如下:

mWindow = PolicyManager.makeNewWindow;mWindow.setCallback;mWindow.setOnWindowDismissedCallback;mWindow.getLayoutInflater().setPrivateFactory;...

可以看到, Window 对象的创建是通过 PolicyManager 的 makeNewWindow
方法实现的,由于 Activity 实现了 Window 的 Callback 接口,因此当 Window
接受到外界的状态改变时就会回调 Activity 的方法。Callback
接口中的方法很多,有几个是我们非常熟悉的,如
onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent 等等。

再回到 Window 的创建,可以看到 Activity 的 Window 是通过 PolicyManager
的一个工厂方法来创建的,但是在 PolicyManager 的实际调用中,PolicyManager
的真正实现是 Policy 类,Policy 类中的 makeNewWindow 方法的实现如下:

public Window makeNewWindow(Context context){ return new PhoneWindow;}

可以看出,Window 的具体实现类的确是 PhoneWindow。到这里 Window
以及创建完成了,下面分析 Activity 的视图是怎么附属到 Window 上的,而
Activity 的视图由 setContentView 提供,所以从 setContentView
入手,它的源码如下:

public void setContentView(int layoutResID){ getWindow().setContentView(layoutResID); initWindowDecorActionBar();}

可以看到,Activity 将具体实现交给了 Window,而 Window 的具体实现是
PhoneWindow,所以只需要看 PhoneWindow 的相关逻辑即可,它的处理步骤如下:

、如果没有 DecorView 就创建一个

DecorView 是 Activity 中的顶级 View,是一个
FrameLayout,一般来说它的内部包含标题栏和内容栏,但是这个会随着主题的变化而改变,不管怎么样,内容栏是一定存在的,并且有固定的
id:”android.R.id.content”,在 PhoneWindow 中,通过 generateDecor
方法创建 DecorView,通过 generateLayout 初始化主题有关布局。

、将 View 添加到 DecorView 的 mContentParent 中

这一步较为简单,直接将 Activity 的视图添加到 DecorView 的 mContentParent
中即可,由此可以理解 Activity 的 setContentView
这个方法的来历了,为什么不叫 setView 呢?因为 Activity
的布局文件只是被添加到 DecorView 的 mContentParent 中,因此叫
setContentView 更加具体准确。

、回调 Activity 的 onContentChanged 方法通知 Activity 视图已经发生改变

前面分析到 Activity 实现了 Window 的 Callback 接口,这里当 Activity
的视图已经被添加到 DecorView 的 mContentParent 中了,需要通知
Activity,使其方便做相关的处理。

经过上面的三个步骤,DecorView 已经被创建并初始化完毕,Activity
的布局文件也已经成功添加到了 DecorView 的 mContentParent
中,但是这个时候 DecorView 还没有被 WindowManager 正式添加到 Window
中。在 ActivityThread 的 handleResumeActivity 方法中,首先会调用 Acitivy
的 onResume 方法,接着会调用 Acitivy 的 makeVisible() 方法,正是在
makeVisible 方法中,DecorView 才真正的完成了显示过程,到这里 Activity
的视图才能被用户看到,如下:

void makeVisible(){ if(!mWindowAdded){ ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes; mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE);}

2、 Dialog 的 Window 创建过程

Dialog 的 Window 的创建过程与 Activity 类似,步骤如下:

、创建 Window

Dialog 中 Window 同样是通过 PolicyManager 的 makeNewWindow
方法来完成的,创建后的对象也是 PhoneWindow。

、初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中

这个过程也和 Activity 类似,都是通过 Window 去添加指定布局文件:

public void setContentView(int layoutResID){ mWindow.setContentView(layoutResID);}

、将 DecorView 添加到 Window 中并显示

在 Dialog 的 show 方法中,会通过 WindowManager 将 DecorView 添加到
Window 中,如下:

mWindowManager.addView(mDecor, 1);mShowing = true;

从上面三个步骤可以发现,Dialog 的 Window 创建过程和 Activity
创建过程很类似,当 Dialog 关闭时,它会通过 WindowManager 来移除
DecorView。普通的 Dialog 必须采用 Activity 的 Context,如果采用
Application 的 Context 就会报错。这是因为没有应用 token 导致的,而应用
token 一般只有 Activity 拥有,另外,系统 Window 比较特殊,可以不需要
token。

3、 Toast 的 Window 创建过程

Toast 与 Dialog 不同,它的工作过程稍显复杂,首先 Toast 也是基于 Window
来实现的,但是由于 Toast 具有定时取消这一功能,所以系统采用了
Handler。在 Toast 内部有两类 IPC 过程,一是 Toast 访问
NotificationManagerService,第二类是 NotificationManagerService 回调
Toast 里的 TN 接口。NotificationManagerService 同 WindowManagerService
一样,都是位于 Framework 层的服务,下面简称 NotificationManagerService
为 NMS。

Toast 属于系统 Window,它内部的视图可以是系统默认样式也可以通过 setView
方法自定义 View,不管如何,它们都对应 Toast 的内部成员 mNextView,Toast
提供 show 和 cancel 分别用于显示和隐藏 Toast,它们内部是一个 IPC
过程,代码如下:

 public void show() { if (mNextView == null) { throw new RuntimeException("setView must have been called"); } INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; try { service.enqueueToast(pkg, tn, mDuration); } catch (RemoteException e) { // Empty } }

 public void cancel() { mTN.hide(); try { getService().cancelToast(mContext.getPackageName; } catch (RemoteException e) { // Empty } }

可以看到,显示和隐藏 Toast 都需要通过 NMS 来实现,TN 是一个 Binder
类,当 NMS 处理 Toast 的显示或隐藏请求时会跨进程回调 TN 中的方法。由于
TN 运行在 Binder 线程池中,所以需要通过 Handler
将其切换到当前线程中,这里的当前线程指的是发送 Toast 请求所在的线程。

代码在显示 Toast 中调用了 NMS 的 enqueueToast 方法, enqueueToast
方法内部将 Toast 请求封装为 ToastRecord 对象并将其添加到一个名为
mToastQueue 的队列中,对于非系统应用来说,mToastQueue 中最多同时存在 50
个 ToastRecord,用于防止 DOS (Denial of Service 拒绝服务)。

当 ToastRecord 添加到 mToastQueue 中后,NMS 就会通过 showNextToastLocked
方法来顺序显示 Toast,但是 Toast 真正的显示并不是在 NMS 中完成的,而是由
ToastRecord 的 callback 来完成的:

void showNextToastLocked (){ ToastRecord record = mToastQueue.get; while(record != null){ if Slog.d(TAG,"show pkg=" + record.pkg + "callback=" + record.callback); try{ record.callback.show(); scheduleTimeoutLocked; return; } ...}

这个 callback 就是 Toast 中的 TN 对象的远程 Binder,最终被调用的 TN
中的方法会运行在发起 Toast 请求的应用的 Binder
线程池中,从以上代码可以看出,Toast 显示以后,NMS 还调用了
sheduleTimeoutLocked 方法,此方法中首先进行延时,具体的延时时长取决于
Toast 的显示时长,延迟相应时间后,NMS 会通过 cancelToastLocked
方法来隐藏 Toast 并将它从 mToastQueue 中移除,这时如果 mToastQueue
中还有其他 Toast,那么 NMS 就继续显示其他 Toast。Toast 的隐藏也是通过
ToastRecord 的 callback 来完成的,同样也是一次 IPC 过程。

从上面的分析,可以知道 NMS 只是起到了管理 Toast
队列及其延时的效果,Toast 的显示和隐藏过程实际上是通过 Toast 的 TN
类来实现的,TN 类的两个方法 show 和 hide,是被 NMS
以跨进程的方式调用的,因此它们运行在 Binder
线程池中,为了将执行环境切换到 Toast 请求所在的线程,在它们内部使用了
Handler。

Toast 毕竟是要在 Window 中实现的,因此它最终还是要依附于
WindowManager,TN 的 handleShow 中代码如下:

mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);mWM.addView(mView, mParams);

TN 的 handleHide 方法同样需要通过 WindowManager
来实现视图的移除,这里就不再贴出。

下面让我们再次认清一些概念:任何 View 都是附属在一个 Window
上面的,Window 表示一个窗口的概念,也是一个抽象的概念,Window
并不是实际存在的,它是以 View 的形式存在的。WindowManager
是外界也就是我们访问 Window 的入口,Window 的具体实现位于
WindowManagerService 中,WindowManagerService 和 WindowManager
的交互是一个 IPC 过程。

相信读完本文后,对 Window 会有一个更加清晰的认识,同时能够深刻理解
Window 和 View 的依赖关系。

参考文章:

《Android 开发艺术探索》

发表评论

电子邮件地址不会被公开。 必填项已用*标注