【艺术探索笔记】第 8 章 理解 Window 和 WindowManager

第 8 章 理解 Window 和 WindowManager

Window 表示一个窗口的概念
Window 是一个抽象类,具体实现是 PhoneWindow
通过 WindowManager 即可创建一个 Window,WindowManager 是外界访问 Window 的入口
Window 的具体实现位于 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一个 IPC 过程
Window 实际是 View 的直接管理者(Android 中的视图都是通过 Window 来呈现的)


8.1 Window 和 WindowManager

代码演示通过 Windowmanager 添加 Window:

    val button = Button(this)
    button.text = "asdfgasdrg"

    val layoutParams = WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT
            , WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT)

    layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
            WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED

    layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY

    layoutParams.gravity = Gravity.START or Gravity.TOP

    layoutParams.x = 100
    layoutParams.y = 300

    windowManager.addView(button, layoutParams)

WindowManager 中的 flagstype 这两个参数比较重要。

  • Flags 参数表示 Window 的属性,可以控制 Window 的显示特性

    • FLAG_NOT_FOCUSABLE
      表示 Window 不需要获取焦点,也不需要接受各种输入事件,此标记会同时启用 FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层具有焦点的 Window
    • FLAG_NOT_TOUCH_MODAL
      此模式下会把当前区域以外的点击事件传递给底层 Window,当前 Window 区域以内的点击事件自己处理。很重要的标记位,一般都需要开启此标记为,不然不能处理点击事件
    • FLAG_SHOW_WHEN_LOCKED
      开启此模式可以让 Window 显示在锁屏的界面上
  • Type 表示 Window 的类型

    • 应用 Window
      它对应着一个 Activity
    • 子 Window
      不能单独存在,它需要附属在特定的父 Window 之中,比如常见的 Dialog 就是个子 Window
    • 系统 Window
      需要声明权限才能创建的 Window。Toast 和 系统状态栏都是系统 Window

Window 是分层的,层级大的会覆盖层级小的。
层级范围:
应用 Window —— 1 ~ 99
子 Window —— 1000 ~ 1999
系统 Window —— 2000 ~ 2999

层级范围对应着 WindowManager.LayoutParams 的 type 参数。
系统级的 Window 层级大,可以全局显示。创建系统级 Window 需要设置 type 和添加权限:
系统级 type 有: TYPE_SYSTEM_OVERLAY、TYPE_SYSTEM_ERROR。
设置权限:<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

WindowManager 所提供的功能(继承自 ViewManager):
image

它可以创建一个 Window 并向其添加 View;更新 Window 中的 View;删除 Window(删除 View 就会删除 Window)。由此看来 WindowManager 操作 Window 的过程更像是在操作 Window 中的 View 。

可以拖动的 Window 效果实现:监听触摸事件,不断通过 WindowManager 的 updateViewLayout 来更新 layoutParams 的 x、y 值。


8.2 Window 的内部机制

每一个 Window 都对应着一个 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,因此 Window 并不是实际存在的,它是以 View 的形式存在的。
实际使用中无法直接访问 View,需要通过 WindowManager。

8.2.1 Window 的添加过程

api-27 Window 添加调用链:

  1. WindowManagerImpl # addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params)
    Window 是个接口类,它的实现是 WindowManagerImpl ,所以调用 Window#addView 即 WindowManagerImpl#addView

  2. WindowManagerGlobal # addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow)

    
    ...
    
    //存储所有 Window 所对应的 View
    private final ArrayList<View> mViews = new ArrayList<View>();
    
    //存储所有 Window 对应的 ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    
    //存储那些正在被删除的 View 对象,或者说是已经调用 removeView 方法但是删除操作还未完成的 Window 对象
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
    
    private final ArraySet<View> mDyingViews = new ArraySet<View>();
    
    ...
    
        public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    
        //1.检查参数是否合法
        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;
    
        //1.如果是子 Window,调整一些布局参数
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }
    
        ...
    
        ViewRootImpl root;
        View panelParentView = null;
    
        synchronized (mLock) {
    
            ...
    
            //2.创建 ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
    
            view.setLayoutParams(wparams);
    
            //2.将 View 添加到列表中
            mViews.add(view);
    
            mRoots.add(root);
            mParams.add(wparams);
    
            // do this last because it fires off messages to start doing things
            try {
    
                //3.通过 ViewRootImpl 来更新界面并完成 Window 的添加
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                ...
            }
        }
    }
  3. ViewRootImpl # setView(View view, WindowManager.LayoutParams attrs, View panelParentView)

    /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
    
                ...
    
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */
    
                // View 绘制过程在此方法里
                requestLayout();
    
                ...
    
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
    
                    //mWindowSession 的类型是 IWindowSession,是一个 Binder 对象,找源码最终的实现是 Session。所以说 Window 的添加过程是一次 IPC 调用
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
    
                ...
            }
        }
    }
    
    
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals(); //View 绘制的入口
        }
    }
    
  4. Session # addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel)

    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
  5. WindowManagerService # addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel)
    这里就是具体实现了,就不贴代码了

