彻底理解Android系统的Window和WindowManager

时间:2018-09-02 23:32:50  来源:本站收集整理  作者:学无止境

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

 

20170401204454629.jpg

 

WindowManagerService

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

 

20170401210007652.png

 

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

 

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 的层级是最大的,当我们采用系统层级时,需要声明权限。

 

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"/>

 

效果如下:

 

20170401223702127.png

 

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

 

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

 

20170402131427522.png

 

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

 

相关文章

文章评论

共有  0  位网友发表了评论 此处只显示部分留言 点击查看完整评论页面