前言
上篇文章中有说到Actiivity中window的一些知识,今天就和大家一起好好捋捋这个window。
Window是什么?在Android中都用到了哪些地方?
- 首先,它是一个窗口,是
Android
中唯一的展示视图的中介,所有的视图都是通过Window来呈现的,无论是Activity,Dialog或Toast,他们的视图都是附加到WIndow上的,所以Window是View的直接管理者。 Window
是一个抽象类,他的具体实现就是PhoneWindow。Window
的具体实现在WindowManagerService中,但是创建Window或者访问Window的操作都需要WindowManager。所以这就需要WindowManager和WindowManagerService进行交互,交互的方式就是通过IPC,具体涉及的参数就是token。- 每一个
Window
都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl建立联系,所以Window并不是实际存在的,而是以View的形式存在。
涉及到Window的地方:
事件分发机制
。界面上事件分发机制的开始都是这样一个过程:DecorView——>Activity——>PhoneWindow——>DecorView——>ViewGroup
。之前看过一个比较有趣的问题:事件到底是先到DecorView还是先到Window的?,其实是先到DecorView
的,具体逻辑可以自己翻下源码,有机会也可以出篇文章讲讲~各种视图的显示
。比如Activity的setContentView
,Dialog,Toast的显示视图等等都是通过Window完成的。
Window的分层和类别?
- 由于界面上有不止一个的Window,所以就有了分层的概念。每个Window都有自己对应的Window层级—z-ordered,层级大的会覆盖到层级小的上面,类似HTML中的z-index。
Window主要分为三个类别::
应用Window
。对应着一个Activity,Window层级为1~99,在视图最下层。子Window
。不能单独存在,需要附属在特定的父Window之中(如Dialog就是子Window),Window层级为1000~1999。系统Window
。需要声明权限才能创建的Window,比如Toast和系统状态栏,Window层级为2000~2999,处在视图最上层。
Window的内部机制—添加、删除、更新。
- 之前说过,Window的操作都是通过
WindowManager
来完成的,而WindowManager是一个接口,他的实现类是WindowManagerImpl
,并且全部交给WindowManagerGlobal
来处理。下面具体说下addView,updateViewLayout,和removeView。
1) addView
代码语言:javascript复制public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//...
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);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
- 首先,通过add方法修改了
WindowManagerGlobal
中的一些参数,比如mViews—存储了所有Window所对应的View,mRoots——所有Window所对应的ViewRootImpl,mParams—所有Window对应的布局参数。 - 其次,
setView
方法主要完成了两件事,一是通过requestLayout方法完成异步刷新界面的请求,进行完整的view绘制流程。其次,会通过WindowSession进行一次IPC调用,交给到WMS来实现Window的添加。
2)updateViewLayout
代码语言:javascript复制 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
代码语言:javascript复制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移除监听器
Window中的Token是什么?
代码语言:javascript复制public abstract class Window {
private IBinder mAppToken;
}
是Window类中的一个变量,是一个Binder对象
。在Window中主要是实现WindowManagerService和应用所在的进程通信,也就是上文说到的WindowManager和WindowManagerService进行交互。是一个添加view的权限标识
。拥有token的context可以创建界面、进行UI操作,而没有token的context如service、Application,是不允许添加view到屏幕上的。所以它存在的意义就是为了保护window的创建,也是为了防止Application或Service来做进行view或者UI相关的一些操作。
Activity,Dialog,Toast的Window创建过程
上篇文章说过Dialog的创建,先来回顾下:
1)Dialog
代码语言:javascript复制
//构造函数
Dialog(Context context, int theme, boolean createContextThemeWrapper) {
//......
//获取了WindowManager对象,mContext一般是个Activity,获取系统服务一般是通过Binder获取
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
//创建新的Window
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w;
//这里也是上方mWindow.getCallback()为什么是Activity的原因,在创建新Window的时候会设置callback为自己
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
//关联WindowManager与新Window,token为null
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
//show方法
public void show() {
//......
if (!mCreated) {
//回调Dialog的onCreate方法
dispatchOnCreate(null);
}
//回调Dialog的onStart方法
onStart();
//获取当前新Window的DecorView对象
mDecor = mWindow.getDecorView();
WindowManager.LayoutParams l = mWindow.getAttributes();
try {
//把一个View添加到Activity共用的windowManager里面去
mWindowManager.addView(mDecor, l);
//......
} finally {
}
}
可以看到一个Dialog从无到有经历了以下几个步骤:
- 首先创建了一个新的Window,类型是PhoneWindow类型,与Activity创建Window过程类似,并设置
setCallback
回调。 - 将这个新Window与从Activity拿到的
WindowManager
对象相关联,也就是dialog与Activity公用了同一个WindowManager
对象。 - show方法展示Dialog,先回调了Dialog的
onCreate,onStart
方法。 - 然后获取Dialog自己的
DecorView
对象,并通过addView方法添加到WindowManager对象中,Dialog出现到屏幕上。
2)Activity
代码语言:javascript复制final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
//...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
setAutofillOptions(application.getAutofillOptions());
setContentCaptureOptions(application.getContentCaptureOptions());
}
public void setContentView(@LayoutRes int layoutResID) {
// 交给Window
getWindow().setContentView(layoutResID);
// 创建ActionBar
initWindowDecorActionBar();
}
关于Activity的启动流程,相比大伙都知道些,流程最后会走到ActivityThread
中的performLauchActivity
方法,然后会创建Activity的实例对象,并调用attach方法,也就是上述贴的源码。
在这个方法中,创建了新的Window对象,设置回调接口。这个回调接口主要就是用作Window在接收到外界状态改变的时候,就会回调给这个callback,比如onAttachedToWindow、dispatchTouchEvent
方法等,这个上篇文章也有说过,事件分发的时候就是通过在DecorView中这个callback进行分发的。
然后view怎么显示到界面上的呢,Activity可没有show方法哦?其实就是通过setContentView方法。该方法主要做了以下几件事:
- 创建
DecorView
,如果不存在的话。 - 然后将xml中解析到的view添加到DecorView的
mContentParent
中,也就是布局为android.R.id.content的ContentView。 - 回调
onContentChanged
方法,通知Activity视图已经发生改变。
贴张图:
到这里,一个有完整view结构的DecorView
就创建出来了,但是它还没有被显示到手机界面上,也就是没有被添加到Window中。最后要调用了WMS的addView
方法才会被用户真正看到:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
3)Toast
代码语言:javascript复制 public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
final int displayId = mContext.getDisplayId();
try {
service.enqueueToast(pkg, tn, mDuration, displayId);
} catch (RemoteException e) {
// Empty
}
}
public void cancel() {
mTN.cancel();
}
//class TN
public void handleShow() {
// ......
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);
}
public void handleHide() {
if (mView != null) {
if (mView.getParent() != null) {
mWM.removeView(mView);
}
mView = null;
}
}
Toast有点不同的在于,它内部维护了两个IPC通信,一个是NotificationManagerService
,一个是回调TN
接口。最终的实现都是走到TN.class的handleShow
和handleHide
方法,也就是addView和removeView。
总结
关于Window
的内容太多了,今天好多地方都是简单的一笔介绍带过,其中还有很多很多逻辑,以后有时间再慢慢和大家聊聊,就这样了晚安。
参考
《Android开发艺术探索》