Window
是一个抽象类,它的具体实现是 PhoneWindow
。创建一个Window是很简单的事情,只需要通过 WindowManager
即可完成。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。
Android中所有的视图都是通过Window来呈现的,不管是Activity、Dialog、Toast,它们的视图实际上都是附加在Window上,因此Window实际是View的直接管理者。
// WindowManager添加一个Window的操作过程
Button button = new Button(this);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
lp.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_SHOW_WHEN_LOCKED;
lp.gravity = Gravity.LEFT | Gravity.TOP;
lp.x = 100;
lp.y = 300;
mWindowManager.addView(button, lp);
WindowManager.LayoutParams.flags
参数表示Window的属性,通过这些选项可以控制Window的显示特性,主要介绍几个比较常用的选项:
表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用 FLAG_NOT_TOUCH_MODAL
,最终事件会直接传递给下层的具有焦点的Window
在此模式下,系统会将当前Window区域以外的单击事件传递给底层Window,当前Window区域以内的单击事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法接收到单击事件
开启此模式可以让Window显示在锁屏的界面上
WindowManager.LayoutParams.type
参数表示Window的类型,Window有三种类型,分别是应用Window、子Window和系统Window。应用类Window对应着一个Activity;子Window不能单独存在,它需要附属在特定的父Window中,比如Dialog;系统Window需要声明权限才能创建的Window,比如Toast和系统状态栏都是系统Window。
Window是分层的,层级大的会覆盖层级小的Window。
应用Window层级范围:1~99
子Window层级范围:1000~1999
系统Window层级范围:2000~2999
系统层级有跟多值,一般我们可以选用 TYPE_SYSTEM_OVERLAY
或者 TYPE_SYSTEM_ERROR
,并且声明权限:
// TYPE_SYSTEM_ERROR已被废弃,使用WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
WindowManager.LayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
<used-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
WindowManager
所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,这三个方法定义在 ViewManager
中,而WindowManager继承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并向其添加View、更新Window中的View,如果想删除Window,那么只需要删除它里面的View即可。由此看来,WindowManager操作Window的过程更像是在操作Window中的View。
// 拖动Window的效果,只需要根据手指的位置来设定LayoutParams中的x和y的值即可改变Window位置
mFloatingButton.setOnTouchListener(this);
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams);
break;
}
return false;
}
Window
是一个抽象概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl建立联系,因此Window并不是实际存在的,它是以View的形式存在。这点从WindowManager提供的三个接口方法 addView
、updateViewLayout
、removeView
可以看出,View才是Window存在的实体。
Window
的操作入口是WindowManager,而WindowManager的真正实现是 WindowManagerImpl
。
// 可以发现,WindowManagerImpl没有直接实现Window的三大操作,而是全部交给WindowManagerGlobal处理
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
WindowMangerGlobal
的addView主要分为如下几步:
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;
if (parentWindow != null) {
// 添加的是子Window
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
// WindowManagerGlobal内部比较重要的集合列表
// 存储所有Window所对应的View
private final ArrayList<View> mViews = new ArrayList<>();
// 存储所有Window所对应的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<>();
// 存储所有Window所对应的布局参数
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<>();
// 存储正在被删除的View对应,即那些已经调用removeView方法但删除操作还未完成的Window对象
private final ArraySet<View> mDyingViews = new ArraySet<>();
// addView方法添加View
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// 这个步骤由ViewRootImpl的setView方法完成
// setView内部会通过requestLayout来完成异步刷新请求,scheduleTraversals绘制View,对View进行测量、布局、绘制过程
public void requestLayout() {
if (!mHandingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
// 接着通过WindowSession完成Window的添加过程
// mWindowSession的类型是IWindowSesssion,它是一个Binder对象,真正的实现类是Session
// Window的添加过程是一个IPC调用
try {
mOriginWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, 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);
}
// Session内部会通过WindowManagerService来实现Window的添加
// WindowManagerService内部会为每一个应用保留一个单独的Session
public int addToDisplay(Window window, int seq, WindowManager.LayoutParams attr,
int viewVisibility, int displayId, Rect outContentInsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outInputChannel);
}
Window的删除过程和添加过程流程走向是一样的。
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索引
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate); // 删除View
if (currView == view) return;
throw new IlleagelStateException("Calling with view " + view +
"but the ViewAncestor is attached to " + curView);
}
}
// 在WindowManager中提供了两种删除接口removeView和removeViewImmediate
// 分别表示异步和同步删除,一般不需要使用removeViewImmediate删除Window以免发生意外错误
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());
}
}
boolean deferred = root.die(immediate); // 交由ViewRootImpl删除
if (view != null) {
view.assignParent(null);
if (deffered) {
mDyingViews.add(view); // 异步删除,添加到待删除列表
}
}
}
boolean die(boolean immediate) {
if (immediate && !mIsTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(...);
}
// 异步删除只发送了一条消息,Handler会调用doDie()
// doDie()内部会调用dispatchDetachedFromWindow()真正删除View
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
dispatchDetachedFromWindow
主要做四件事:
垃圾回收相关工作,比如清除数据和消息、移除回调
通过Session的remove()删除Window;mWindowSession.remove(mWindow)
,同样是一个IPC过程,最终会调用 WindowManagerService.removeWindow()
调用View的 dispatchDetachedFromWindow()
,在内部会调用View的 onDetachedFromWindow()
以及 onDetachedFromWindowInternal()
。
调用WindowManagerGlobal的 doRemoveView()
刷新数据,包括mRoots、mParams以及mDyingViews,需要将当前Window所关联的这三类对象从列表中删除
// Window的更新过程比较简单,首先更新View的LayoutParams,再更新ViewRootImpl的LayoutParams
// 然后ViewRootImpl通过scheduleTraversals()重新测量、布局、重绘
// ViewRootImpl还会通过WindowSession更新Window视图,最终是由WindowManagerService的relayoutWindow()实现更新。
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.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);
}
}
无论是添加、删除还是更新都是走这个流程,最终都会由 WindowManagerService
处理。
WindowManager
(WindowManagerImpl)
-> WindowManagerGlobal
-> ViewRootImpl
-> WindowSession
(Session
) -> WindowManagerService
这里只会简述Activity、Dialog和Toast的Window创建过程,只分析流程不具体深入代码实现。
Activity中的Window通过 PolicyManager
的 makeNewWindow()
完成,makeNewWindow()
创建的是 PhoneWindow
。此时Activity的Window就创建完成了,下面分析Activity的视图怎么附属在Window上。
Activity的视图由 setContentView()
提供,Activity会在这个方法将具体实现交给PhoneWindow,PhoneWindow的 setContentView()
大致遵循如下几个步骤(setContentView的具体流程):
DecorView
是一个FrameLayout,是Activity中的顶级View,一般来说它的内部包含标题栏和内容栏,内容固定id为是 android.R.id.content
。DecorView的创建过程由 installDecor()
完成,方法内部会通过 generateDecor()
直接创建DecorView,这个时候DecorView还是一个空白的FrameLayout。
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
为了初始化DecorView结构,PhoneWindow还需要通过 generateLayout()
加载具体的布局到DecorView中。
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
// public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content
ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
步骤1已经创建了DecorView,因此这一步直接将Activity的视图添加到DecorView的 mContentParent
中:mLayoutInflater.inflate(layoutResID, mContentParent)
。
从这里可以理解Activity的setContentView方法的来历了,因为Activity的布局文件只是被添加到DecorView的mContentParent中,叫setContentView比setView更合适。
通知Activity做相应的回调处理。
但此时DecorView还没有被WindowManager添加到Window中。
在ActivityThread的 handleResumeActivity()
中,首先会调用Activity的 onResume()
,接着调用Activity的 makeVisible()
,正式在makeVisible(),DecorView真正完成了添加和显示这两个过程,到这里Activity的视图才能被用户看到。
void makeVisible() {
if (!mWindowAdded) {
ViewManager vm = getWindowManager();
vm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
总结一下Activity的Window创建流程:
Activity进行启动流程,通过 PolicyManager
创建出 PhoneWindow
-> 创建 DecorView
(空白的FrameLayout) -> PhoneWindow
加载具体的布局到 DecorView
(标题栏和内容栏) -> DecorView
填充布局内容View -> Activity回调onResume,WindowManager
将DecorView添加到PhoneWindow中完成显示
Dialog的Window创建过程和Activity类似。
也是通过 PolicyManager
创建PhoneWindow
这个过程也和Activity的 setContentView()
类似,创建DecorView,PhoneWindow加载布局到DecorView,DecorView通过布局文件填充内容View
在Dialog的 show()
中,会通过WindowManager将DecorView添加到Window中:
mWindowManager.addView(mDecor, 1);
mShowing = true;
当Dialog被关闭时,会通过WindowManager移除DecorView:
mWindowManager.removeViewImmediate(mDecor);
普通的Dialog有一个特殊之处,就是必须采用Activity的Context,如果采用Application的Context就会报错提示找不到token,因为token一般只有Activity拥有。
Toast
也是基于Window来实现的,但是由于Toast具有定时取消这一功能,所以系统采用了Handler。在Toast内部有两类IPC过程,第一类是Toast访问 NotificationManagerService
(NMS),第二类是NotificationManagerService回调Toast里的TN接口。
Toast属于系统Window,它内部的视图由两种方式指定,一种是系统默认样式,另一种是通过 setView()
指定一个自定义View,它们都对应Toast的一个View类型的内部成员 mNextView
。Toast提供 show()
和 cancel()
显示和隐藏Toast,它们内部是一个IPC过程。
public void show() {
if (mNextView == null) throw new RuntimeException("setView must have been called");
INotificationManager service = getService(); // Binder, NMS
String pkg = mContext.getOpPackageName();
// Binder,可以把TN理解为Toast和NMS进行IPC时的回调
// Toast和NMS进行IPC过程中,当NMS处理Toast的现实或隐藏请求时会跨进程回调TN中的方法
// TN运行在Binder线程池中,需要通过Handler将其切换到当前线程中
// 这里的当前线程是指发送Toast请求所在的线程
// 由于这里使用了Handler,意味着Toast无法在没有Looper的线程中弹出
TN tn = mTN;
tn.mNextView = mNextView;
try {
// enqueueToast将Toast请求封装为ToastRecord对象并添加其添加到mToastQueue队列中
// 对于非系统应用,mToastQueue最多能同时存在50个ToastRecord
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {}
}
public void cancel() {
mTN.hide();
try {
getService().cancelToast(mContext.getPackageName(), mTN);
} catch (RemoteException e) {}
}
// 当ToastRecord被添加到mToastQueue后,NMS会通过showNextToastLocked显示当前的Toast
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
try {
// callback实际上就是Toast中的TN对象的远程Binder
// 最终被调用的TN中的方法会运行在发起Toast请求的应用的Binder线程池中
record.callback.show();
// 发送延时消息隐藏Toast
scheduleTimeoutLocked(record);
return;
} catch (RemoteException e) {}
}
}
private void scheduleTimeoutLocked(ToastRecord r) {
mHandler.removeCallbackAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
mHandler.sendMessageDelayed(m, delay);
}
try {
// 接收scheduleTimeoutLocked的延时消息隐藏Toast,也是一次IPC过程
record.callback.hide();
} catch (RemoteException e) {}
// TN有两个方法show和hide,这两个方法是被NMS以跨进程方式调用,因此它们运行在Binder线程池中
// 为了将执行环境切换到Toasst请求所在的线程,内部使用了Handler
// mShow和mHide是Runnable,内部分别调用了handleShow()和handleHide()真正完成显示和隐藏Toast
@Override
public void show() {
mHandler.post(mShow);
}
@Override
public void hide() {
mHandler.post(mHide);
}
// Toast显示和隐藏最终会将Toast视图添加到Window中
// handleShow()
mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);
// handleHide()
if (mView.getParent() != null) {
mWM.removeView(mView);
}