Window 的添加最终交给 WindowManagerService 去处理了,在 WindowManagerService 内部会为每一个应用保留一个单独的 Session。

8.2.2 Window 的删除过程

api-27 调用链:

  1. WindowManagerImpl # removeView(View view)

  2. WindowManagerGlobal # removeView(View view, boolean immediate)

        public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
    
        synchronized (mLock) {
            //找到将要删除的 View 的索引
            int index = findViewLocked(view, true);
    
            View curView = mRoots.get(index).getView();
    
            //调用 removeViewLocked 进行进一步删除
            removeViewLocked(index, immediate);
    
            if (curView == view) {
                return;
            }
    
            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }
  3. WindowManagerGlobal # removeViewLocked(int index, boolean immediate)

        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());
            }
        }
    
        //通过 ViewRootImpl 来完成删除操作
        boolean deferred = root.die(immediate);
    
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                //异步删除,把要删除的 View 放入列表
                mDyingViews.add(view);
            }
        }
    }

    WindowManager 有两种删除接口 removeViewremoveViewImmediate,分别表示异步删除和同步删除。
    一般不用同步删除,以免发生意外的错误

  4. ViewRootImpl # die(boolean immediate)

        /**
     * @param immediate True, do now if not in traversal. False, put on queue and do later.
     * @return True, request has been queued. False, request has been completed.
     */
    boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            //同步删除,直接调用 doDie 方法进行进一步删除
            doDie();
            return false;
        }
    
        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
    
        //异步删除 给 handler 发消息后,直接返回 true。(会在 removeViewLocked 中把将要删除的 View 放到 mDyingViews 列表)
        //handler 处理此消息并调用 doDie 方法
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
    
  5. ViewRootImpl # doDie()

    
    void doDie() {
    
        ...
    
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }
    
            ...
    
            mAdded = false;
        }
    
        //刷新 WindowManagerGlobal 的数据。包括 mRoots、mParams、mDyingViews,需要将当前 Window 所关联的这三类对象从列表中删除
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }
    
    
    void dispatchDetachedFromWindow() {
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
    
            //调用 View 的 dispatchDetachedFromWindow,在内部会调用 View 的 onDetachedFromWindow() 方法以及 onDetachedFromWindowInternal()。
            //onDetachedFromWindow() 当 View 被移除时,这个方法会被调用,处理一些资源回收、终止动画、停止线程等
            mView.dispatchDetachedFromWindow();
        }
    
        //垃圾回收相关工作,比如清除数据和消息、移除回调 start
        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();
    
        destroyHardwareRenderer();
    
        setAccessibilityFocus(null, null);
    
        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;
    
        mSurface.release();
    
        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }
        //垃圾回收相关工作,比如清除数据和消息、移除回调 end
    
        try {
            //通过 Session 的 remove 方法删除 Window。这也是一个 IPC 过程,最终会调用 WindowManagerService 的 removeWindow 方法删除
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }
    
        // Dispose the input channel after removing the window so the Window Manager
        // doesn't interpret the input channel being closed as an abnormal termination.
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }
    
        mDisplayManager.unregisterDisplayListener(mDisplayListener);
    
        unscheduleTraversals();
    }
    

8.2.3 Window 的更新过程

api-27 调用链:

  1. WindowManagerImpl # updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params)

  2. WindowManagerGlobal # updateViewLayout(View view, ViewGroup.LayoutParams params)

        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 的 LayoutParams 并替换掉老的 LayoutPArams。
        view.setLayoutParams(wparams);
    
        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
    
            //更新 ViewRootImpl 中的 LayoutParams
            root.setLayoutParams(wparams, false);
        }
    }

    ViewRootImpl 中会调用 scheduleTraversals 方法对 View 重新绘制,包括 测量、布局、重绘这三个过程。
    还会通过 WindowSession 来更新 Window 的视图,这个过程最终是由 WindowManagerService 的 relayoutWindow()来具体实现的,也是一个 IPC 过程。


8.3 Window 的创建过程

View 是 Android 中视图的呈现方式,但是不能单独存在,必须附着在 Window 这个抽象概念上,因此有视图的地方就有 Window

8.3.1 Activity 的 Window 创建过程

Activity 的启动最终会由 ActivityThread 中的 performLaunchActivity() 来完成整个启动过程。这个方法内部会 实例化 Activity、调用 attach 方法等,attach 方法会为其关联运行过程中所依赖的一系列上下文环境变量:

image

在 Activity 的 attach 方法里,系统会创建 Activity 所属的 Window 对象并设置回调接口。当 Window 接收到外界状态改变时就会回调 Activity 的方法。(如 onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent 等等):

//api-27
    //Activity#attach()

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }

