《Android 开发艺术探索》笔记——(8)Window 和 WindowManager

  • Android 中的 Window 有两种概念。一种是 Window 抽象类,它的具体实现是 PhoneWindow,用来进行操作,比如生成 DecorView(PhoneWindow 通常只用在 Activity、Dialog 上)。另一种是以 View 为实体的抽象概念,用来显示。
  • 外界访问 Window (显示)要通过 WindowManager,WindowManager 会进一步访问 WindowManagerService(WMS),WMS 再去生成Window(显示),WindowManager 和 WindowManagerService 的交互是一个IPC过程。

    WindowManager –> WindowManagerService –> Window

  • Window(显示) 通过 ViewRootImpl 对 View 进行管理。

Window 和 WindowManager

以添加 Window(显示) 为例展示 Window 和 WIndowManager 的关系:

WindowManager.addView(view, layoutParams);

可以看到,WindowManager 添加 Window 其实是在添加 View,然后它会生成 View 对应的 ViewRootImpl, View 和 ViewRootImpl 一起就代表了 Window(显示)。上面 layoutParams 是 WindowManager.LayoutParams,其中 flags 和 type 两个参数比较重要。

Flags 表示 Window 的属性,三种常用的:

  • FLAG_NOT_FOCUSABLE
    表示 Window 不需要获取焦点,也不需要输入事件。
  • FLAG_NOT_TOUCH_MODAL
    当前 WIndow 区域之外的点击事件会传递给下面的 Window,一般都会开启。
  • FLAG_SHOW_WHEN_LOCKED
    让 Window 显示在锁屏上。

Type 表示 Window 的类型,有三种,每种 Window 都有不同的层级:

  • 应用 Window (Activity) 层级:1-99
  • 子 Window (Dialog) 层级:1000-1999
  • 系统 Window (Toast 和 系统状态栏) 层级:2000-2999

不同层级范围对应着 WindowManager.LayoutParams 的 type 参数,有很多对应的值。若采用系统 Window, 要声明相应权限,如使用TYPE_SYSTEM_ERROR 就要声明

< user-permission android:name=”android.permission.SYSTEM_ALERT_WINDOW” />。

WindowManager 常用的有三个方法,即添加VIew,更新VIew,删除View。它们定义在 ViewManager 中,WindowManager 继承了 ViewManager。WindowManager 和 ViewManager 都是一个接口。

Window的内部机制

Window(显示) 是一个抽象概念,每一个 Window 都对应着一个 View 和 一个 ViewRootImpl。View 是 Window 的实体,通过 ViewRootImpl 与 Window 建立联系。

这个 View 就是 DecorView。

Window

添加、删除和更新都是通过这样的过程来完成的:

WindowManager(接口)→
WindowManagerImpl (类)→
WindowManagerGlobal (工厂)。

Window 的添加过程

实际上通过 WindowManagerGlobal.addView() 来完成。

过程:

  1. 检查参数是否合法,如果是子 Window 那么还需要调整一些布局参数
  2. 创建 ViewRootImpl 并将 View 添加到列表中
  3. 通过 ViewRootImpl 来更新界面并完成 Window 的添加过程
    View 的绘制过程都是由 ViewRootImpl 来完成的,所以这里也由ViewRootImpl 的 setView 方法来完成,setView 内部会通过 requestLayout 来完成异步刷新请求。其中 scheduleTraversals() 就是绘制的入口。

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

    接着会通过 WindowSession 最终来完成 Window 的添加过程。WindowSession 的类型是 IWindowSession,它时一个 Binder 对象,真正的实现是 Session,在 Session 内部会通过 WindowManagerService 来实现 Window (显示)的添加。

Window 的删除过程

实际上通过 WindowManagerGlobal.removeView() 来完成。

public void removeView(View view, boolean immediate) {
    if (view == null) {
        throw new IlleagalArgumentException("view must not be null");
    }

    synchronized (mLock) {
        int index = findViewLocked(view, true);//找到要删除的 view 的索引
        View curView = mRoots.get(index).getView();
        removeViewLocked(index, immediate);//删除
        if (curView == view) {
            return;
        }
        throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to" + curView);
    }
}

进一步的删除在 removeViewLocked() 中完成,代码如下:

private void removeViewLocked(int index, boolean immediate) {
    ViewRootImpl root = mRoots.get(index);
    View view = root.getView();
    if(view != null) {
        InputMethodManager imm = InputMethodManager.getInstance();
        if (imm != null){
            imm.windowDismissed(mViews.get(index).getWindowToken());
        }
    }
    boolean deferred = root.die(immediate);
    if (view != null){
        view.assignParent(null);
        if (deferred) {
            mDyingViews.add(view);
        }
    }
}

