
点击上方蓝字关注我,天天一见,给你力量java

前言
关于Window,你了解多少呢?看看下面这些问题你都能答上来吗。android
若是你遇到这些问题
-
Window是什么?和View的关系? -
WindowManager是什么?和WMS的关系? -
怎么添加一个Window? -
Window怎样能够显示到锁屏界面 -
Window三种类型都存在的状况下,显示层级是怎样。 -
Window就是指PhoneWindow吗? -
PhoneWindow何时被建立的? -
要实现能够拖动的View该怎么作? -
Window的添加、删除和更新过程。 -
Activity、PhoneWindow、DecorView、ViewRootImpl 的关系? -
Window中的token是什么,有什么用? -
Application中能够直接弹出Dialog吗? -
关于事件分发,事件究竟是先到DecorView仍是先到Window的?
Window是什么
窗口。你能够理解为手机上的整个画面,全部的视图都是经过Window呈现的,好比Activity、dialog
都是附加在Window上的。Window类的惟一实现是PhoneWindow
,这个名字就更加好记了吧,手机窗口呗。web
那Window
到底在哪里呢?咱们看到的View是Window吗?是也不是。面试
-
若是说的只是
Window概念
的话,那能够说是的,View就是Window的存在形式,Window管理着View。微信 -
若是说是
Window类
的话,那确实不是View,惟一实现类PhoneWindow管理着当前界面上的View,包括根布局——DecorView,和其余子view的添加删除等等。架构
不知道你晕没有,我总结下,Window
是个概念性的东西,你看不到他,若是你能感知它的存在,那么就是经过View,因此View是Window的存在形式,有了View,你才感知到View外层有一个皇帝的新衣
——window。app
WindowManager是什么?和WMS的关系?
WindowManager
就是用来管理Window的,实现类为WindowManagerImpl
,实际工做会委托给WindowManagerGlobal
类中完成。异步
而具体的Window操做,WM会经过Binder
告诉WMS,WMS作最后的真正操做Window的工做,会为这个Window分配Surface,并绘制到屏幕上。编辑器
怎么添加一个Window?
var windowParams: WindowManager.LayoutParams = WindowManager.LayoutParams()
windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
windowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG
var btn = Button(this)
windowManager.addView(btn, windowParams)
简单贴了下代码,加了一个Button。ide
有的朋友可能会疑惑了,这明明是个Button,是个View啊,咋成了Window?
刚才说过了,View是Window的表现形式,在实际实现中,添加window其实就是添加了一个你看不到的window,而且里面有View才能让你感受获得这个是一个Window。
因此经过windowManager
添加的View其实就是添加Window的过程。
这其中还有两个比较重要的属性:flags和type
,下面会依次说到。
Window怎样能够显示到锁屏界面
Window的flag能够控制Window
的显示特性,也就是该怎么显示、touch事件处理、与设备的关系、等等。因此这里问的锁屏界面显示也是其中的一种Flag。
// Window不须要获取焦点,也不接受各类输入事件。
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
// @deprecated Use {@link android.R.attr#showWhenLocked} or
// {@link android.app.Activity#setShowWhenLocked(boolean)} instead to prevent an
// unintentional double life-cycle event.
// 窗口能够在锁屏的 Window 之上显示
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
Window三种类型都存在的状况下,显示层级是怎样。
Type表示Window的类型,一共三种:
-
应用Window
。对应着一个Activity,Window层级为1~99,在视图最下层。 -
子Window
。不能单独存在,须要附属在特定的父Window之中(如Dialog就是子Window),Window层级为1000~1999。 -
系统Window
。须要声明权限才能建立的Window,好比Toast和系统状态栏,Window层级为2000-2999,处在视图最上层。
能够看到,区别就是有个Window层级(z-ordered),层级高的能覆盖住层级低的,离用户更近。
Window就是指PhoneWindow吗?
若是有人问我这个问题,我确定内心要大大的疑惑了🤔。
可不就是PhoneWindow
吗?都惟一实现类了,净问些奇怪问题。
可是面试的时候遇到这种问题总要答啊?这时候就要扯出Window的概念了。
-
若是指的Window类,那么PhoneWindow做为惟一实现类,通常指的就是PhoneWindow。
-
若是指的Window这个概念,那确定不是指
PhoneWindow
,而是存在于界面上真实的View。固然也不是全部的View都是Window,而是经过WindowManager添加到屏幕的view才是Window,因此PopupWindow
是Window,上述问题中添加的单个View也是Window。
PhoneWindow何时被建立的?
熟悉Activity启动流程的朋友应该知道,启动过程会执行到ActivityThread的handleLaunchActivity
方法,这里初始化了WindowManagerGlobal
,也就是WindowManager实际操做Window的类,待会会看到:
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
//...
WindowManagerGlobal.initialize();
//...
final Activity a = performLaunchActivity(r, customIntent);
//...
return a;
}
而后会执行到performLaunchActivity中建立Activity,并调用attach方法进行一些数据的初始化(伪代码):
final void attach() {
//初始化PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
//和WindowManager关联
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
}
能够看到,在Activity的attach方法中,建立了PhoneWindow,而且设置了callback,windowManager
。
这里的callback待会会说到,跟事件分发有关系,能够说是当前Activity和PhoneWindow创建联系。
要实现能够拖动的View该怎么作?
仍是接着刚才的btn例子,若是要修改btn的位置,使用updateViewLayout便可,而后在ontouch方法中传入移动的坐标便可。
btn.setOnTouchListener { v, event ->
val index = event.findPointerIndex(0)
when (event.action) {
ACTION_MOVE -> {
windowParams.x = event.getRawX(index).toInt()
windowParams.y = event.getRawY(index).toInt()
windowManager.updateViewLayout(btn, windowParams)
}
else -> {
}
}
false
}
Window的添加、删除和更新过程。
Window的操做都是经过WindowManager
来完成的,而WindowManager是一个接口,他的实现类是WindowManagerImpl
,而且所有交给WindowManagerGlobal
来处理。下面具体说下addView,updateViewLayout,和removeView。
1)addView
//WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
ViewRootImpl root;
View panelParentView = null;
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView);
}
}
}
-
这里能够看到,建立了一个ViewRootImpl实例,这样就说明了每一个Window都对应着一个ViewRootImpl。 -
而后经过add方法修改了 WindowManagerGlobal
中的一些参数,好比mViews—存储了全部Window所对应的View,mRoots——全部Window所对应的ViewRootImpl,mParams—全部Window对应的布局参数。 -
最后调用了ViewRootImpl的setView方法,继续看看。
final IWindowSession mWindowSession;
mWindowSession = WindowManagerGlobal.getWindowSession();
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
//
requestLayout();
res = mWindowSession.addToDisplay(mWindow,);
}
setView
方法主要完成了两件事,一是经过requestLayout方法完成异步刷新界面的请求,进行完整的view绘制流程。其次,会经过IWindowSession进行一次IPC调用,交给到WMS来实现Window的添加。
其中mWindowSession是一个Binder对象,至关于在客户端的代理类,对应的服务端的实现为Session,而Session就是运行在SystemServer进程中,具体就是处于WMS服务中,最终就会调用到这个Session的addToDisplay方法,从方法名就能够猜到这个方法就是具体添加Window到屏幕的逻辑,具体就不分析了,下次说到屏幕绘制的时候再细谈。
2)updateViewLayout
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
//...
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);
}
}
这里更新了WindowManager.LayoutParams
和ViewRootImpl.LayoutParams
,而后在ViewRootImpl内部一样会从新对View进行绘制,最后经过IPC通讯,调用到WMS的relayoutWindow完成更新。
3)removeView
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
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);
}
}
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
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);
}
}
}
该方法中,经过view找到mRoots
中的对应索引,而后一样走到ViewRootImpl
中进行View删除工做,经过die
方法,最终走到dispatchDetachedFromWindow()
方法中,主要作了如下几件事:
-
回调onDetachedFromeWindow。 -
垃圾回收相关操做; -
经过Session的remove()在WMS中删除Window; -
经过Choreographer移除监听器
Activity、PhoneWindow、DecorView、ViewRootImpl 的关系?
看完上面的流程,咱们再来理理这四个小伙伴之间的关系:
-
PhoneWindow
实际上是 Window 的惟一子类,是Activity
和 View 交互系统的中间层,用来管理View的,而且在Window建立(添加)的时候就新建了ViewRootImpl实例。 -
DecorView
是整个 View 层级的最顶层,ViewRootImpl
是DecorView 的parent,可是他并非一个真正的 View,只是继承了ViewParent接口,用来掌管View的各类事件,包括requestLayout、invalidate、dispatchInputEvent 等等。

