美文网首页android高级
理解Window和WindowManager(一)

理解Window和WindowManager(一)

作者: Utte | 来源:发表于2018-08-16 10:44 被阅读484次

    一、概述

    1 ) 什么是Window?什么是WindowManager?

    1. Window

    1. Window是一个抽象类,PhoneWindow是它的唯一实现类。
    2. Window实际上是View的直接管理者。
      • Android中的所有视图都是通过Window来实现的。
      • 不管是Activity、Dialog还是Toast,它们的视图实际上都是附加在Window上的。
      • View是Android中呈现视图的方式,但是View不能单独存在,必须附着在Window这个抽象的概念上。
      • 有视图的地方就有Window。

    2. WindowManager

    1. 创建一个Window需要通过WindowManager。
    2. Window的具体实现在WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。

    2 ) 添加一个Window实例

    1. 处理权限

    AndroidManifest.xml
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    
    AddWindowActivity # onCreate()
    if (Build.VERSION.SDK_INT >= 23 && !Settings.canDrawOverlays(this)) {
        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivityForResult(intent, 1);
    } else {
        addWindow();
    }
    

    2. 完成addWindow()

    AddWindowActivity # addWindow()

    在获得权限之后,就会在屏幕正中间显示一个Button了。这里需要注意type的设置,如果不设置type,就会报错,在下面会说关于type参数的知识点。

    private void addWindow() {
        // 先创建一个button
        mButton = new Button(this);
        mButton.setText("Button");
        // 配置window的参数
        mLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
                0, 0, PixelFormat.TRANSPARENT);
        mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL;
        mLayoutParams.gravity = Gravity.CENTER;
        // 8.0以上需要这样设置type
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mLayoutParams.type = LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ALERT;
        }
        // 获得WindowManager
        mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        if (mWindowManager != null) {
            // 通过WindowManager添加Window
            mWindowManager.addView(mButton, mLayoutParams);
        }
    }
    

    3 ) Window的参数

    上面实例中,设置属性都是通过一个LayoutParams来实现的,这个WindowManager.LayoutParams是WindowManager的静态内部类,用来管理Window的参数。

    1. Flags参数

    Flags参数可以控制Window的显示特性。Flags参数非常多,详细看这里
    常用的有下面几种:

    • FLAG_NOT_TOUCH_MODAL
      • 设置后Window区域外的事件传递给下层的Window,区域内的事件自己处理。
      • 一般都会设置该参数,否则其它Window会收不到事件。
    • FLAG_NOT_FOCUSABLE
      • 不接受任何输入事件,跳不出软键盘。
      • 这个参数会同时也会开启FLAG_NOT_TOUCH_MODAL。
    • FLAG_SHOW_WHEN_LOCKED
      • 表示Window可以在锁屏界面上显示。
      • 只适用于最顶层的全屏幕Window。

      这个参数在AIP27时过期了,推荐使用 R.attr.showWhenLocked参数或者Activity.setShowWhenLocked(boolean)。

    2. Type参数

    这个参数表示Window的类型,分为三种:

    • 应用Window:对应着一个Activity。
    • 子Window:不能单独存在,附属在父Window中,比如Dialog。
    • 系统Window:需要声明权限,比如Toast、系统状态栏。

    Window是分层的,每个Window都有对应的z-ordered,大层级的Window会覆盖在小层级的上面。层级就对应着Type参数。

    // 应用Window的层级范围
    public static final int FIRST_APPLICATION_WINDOW = 1;
    public static final int LAST_APPLICATION_WINDOW = 99;
    // 子Window的层级范围
    public static final int FIRST_SUB_WINDOW = 1000;
    public static final int LAST_SUB_WINDOW = 1999;
    // 系统Window的层级范围
    public static final int FIRST_SYSTEM_WINDOW = 2000;
    public static final int LAST_SYSTEM_WINDOW = 2999;
    

    比如在上面的实例中使用的TYPE_APPLICATION_OVERLAY,实际上就是一个系统层级的Type。

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        mLayoutParams.type = LayoutParams.TYPE_APPLICATION_OVERLAY;
    } else {
        mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ALERT;
    }
    
    public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
    public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
    

    因为是系统层级的Window,所以需要处理这个权限:

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    

    4 ) WindowManager提供的方法

    WindowManager继承了ViewManager。常用的也就是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操作Window实际上就是在操作Window中的View。

    二、Window内部机制

    每个Window都对应一个View和一个ViewRootImpl,View和Window通过ViewRootImpl连接,所以Window其实是以View的形式存在的。

    WindowManager也是一个接口,实现类是WindowManagerImpl,实现了ViewManager的方法,来看这些方法的实现。

    1 ) Window的添加过程

    Window的添加是通过addView()来实现的。

    WindowManagerImpl # addView()

    可以看出WindowManagerImpl并没有直接实现,而是全部交给mGlobal。

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
    
    WindowManagerGlobal # getInstance()

    mGlobal是一个WindowManagerGlobal对象。提供一个单例对象管理着所有的WindowManager中调用的操作。

    public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }
    

    WindowManagerGlobal中有四个全局集合,存储着所有的Window的参数、所有Window对应的ViewRootImpl和它们对应的顶级View,最后一个存储的是调用了removeView()但是操作还尚未完成的View。

    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
    private final ArraySet<View> mDyingViews = new ArraySet<View>();
    
    WindowManagerGlobal # addView()

    下面就是addView()的代码,主要做了这些工作:

    1. 检查参数。
    2. 判断是否有父Window
      • 有,调整title和token。
      • 无,应用开启了硬件加速的话,该View就开启。
    3. 遍历所有ViewRootImpl,更新系统参数。
    4. 如果需要添加的View已经在mViews集合中。
      • 如果还在dying集合中,就将原先的Window删除,之后继续。
      • 如果不在,就直接结束该方法,因为已经添加好了。
    5. 如果是子Window,就遍历搜索父Window,赋值引用。
    6. 新建一个ViewRootImpl,给要添加的View配置LayoutParams,将三个对象添加到集合中。
    7. 调用ViewRootImpl的setView()更新界面。
    8. 最后,在setView()中完成Window的添加。
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // 检查参数
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        // 判断params是否是WindowManager.LayoutParams类型的
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }
        
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        // 如果有父Window,就根据type赋值params的title和token
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                // 如果没有父Window并且应用开启了硬件加速,就设置该Window开启硬件加速
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
        
        ViewRootImpl root;
        View panelParentView = null;
        // 先上锁
        synchronized (mLock) {
            if (mSystemPropertyUpdater == null) {
                // 赋值一个Runnable,用来遍历更新所有ViewRootImpl的有关参数
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                // 添加到执行队列中
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }
            
            // 看需要add的view是否已经在mViews中
            int index = findViewLocked(view, false);
            if (index >= 0) {
                // 如果被添加过,就看是否在死亡队列里也存在
                if (mDyingViews.contains(view)) {
                    // 如果存在,就将这个已经存在的view对应的window移除
                    mRoots.get(index).doDie();
                } else {
                    // 否则,说明view已经被添加,不需要重新添加了
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
            }
            
            // 如果属于子Window层级
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                // 遍历ViewRootImpl,看是否存在一个Window的IBinder对象和需要添加的Window的token一致,之后赋值引用
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }
            
            // 新创建一个ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
            // 给需要添加的View设置params
            view.setLayoutParams(wparams);
            // 将三个参数加入三个集合中
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            
            try {
                // 调用ViewRootImpl的setView(),这个方法会调用开始ViewRootImpl的performTraversals()
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
    
    ViewRootImpl # setView()
    1. 调用requestLayout(),post一个调用performTraverslas()的Runnable去更新界面。
    2. 调用IWindowSession的addToDisplay()远程调用WindowManagerService处理添加Window的请求。
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                // ......
                mAdded = true;
                int res;
                // 内部调用提交一个更新界面的Runnable去执行performTraversals()
                requestLayout();
                // ......
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    // IPC调用,调用WindowManagerService去处理
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } // ......
            }
        }
    }
    

    2 ) Window的删除过程

    WindowManagerImpl # removeView()

    同样还是交给WindowManagerGlobal处理。

    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
    
    WindowManagerGlobal # removeView()

    主要工作是获取了下标,做了判断。调用removeViewLocked()删除。

    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);
            // 获得需要移除对象的Window的顶级View
            View curView = mRoots.get(index).getView();
            // 删除逻辑
            removeViewLocked(index, immediate);
            // 如果是同一个对象,就返回
            if (curView == view) {
                return;
            }
            // 如果不是,抛出一个异常,删除的View不是Window的顶级View
            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }
    
    WindowManagerGlobal # removeViewLocked()

    处理输入法后调用die()去删除。

    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());
            }
        }
        // 调用die去进行删除操作
        // immediate参数指是否使用同步删除
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                // 加入死亡集合
                mDyingViews.add(view);
            }
        }
    }
    
    ViewRootImpl # die()

    removeView()传入的immediate为false表示异步删除,removeViewImmediate()传入的是true,异步删除。

    • 如果是同步就直接调用doDie()直接删除。
    • 如果是异步就发送一个消息给Handle。
    boolean die(boolean immediate) {
        // 如果是同步并且没有正在执行traversal
        if (immediate && !mIsInTraversal) {
            // 直接调用删除
            doDie();
            // 返回false表示没有排队,立即执行删除了。
            return false;
        }
        if (!mIsDrawing) {
            // 如果不在绘制流程中,就关掉硬件加速
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        // 给ViewRootImpl发送一个MSG_DIE消息
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
    

    在handleMessage()中,处理MSG_DIE也是调用doDie()。

    case MSG_DIE:
        doDie();
        break;
    
    ViewRootImpl # doDie()

    doDie()主要调用了两个方法,dispatchDetachedFromWindow()和doRemoveView(),这两个方法来处理删除。

    void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }
            // ......
            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }
    
    ViewRootImpl # dispatchDetachedFromWindow()

    主要做了这些事:

    1. 调用Window移除相关的监听回调方法。
    2. 移除回调,移除Window的数据,释放、置空。
    3. 远程调用WindowManagerService的removeWindow()。
    4. 切断通信通道。
    5. 移除traverslas的Runnable。
    void dispatchDetachedFromWindow() {
        if (mView != null && mView.mAttachInfo != null) {
            // 调用监听的onXXX()回调方法
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            mView.dispatchDetachedFromWindow();
        }
        
        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        
        // 移除回调
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();
        // 移除硬件加速
        destroyHardwareRenderer();
        
        // 将数据置为null或释放
        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;
        }
        try {
            // IPC调用WindowManagerService删除Window
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }
        
        // 切断和远程通信
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }
        // 取消监听
        mDisplayManager.unregisterDisplayListener(mDisplayListener);
        // 移除消息队列中准备执行traversals的Runnable
        unscheduleTraversals();
    }
    
    WindowManagerGlobal # doRemoveView()

    在调用dispatchDetachedFromWindow()移除Window之后,还调用了这个方法,它主要是处理WindowManagerGlobal几个集合,将删除的Window相关对象从集合中移除。

    void doRemoveView(ViewRootImpl root) {
        synchronized (mLock) {
            final int index = mRoots.indexOf(root);
            if (index >= 0) {
                mRoots.remove(index);
                mParams.remove(index);
                final View view = mViews.remove(index);
                mDyingViews.remove(view);
            }
        }
        if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
            doTrimForeground();
        }
    }
    

    3 ) Window的更新过程

    WindowManagerImpl # updateViewLayout()

    同样也是交给WindowManagerGlobal处理。

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }
    
    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设置新的参数
        view.setLayoutParams(wparams);
        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            // 移除之前这个view的Params
            mParams.remove(index);
            // 加入新的
            mParams.add(index, wparams);
            // 给ViewRootImpl设置新的参数
            root.setLayoutParams(wparams, false);
        }
    }
    
    ViewRootImpl # setLayoutParams()

    在这个方法里主要做两件事,一个是赋值新参数,另一个是提交traversals重新侧脸布局绘制。

    void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
        synchronized (this) {
            // ......
            
            // 复制赋值参数
            mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
            
            // ......
            
            mWindowAttributesChanged = true;
            // 调用performTraversals()
            scheduleTraversals();
        }
    }
    

    到这里还没有结束,addView()、removeView()都会调用到WindowManagerService,updateViewLayout()也不例外。回顾一下,在performTraversals()中,除了三个流程的调用,还有这样一行,远程调用了WindowManagerService的relayoutWindow()。

    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
    

    相关文章

      网友评论

        本文标题:理解Window和WindowManager(一)

        本文链接:https://www.haomeiwen.com/subject/jnenbftx.html