理解Window和WindowManager(一)

一、概述

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);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容