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

  • Window 是一个抽象类,它的具体实现是 PhoneWindow。
  • WindowManager 是外界访问 Window 的入口,Window 的具体实现位于 WindowManagerService 中,WindowManager 和WindowManagerService 的交互是一个IPC过程。
  • Window是View的直接管理者。

Window 和 WindowManager

WindowManager 添加 Window(伪代码):

WindowManager.addView(view, layoutParams);

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。

Window的内部机制

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

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

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

Window 的添加过程

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

过程:

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

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

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的创建过程

Activity 的 Window 创建过程

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

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

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

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

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 视图已经发生变化

Dialog 的 Window 创建过程

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

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

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。

你可能感兴趣的:(window,Android开发,笔记,ipc)