下面分析 Activity 的视图是怎么附着到 Window 上的。看 setContentView 方法(它提供了 Activity 的视图):

    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
        initWindowDecorActionBar();
    }

Activity 的 setContentView 调用了 Window 的 setContentView,而 Window 的具体实现是 PhoneWindow(atach 方法中可以看出来)。PhoneWindow 的 setContentView 的步骤:

//PhoneWindow#setContentView()

public void setContentView(View view, ViewGroup.LayoutParams params) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
  1. 如果没有 DecorView,就创建它
    DecorView 是 Activity 顶层 View,包括 标题栏 和 内容栏,内容栏固定资源 id:android.R.id.content
    通过 generateDecor() 来创建 DecorView(跟布局是 FrameLayout)
    通过 generateLayout() 来加载具体的布局文件(标题栏和内容栏)

  2. 将 View 添加到 DecorView 的 mContentParent 中
    添加到了 内容栏(android.R.id.content)。
    所以 Activity 的添加试图方法叫 setContentView 而不是 setView

  3. 回调 Activity 的 onContentChanged 方法通知 Activity 视图已经发生改变
    Activity 实现了 Window 的 CallBack 接口,就能收到回调做相应处理了

经过这三个步骤后,DecorView 创建并初始化完毕、Activity 的布局文件也添加到 DecorView 中了。但是,DecorView 还没有被 WindowManager 添加到 Window 中。

在 ActivityThread 的 handleResumeActivity 方法中,先调用 Activity 的 onResume 方法,接着调用 Activity 的 makeVisiable()。在 makeVisiable 方法中,DecorView 真正完成了添加和显示这两个过程:

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

此时,Activity 中的 Window 的创建过程才算完了。

总结一波

  1. ActivityThread#handleLaunchActivity被调用,内部

    1. ActivityThread#performLaunchActivity 被调用,内部
      1. 实例化 Activity
      2. 调用 Activity#attach() 方法
        • 内部实例化 Window(即 PhoneWindow)
      3. 使 Activity 的 onCreate 方法被调用
        • setContentView 在此调用。
          • 调用 Window 的 setContentView
            1. 创建 DecorView
            2. 把 Activity 的布局文件添加到 DecorView 的内容区
    2. ActivityThread#handleResumeActivity 方法调用
      1. 使 Activity 的 onResume 被调用
      2. 调用 Activity 的 makeVisiable 方法
        在 makeVisiable 方法里才真正完成 Window 的添加和显示过程

8.3.2 Dialog 的 Window 创建过程

  1. 创建 Window
    Dialog 的 构造方法会实例化 Window(PhoneWindow)

        Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        ...
    
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
    
        mListenersHandler = new ListenersHandler(this);
    }
  2. 初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中
    调用的 Window 的 setContentView 方法

        public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
    }
  3. 将 DecorView 附着到 Window 上。(通过调用 Dialog 的 show 方法)

        public void show() {
        ...
    
        mWindowManager.addView(mDecor, l);
        mShowing = true;
    
        sendShowMessage();
    }

创建一个 Dialog 并显示的示例:

        val textView = TextView(this@MainActivity)
        textView.text = "hello"

        val dialog = Dialog(this@MainActivity) //构造方法创建 PhoneWindow
        dialog.setContentView(textView) //内部调用 PhoneWindow 的 setContentView 初始化 DecorView 并添加 Dialog 布局到 DecorView 内容区
        dialog.show() //将 DecorView 附着到 Window 上

8.3.3 Toast 的 Window 创建过程

弹出 Toast 常规示例:

        Toast
            .makeText(this, "123", Toast.LENGTH_SHORT)
            .show()

从弹出一个 Toast 开始分析:

  • makeText 方法,会实例化 Toast、加载默认布局设置提示文字,把默认布局设置给 mNextView、设置 duration
  • show 方法,内部通过 binder 方式 调用 NMS 的 enqueueToast 方法
  • NMS 的 enqueueToast 方法,把 Toast 封装成 ToastRecord 并添加到 mToastQueue 集合中,然后尝试去调用 showNextToastLocked 方法弹出 Toast
  • showNextToastLocked 方法,通过 binder 调用 Toast 的 内部 Binder 类 TN 的 show 方法、给 NMS 内部发延时消息隐藏 Toast,最终通过 binder 调用 Toast 的 内部 Binder 类 TN 的 hide 方法
  • TN 的 show 方法 发送 handler 消息最终切换到原始线程调用 handleShow 方法,handleShow 方法里:把 Toast 的 View 附着到 Window 上
  • TN 的 hide 方法 发送 handler 消息最终切换到原始线程调用 handleHide 方法,handleHide 方法里:调用 WindowManager 的 removeViewImmediate 方法移除视图

提问:为什么 Toast 不能在没有 Looper 的线程里弹出?(答案不写,便于每次看到的时候思考一下)


猜你喜欢

转载自blog.csdn.net/Captive_Rainbow_/article/details/81393084