Android开发——带你彻底理解 Window 和 WindowManager

0. 前言  

有时候我们需要在桌面上显示一个类似悬浮窗的东西需要用Window来实现Window是一个抽象类,表示一个窗口,它的具体实现类是PhoneWindow,实现位于WindowManagerService中,它的职责就是管理系统中的所有窗口。窗口在 Android 中就是绘制的画布Surface,当一块Surface显示在屏幕上时,就是用户所看到的窗口了。

WindowManagerService 添加一个窗口的过程,其实就是 WindowManagerService 为其分配一块Surface的过程,一块块的 Surface WindowManagerService 的管理下有序的排列在屏幕上,Android 才得以呈现出多姿多彩的界面。

本文转载自http://blog.csdn.net/yhaolpz/article/details/68936932。

 

1.  Window 分类

Window有三种类型,分别是应用Window、子Window和系统Window

应用Window对应一个Acitivity。层级范围是1~99

Window不能单独存在,需要依附在特定的父Window中,比如常见的一些Dialog就是一个子Window。子Window层级范围是1000~1999

系统Window是需要声明权限才能创建的Window,比如Toast和系统状态栏都是系统Window。系统Window层级范围是2000~2999

 

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

 

2.  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);

    }
}

代码中并没有调setContentView方法,而是直接通过WindowManager添加Window,其中设置为系统Window,所以应该添加权限<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

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



3.  WindowManager的内部机制

WindowManager 提供的三个接口方法都是针对View的,这说明View才是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中的实现过程。


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

首先检查参数的合法性,继而将View添加到集合中,集合如下所示:

//WindowManagerGlobal内部维护的集合
private final ArrayList<View> mViews =new ArrayList<View>();
private final ArrayList<ViewRootImpl>mRoots = new ArrayList<ViewRootImpl>();
private finalArrayList<WindowManager.LayoutParams> mParams = newArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews= new ArraySet<View>();

其中mViews存储的是所有Window所对应的ViewmRoots存储的是所有Window所对应的ViewRootImplmParams存储的是所有Window所对应的布局参数,mDyingViews存储了那些正在被删除的View对象,或者说是那些已经调用了removeView方法但是操作删除还未完成的Window对象。

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

root = newViewRootImpl(view.getContext(),display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

 

3.2  通过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, intseq, WindowManager.LayoutParams, attrs, int viewVisibility,
                  int displayId, RectoutContentInsets, InputChannel outInputChannel){
   returnmService.addWindow(this, window, seq, attrs, viewVisibility, displayId,outContentInsets, outInputChannel);
}

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


 

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

 

4.  Window的创建过程

View不能单独存在,它必须附着在Window这个抽象的概念上面,因此有视图的地方就有 Window

Android有视图的地方有 ActivityDialogToastPopUpWindow等,它们都对应着一个Window。这也是面试中常问到的一个知识点,一个应用中有多少个Window

下面分别分析 ActivityDialog以及ToastWindow创建过程。

 

4.1  ActivityWindow创建过程

Window 本质就是一块显示区域,所以关于 Activity Window 创建应该发生在 Activity 的启动过程Activity 的启动过程很复杂,最终会由 ActivityThread 中的 performLaunchActivity() 来完成整个启动过程,在这个方法内部会通过类加载器创建 Activity 的实例对象并调用其 attach()方法为其关联运行过程中所依赖的一系列上下文环境变量。Activity的window创建就发生在attach方法里,系统会创建Activity所属的Window对象并为其设置回调接口。

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

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

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

public Window makeNewWindow(Context context){
   returnnew PhoneWindow(context);
}

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

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

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

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

2 View 添加到DecorViewmContentParent

3)通过接口回调 Activity onContentChanged 方法通知 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);
}


4.2  DialogWindow创建过程

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

1Dialog Window 同样是通过PolicyManager makeNewWindow()方法来创建的,创建Window后的对象也是 PhoneWindow

2初始化 DecorView 并将 Dialog 的视图添加到DecorView ,这个过程也和 Activity类似,都是通过 Window 去添加指定布局文件:

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

3 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

 

4.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 的内部成员 mNextViewToast 提供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 voidcancel() {
        mTN.hide();
 
        try {
           getService().cancelToast(mContext.getPackageName(), mTN);
        } 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(0);
  while(record != null){
      if(DBG)
         Slog.d(TAG,"show pkg=" + record.pkg + "callback=" +record.callback);
      try{
         record.callback.show();
         scheduleTimeoutLocked(record);
         return;
        }
 
      ...
}

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

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

Toast 毕竟是要在 Window 中实现的,因此它最终还是要依附于 WindowManagerTN handleShow 中代码如下。TN handleHide 方法同样需要通过 WindowManager 来实现视图的移除,这里就不再贴出。

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

总结

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

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值