理解Window和WindowManager(一)window的添加删除更新view

window表示窗口的概念,平时开发中使用不多,但是某些时候我们需要在桌面上显示一个类似悬浮窗的东西(比如系统弹的吐司:Toast,就是在window上弹得)那么这种效果就需要window来实现。

一、简单介绍

1、window是一个抽象类,他的具体唯一实现类phonewindow
2、创建window使用windowmanager即可,windowmanager是外界访问window的入口
3、window的具体实现位于windowManagerService中,windowmanager和windowManagerService的交互是一个ipc过程
4、安卓中所有的视图都是通过window来呈现的,不管是activity,dialog还是toast,他们视图实际上都是附加在window上的
5、通过view的事件分发机制我们知道,事件由window传递给decorview,然后由decorview传递给我们的view,就连activity的setContentView底层也是通过window来完成的。

二、 window添加view的简单栗子

我们就向系统级别的window添加view
1、声明权限
  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
2、简单Demo
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 打开权限设置  设置允许后 就可以 在window上显示按钮
        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 {

            Button button = new Button(this);
            button.setText("按钮");

            //1 声明布局参数
            WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                    WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
                    0, 0, PixelFormat.TRANSPARENT);

            // 设置flag
            layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;

            layoutParams.gravity = Gravity.CENTER;
            layoutParams.x = 100;
            layoutParams.y = 100;
            // 8.0 处理
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
            } else {
                layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
            }

            // 2、添加到window
            WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
            windowManager.addView(button,layoutParams);
        }

    }

}

app运行跳转权限界面,我们允许相关的显示权限即可。如下图桌面上显示出来了
理解Window和WindowManager(一)window的添加删除更新view_第1张图片

代码很简单就是else中的逻辑就三步
1、声明布局参数: WindowManager.LayoutParams
2、设置一些布局参数(主要是flag、type的设置)
3、添加到window(通过windowmanager就可以添加了)

三、window的参数和WindowManager

上面的栗子中有个重要的类:WindowManager.LayoutParams这个类是WindowManager的内部类用来管理window的参数。

1、Flags:表示window的属性,flags有很多,通过这些flags可以控制window的显示特性。接下来总结常见几种:
  • FLAG_NOT_FOCUSABLE
    表示window不需要获得焦点,也不需要各种输入事件(跳不出软键盘)此标记同时也会开启FLAG_NOT_TOUCH_MODAL标记,最终事件传递给具有焦点的window。

  • FLAG_NOT_TOUCH_MODAL
    这个标记设置后,系统会将当前window以外的单击事件,传递给底层的window,当前window以内的点击事件自己处理。这个标记很重要一般来说都开启,否则其他window我发收到单击事件。

  • FLAG_SHOW_WHEN_LOCKED
    表示Window可以在锁屏界面上显示,只适用于最顶层的全屏幕Window
    ps:这个参数在AIP27时过期了,推荐使用 R.attr.showWhenLocked参数或者Activity.setShowWhenLocked(boolean)。

2、Type:表示window的类型。window有三种类型
  • 应用Window:对应着一个Activity。
  • 子Window:不能单独存在,附属在父Window中,比如Dialog。
  • 系统Window:需要声明权限,比如Toast、系统状态栏。

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

用用window的层级范围:1-99
子window的层级范围:1000-1999
系统Window的层级范围:2000-2999

1、我们上文使用的:
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
TYPE_APPLICATION_OVERLAY这个type进入源码发现其数值为2038
2、我们上文使用的:
  layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
  TYPE_SYSTEM_ALERT进入源码我们发现其数值为2003

如果想要window位于所有的window的最顶层,只需要采用较大的层级即可(type使用大数值的),很显然系统window的层级时最大的而且系统层级有很多值我们一般使用TYPE_APPLICATION_OVERLAY或者TYPE_SYSTEM_ALERT都可,同时采用系统类型window需要加权限(我们栗子中使用就是系统类型window)。

3、 WindowManager

WindowManager所提供的功能很简单,常用的有三个方法:
1、 addView添加view
2、updateViewLayout更新 view
3、 removeView 删除view
这三个方法定义在viewManager这个接口中WindowManager是他的实现类

如果我们想要窗口上的view可以触摸滑动只需要给view设置触摸事件,在触摸事件中不断修改布局参数的位置,在调用updateViewLayout更新view即可