root 的 die 方法根据参数 imediate 判断是异步删除还是同步删除。

  • 异步:die 会发一个消息给 ViemRootImpl 的 Handler, Handler 会处理此消息并调用 doDie 方法。
  • 同步:直接调用 doDie 方法。

若是异步,则 die 方法后 view 并没有被删除,view 将被放入 mDyingViews 中等待删除。

Window 的更新过程

实际上通过 WindowManagerGlobal.updateViewLayout() 来完成。

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

    view.setLayoutParams(wparams);

    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        mParams.add(index, wparams);
        root.setLayoutParams(wparams, false);
    }
}

updateViewLayout 方法比较简单,先更新 View 的 LayoutParams 并替代老的 LayoutParams,接着通过 ViewRootImpl 的 setLayoutParams 方法来更新 ViewRootImpl 的 LayoutParams。

Window 的创建过程

这里指操作上的 Window

Activity 的 Window 创建过程

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

在 Activity 的 attach() 方法中,系统会通过 PolicyManager.makeNewWindow() 创建 Activity 所属的 Window 对象并为其设置回调接口。

实际中,PolicyManager 的真正实现是 Policy 类,Policy 类中的 makeNewWindow 会返回一个 PhoneWindow 对象。

大致过程如下(==> 为内部,—> 为之后):

performLaunchActivity() ===>
activity = mInstrumentation.newActivity(…) —>
activity.attach() ===>
mWindow = PolicyManager.makeNewWindow(this) ===>
return new PhoneWindow(context);

现在 Window 已经创建完成,下面分析 Activity 的视图是怎样附属在 Window 上的,大致过程如下:

Activity.setContentView() ==>
getWindow.setContentView() ==>
PhoneWindow.setContentView()

PhoneWindow.setContentView() 方法大致有以下步骤:

  1. 如果没有 DecorView , 就创建它
  2. 将 View 添加到 DecorView 的 mContentParent 中
  3. 回调 Activity 的 onContentChanged 方法通知 Activity 视图已经发生变化

PhoneWindow 是操作上的 Window,用来生成 DecorView。通过以上三个步骤,DecorView 已经被创建且初始化完毕,但此时显示上的 Window 此时还没有出现。在 ActivityThread 的 handleResumeActivity 中,会调用 Activity 的 onResume 方法,接着调用了 Activity 的 makeVisible 之后,显示上的 Window 才会出现:

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

通过 WindowManager.addView() 后,显示上的 Window 才正式出现。

Dialog 的 Window 创建过程

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

  1. 创建 Window
  2. 初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中
  3. 将 DecorView 添加到 WindowManager

Dialog 的 Window 创建过程与 Activity 很类似,几乎没什么区别。当 Dialog 被关闭,它会通过 WindowManager 来移除 DecorView:mWindowManager.removeViewImmediate(mDecor)(同步)。
普通的 Dialog 必须采用 Activity 的 Context,因为需要 Activity 的应用 Token(?)。

Toast 的 Window 创建过程

Toast 的 Window 的创建过程就比较复杂了。由于 Toast 具有定时取消这一功能,所以系统采用了 Handler。

Toast 提供了 show 和 cancel 分别用于显示和隐藏 Toast,它们内部是一个 IPC 过程。

Toast 的显示:

INotificationManager service = getService() —>
service.enqueueToast(pkg, tn, mDuration) ===>
将 Toast 请求封装为 ToastRecord 添加到 mToastQueue 队列中 —>
showNextToastLocked() ===>
ToastRecord.callback.show() (显示) —>
scheduleTimeoutLocked(record) (延时)

Toast 的隐藏与显示类似,也是通过 ToastRecord 的 callback 来完成的。

ToastRecord.callback.hide() (隐藏)

callback 实际上是 Toast 中的 TN 对象的远程 Binder,通过 callback 来访问 TN 中的方法是需要跨进程来完成的,最终被调用的 TN 中的方法会运行在发起 Toast 请求的应用的 Binder 线程池中。

所以,Toast 的显示和隐藏过程实际上是通过 Toast 中 TN 这个类来实现的,它有两个方法 show 和 hide,即显示和隐藏。由于这两个方法被 NotificationManagerService 以跨进程的方式调用的,因此它们运行在 Binder 线程池中。为了将执行环境切换到 Toast 请求所在线程,在它们内部使用了 Handler。

猜你喜欢

转载自blog.csdn.net/gdeer/article/details/51296514