带你彻底理解 Android 中的 Window 和 WindowManager

澳门新葡亰3522平台游戏 4

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

Window和Window内部机制

Window表示一个窗口的概念。Android所有的View都是是通过Window呈现的,比如Activity,Dialog,Toast等。如果有在桌面上创建一个悬浮窗的需求,就需要用到Window.Window也是View的直接管理者。

Window是一个抽象类,PhoneWindow是其具体实现。Windwo创建通过WindowManager,而具体实现是在WindowManagerService中,这两者交互是个IPC过程。

澳门新葡亰3522平台游戏 1

WindowManager常用的功能:

addView(),updateViewLayout(),removeView();

WindowManagerService

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

澳门新葡亰3522平台游戏 2

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

Window内部机制

Window是一个抽象的概念,每一个Window都对应着一个View和ViewRootImpl。Window和View通过ViewRootImpl建立联系。所以Window不是单独存在的,他是以View的形式存在。

Window 分类

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

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

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

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

Window的添加过程

Window添加过程,通过WindowManager的addView,而WindowManager是一个接口,真正实现它的是WindowManagerImpl。在WindowManagerImpl的三大操作中(增,更,查),全部委托给WindowManagerGlobal来实现。
在WindowManagerGlobal中创建ViewRootImpl并将View添加到列表中。
通过ViewRootImpl来更细页面,在通过WindowSession(IWindowSession),它是一个Binder对象。这就是一次IPC调用。在Session内部通过WindowManagerService来实现Window的添加。

WindowManager 使用

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

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

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

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Button floatingButton = new Button(this);
        floatingButton.setText("button");
        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);

    }
}

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

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

效果如下:

澳门新葡亰3522平台游戏 3

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

Window的删除过程

通过WindowMImpl,在通过WindowMGlobal中的removeView实现,在这remove是通过ViewRootImpl来完成删除操作的。

WindowManager 的内部机制

在实际使用中无法直接访问 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澳门新葡亰3522平台游戏, 来处理,下面以 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(wparams);
}

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
对象,或者说是那些已经调用了 removeView 方法但是操作删除还未完成的
Window 对象,可以通过表格直观的表示:

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

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

root = new ViewRootImpl(view.getContext(),display);
view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

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 的流程已经清楚了,下面通过图示总结一下:

澳门新葡亰3522平台游戏 4

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

Window的更新过程

通过WindowMImpl,在通过WindowMGlobal中的updateViewLatyout实现,在通过更新ViewRootImpl中的LayoutParams,通过setLayoutParams方法来实现。这个过程最终是由WindowManagerService的relayoutWindow()来具体实现,也是一个IPC过程。