Window中的token是什么,有什么用?
token?又是个啥呢?刚才window操做过程当中也没出现啊。
token
其实你们应该工做中会发现一点踪影,好比application的上下文去建立dialog的时候,就会报错:
unable to add window --token null
因此这个token跟window操做是有关系的,翻到刚才的addview方法中,还有个细节咱们没说到,就是adjustLayoutParamsForSubWindow方法。
//Window.java
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
//子Window
if (wp.token == null) {
View decor = peekDecorView();
if (decor != null) {
wp.token = decor.getWindowToken();
}
}
} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
//系统Window
} else {
//应用Window
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
}
}
上述代码分别表明了三个Window的类型:
-
子Window
。须要从decorview中拿到token。 -
系统Window
。不须要token。 -
应用Window
。直接拿mAppToken,mAppToken是在setWindowManager方法中传进来的,也就是新建Window的时候就带进来了token。
而后在WMS中的addWindow方法会验证这个token,下次说到WMS的时候再看看。
因此这个token
就是用来验证是否可以添加Window,能够理解为权限验证,其实也就是为了防止开发者乱用context建立window。
拥有token
的context(好比Activity)就能够操做Window。没有token
的上下文(好比Application)就不容许直接添加Window到屏幕(除了系统Window)。
Application中能够直接弹出Dialog吗?
这个问题其实跟上述问题相关:
-
若是直接使用 Application
的上下文是不能建立Window的,而Dialog的Window等级属于子Window,必须依附与其余的父Window,因此必须传入Activity这种有window的上下文。 -
那有没有其余办法能够在 Application
中弹出dialog呢?有,改为系统级Window:
//检查权限
if (!Settings.canDrawOverlays(this)) {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
intent.data = Uri.parse("package:$packageName")
startActivityForResult(intent, 0)
}
dialog.window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG)
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-
另外还有一种办法,在Application类中,能够经过 registerActivityLifecycleCallbacks
监听Activity生命周期,不过这种办法也是传入了Activity的context,只不过在Application类中完成这个工做。
关于事件分发,事件究竟是先到DecorView仍是先到Window的?
通过上述一系列问题,是否是对Window印象又深了点呢?最后再看一个问题,这个是wanandroid论坛上看到的,
这里的window能够理解为PhoneWindow
,其实这道题就是问事件分发在Activity、DecorView、PhoneWindow
中的顺序。
当屏幕被触摸,首先会经过硬件产生触摸事件传入内核,而后走到FrameWork层(具体流程感兴趣的能够看看参考连接),最后通过一系列事件处理到达ViewRootImpl的processPointerEvent
方法,接下来就是咱们要分析的内容了:
//ViewRootImpl.java
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
...
//mView分发Touch事件,mView就是DecorView
boolean handled = mView.dispatchPointerEvent(event);
...
}
//DecorView.java
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
//分发Touch事件
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//cb其实就是对应的Activity
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
//Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
//PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
//DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
事件的分发流程就比较清楚了:
ViewRootImpl——>DecorView——>Activity——>PhoneWindow——>DecorView——>ViewGroup
(这其中就用到了getCallback参数,也就是以前addView中传入的callback,也就是Activity自己)
可是这个流程确实有些奇怪,为何绕来绕去的呢,光DecorView就走了两遍。
参考连接中的说法我仍是比较认同的,主要缘由就是解耦。
-
ViewRootImpl
并不知道有Activity这种东西存在,它只是持有了DecorView。因此先传给了DecorView,而DecorView知道有AC,因此传给了AC。 -
Activity
也不知道有DecorView,它只是持有PhoneWindow,因此这么一段调用链就造成了。
参考
《Android开发艺术探索》 《Android进阶解密》 https://mp.weixin.qq.com/s/wy9V4wXUoEFZ6ekzuLJySQ https://blog.csdn.net/weixin_43766753/article/details/108350589 https://wanandroid.com/wenda/show/12119
感谢你们的阅读,有一块儿学习的小伙伴能够关注下公众号—
码上积木
❤️每日一个知识点,创建完总体系架构。
点在看你最好看


本文分享自微信公众号 - 码上积木(Lzjimu)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。