四、window的内部机制

1、window是一个抽象概念,每个window都对应一个view和和一个viewRootImpl
2、window通过viewRootImpl和view建立联系,所以window不是真实存在的,他是以view的形式存在。
3、我们无法直接访问window,windowManager提供了操作window的api,他的常用方法也也是针对view的。
为了分析window的机制我们从window的添加、删除、更新说起

1、window的添加过程

window的添加过程是通过windowManager的addview来完成的,windowManager也是一个接口,他的实现类是windowManagerImpl接下来我们看看其三大操作(添加删除更新)源码:

// mGlobal 是 WindowManagerGlobal 对象
    public void addView( View view,  ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

    public void updateViewLayout(View view, ViewGroup.LayoutParams params){
        mGlobal.updateViewLayout(view,params);
    }
    public void removeView(View view){
        mGlobal.removeView(view);
    }

可以看到windowManagerImpl并没有直接实现而是交给了WindowManagerGlobal 去实现

WindowManagerGlobal 的addview源码
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;
        }
    }
}

可以看到主要做了如下操作:(参考图解)
1、检查参数是否合法,如果是子window还需要调整布局参数
2、创建ViewRootImpl对象,并将view添加到列表
3、通过ViewRootImpl的setView来完成window的添加过程
ps:通过view的事件体系我们知道view的绘制过程是由ViewRootImpl来完成的,这里当然不例外。setView内部调用requestLayout方法来完成异步刷新请求,requestLayout内部的scheduleTraversals就是view绘制入口。接着通过WindowSession来完成window的添加过程

ViewRootImpl的setView源码
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);
            } // ......
        }
    }
}
window添加过程流程图

理解Window和WindowManager(一)window的添加删除更新view_第2张图片

2、window的删过程

通过上面我们知道删除过程也是WindowManagerGlobal 来接管的。源码如下:

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);
    }
}

逻辑清晰:首先通过findViewLocked来查找待删除view的索引。
然后调用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());
        }
    }
    // 调用die去进行删除操作
    // immediate参数指是否使用同步删除
    boolean deferred = root.die(immediate);
    if (view != null) {
        view.assignParent(null);
        if (deferred) {
            // 加入死亡集合
            mDyingViews.add(view);
        }
    }
}

这里创建了ViewRootImpl 对象具体的删除操作还是由ViewRootImpl 对象的die方法来完成
注意:die方法传递参数 false表示异步删除,true表示同步删除。
我们继续看die的源码:

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;
}

如果是同步调用doDie处理,立即删除。
如果是异步发送MSG_DIE消息给ViewRootImpl的Handler,handleMessage()中也是调用doDie处理
doDie源码:

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);
}

可以看到主要调用两个方法:dispatchDetachedFromWindow、doRemoveView

dispatchDetachedFromWindow源码:

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();
}

doRemoveView源码:

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();
    }
}

dispatchDetachedFromWindow和doRemoveView主要做了四件事:
1、垃圾回收相关工作,比如清除数据消息,移除回调
2、通过Session的remove方法删除view,这是个ipc过程,最终是远程调用WindowManagerService的removeWindow()
3、调用view对象的dispatchDetachedFromWindow(),内部调用view的onDetachedFromWindow()完成回收工作
4、调用windowManagerGloba的doRemoveView刷新数据,包括mRoots,mParams,mDyingViews,将当前view所关联三类对象从列表删除

window删除view的流程图

理解Window和WindowManager(一)window的添加删除更新view_第3张图片

3、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设置新的参数
    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);
    }
}

1、首先更新view的LayoutParams ,替换老的布局参数。
2、创建ViewRootImpl 对象更新ViewRootImpl 的布局参数
3、ViewRootImpl 的setLayoutParams内部通过,scheduleTraversal来重新完成view的测量布局重绘三个过程。还通过windowSession来完成window的视图更新。

流程图如下

理解Window和WindowManager(一)window的添加删除更新view_第4张图片

五、小结

这里主要总结了下window添加删除更新view的情况,下节总结下各种window的创建

参考文章:https://www.jianshu.com/p/7e589ddb634a

The end

本文来自<安卓开发艺术探索>笔记总结
下篇:理解Window和WindowManager(二)三种window的创建过程

你可能感兴趣的