0%

理解 Window 和 WindowManager(三):Window 的创建过程

1. Activity 的 Window 创建过程

  • Activity 中 Window 的创建过程与 Activity 的启动过程紧密相关。Activity 的启动过程很复杂,最终会由 ActivityThread 中的 performLaunchActivity() 方法来完成整个启动过程

  • Activity 实现了 Window 的 Callback 接口,Callback 接口中的方法很多,常见的有 onAttachedToWindow()onDetachedFromWindow()dispatchTouchEvent() 等方法

  • 分析源码,Activity 的 Window 是通过 PolicyManager 的实现类 Policy 的 makeNewWindow() 方法创建的,由方法实现可知,Window 的具体实现类是 PhoneWindow:

    1
    2
    3
    public Window makeNewWindow(Context context) {
    return new PhoneWindow(context);
    }
  • Activity 的视图附属在 Window 上的原理(即 setContenView() 方法的原理),因为 Window 的具体实现是 PhoneWindow,所以 PhoneWindow 的 setContentView() 方法的主要步骤:

    1
    2
    3
    4
    public void setContentView(int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
    }
    1. 如果没有 DecorView,那么就创建它

      • DecorView 是一个 FrameLayout,而且是 Activity 中的顶级 View,一般来说它的内部包含标题栏和内部栏,但这个会随着主题的改变而发生改变
      • 无论如何,内容栏是一定要存在的,并且内容栏有具体固定的 id,即 content,它的完整 id 是 android.R.id.content
    2. 将 View 添加到 DecorView 的 mContentParent

      • 直接将 Activity 的视图添加到 DecorView 的 mContentParent 中:mLayoutInflater.inflate(layoutResID, mContentParent);
      • 因为 Activity 的布局文件只是被添加到 DecorView 的 mContentParent 中,因此不加 setView() 而是叫 setContentView() 更准确
    3. 回调 Activity 的 onContentChanged() 方法通知 Activity 视图已经发生改变

      • 由于 Activity 实现了 Window 的 Callback 接口,表示 Activity 的布局文件已经被添加到 DecorView 的 mContentParent 了,于是需要通知 Activity,使其可以做相应的处理

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

        1
        2
        3
        4
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
        }
    4. 然后在 ActivityThread 的 handleResumeActivity() 方法中,首先会调用 Activity 的 onResume() 方法,接着会调用 Activity 的 makeVisible() 方法,正是在 makeVisible() 方法中,DecorView 真正地完成了添加和显示这两个过程,到这里 Activity 的视图才能被用户看到

      1
      2
      3
      4
      5
      6
      7
      8
      void makeVisible() {
      if (!mWindowAdded) {
      ViewManager vm = getWindowManager();
      vm.addView(mDecor, getWindow().getAttributes());
      mWindowAdded = true;
      }
      mDecor.setVisibility(View.VISIBLE);
      }

2. Dialog 的 Window 创建过程

  1. 创建 Window

    • Dialog 中 Window 的创建同样是通过 PolicyManager 的 makeNewWindow() 方法来完成的
    • 创建后的对象实际上就是 PhoneWindow,这个过程和 Activity 的 Window 的创建过程是一致的
  2. 初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中

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

      1
      2
      3
      public void setContentView(int layoutResID) {
      mWindow.setContentView(layoutResID);
      }
  3. 将 DecorView 添加到 Window 中并显示

    • 在 Dialog 的 show() 方法中,会通过 WindowManager 将 DecorView 添加到 Window 中
    • 当 Dialog 被关闭时,它会通过 WindowManager 来移除 DecorView: mWindowManager.removeViewImmediate(mDecor);
  4. 需要注意的一点:普通的 Dialog 有一个特殊之处:必须采用 Activity 的 Context,如果采用 Applicaiton 的 Context 会报错:没有应用 token

    • 应用 token 一般只有 Activity 拥有,所以这里需要使用 Activity 作为 Context 来显示对话框

    • 另外,系统 Window 比较特殊,它可以不需要 token

      1
      2
      3
      4
      5
      // WindowManager.LayoutParams 中的 type 表示 Window 的类型,系统 Window 的层级范围是 2000 ~ 2999		
      dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_OVERLAY);

      // 在 AndroidManifest.xml 文件中声明权限从而可以使用系统 Window
      <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

3. Toast 的 Window 创建过程

  • Toast 也是基于 Window 来实现的,但由于 Toast 具有定时取消这一功能,所以系统采用了 Handler
  • 在 Toast 的内部有两类 IPC 过程,一类是 Toast 访问 NotificationManagerService,一类是 NotificationManagerService 回调 Toast 里的 TN 接口
  • Toast 属于系统 Window,它内部的视图由两种方式指定,一种是系统默认的样式,一种是通过 setView() 方法来指定一个自定义 View
  • Toast 提供了 show()cancel() 方法分别用于显示和隐藏 Toast,这两个方法的内部都是一个 IPC 过程
  • Toast 的显示和隐藏逻辑底层使用了 Handler 切换线程,所以 Toast 无法在没有 Looper 的线程中弹出,这是因为 Handler 需要使用 Looper 才能完成切换线程的功能
  • 分析源码,对于非系统应用来说,mToastQueue 中最多能同时存在 50 个 ToastRecord,这样做是为了防止 DOS(Denial of Service)。如果不这么做,如果使用大量循环去连续弹出 Toast,这将会导致其他应用没有机会弹出 Toast,那么对于其他应用的 Toast 请求,系统的行为就是拒绝服务
  • Toast 显示以后,NMS 还会通过 scheduleTimeoutLocked() 方法来发送一个延时消息,具体的延时时长取决于 Toast 的时长,LONG_DELAY 是 3.5sSHORT_DELAY 是 2s
  • 除了 Activity、Dialog 和 Toast 之外,PopupWindow菜单状态栏都是通过 Window 来实现的,任何 View 都是附属在一个 Window 上面的
-------------------- 本文结束感谢您的阅读 --------------------