Window 是一个抽象类,它的具体实现是 PhoneWindow,创建一个 Window 非常简单,只需要通过 WindowManager 即可完成。 WindowManager 是外界访问 Window 的入口。Window 的具体实现位于 WindowManagerService 中,WindowManager 和 WindowManager 的交互是一个 IPC 的过程。在 Android 中,所有的视图都是通过 Window 来呈现的,不管是 Activity、Dialog 还是 Toast ,他们的视图实际上都是附加到 Window 上的,因此 Window 实际上是 View 的直接管理者。
一、 Window 和 WindowManager
为了分析 Window 的工作机制,我们需要先了解如何使用 WindowManager 添加一个 Window。下面的代码通过 WindowManager 添加 Window 的过程。
WindowManager manager = this.getWindowManager();
Button button = new Button(this);
button.setText("button");
LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT
, LayoutParams.WRAP_CONTENT,1,0, PixelFormat.TRANSPARENT);
layoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_SHOW_WHEN_LOCKED ;
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
layoutParams.x = 100;
layoutParams.y = 300;
manager.addView(button,layoutParams);
上面的代码可以将 Button 添加到屏幕坐标为(100,300) 的位置上。WindowManager.LayoutParams 中的 flag 和 type 这两个参数比较重要。
flag 参数
flag 参数表示 Window 的属性,它有很多选项,通过这些选项可以控制 Window 的显示特性。这里介绍常用的特性:
FLAG_NOT_FOCUSABLE
表示 Window 不需要获取焦点,也不需要接收各种输入事件,此时标记会同时启用 FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的 Window。FLAG_NOT_TOUCH_MODAL
在此模式下,系统将当前 Window 区域以外的单击事件传递给底层的 Window ,当前 Window 区域以内的单击事件则自己处理。一般来说都会开启这个标记。FLAG_SHOW_WHEN_LOCKED
开启此模式可以让 Window 显示在锁屏界面上。
Type 参数
Type 表示 Window 的类型,Window 有三种类型,分别是应用 Window、子 Window 和系统 Window。应用类 Window 对应一个 Activity;子 Window 不能单独存在,需要附属在特定的父 Window,如常见的 Dialog;系统 Window 需要声明权限才能创建,如 Toast 和系统状态栏。
Window 是分层的,每个 Window 都有对应的 z-ordered,层级大的会覆盖在层级小的上面。在上面的三类 Window 中,应用层 window 的层级范围是 1 ~ 99; 子 Window 的层级范围是 1000 ~ 1999;系统 Window 的层级范围是 2000 ~ 2999;这些层级范围对用着 WindowManger.LayoutParams 的 type 参数,如果想要 Window 位于所有 Window 的最顶层,那么采用最大层级就行了。系统层级有很多值,一般我们选择 TYPE_SYSTEM_OVERLAY 或者 TYPE_SYSTEM_ERRO 就行。如我们可以指定:
layoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;
同时需要声明权限:
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);
}
对于我们来说,大部分 Window 的操作都可以通过这三个方法来实现,如拖动 View 的效果,就是根据手指的位置来设置 LayoutParams 中的 x 和 y 的值就行了。如:
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:{
layoutParams.x = rawX;
layoutParams.y = rawY;
manager.updateViewLayout(button,layoutParams);
}
break;
default:
break;
}
return false;
}
});
二、Window 的内部机制
Window 是一个抽象的概念,每一个 Window 都对应着一个 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,Window 和 View 通过 ViewRootImpl 来建立联系,因此 Window 并不是实际存在的,而是以 View的形式表现出来。在实际使用中不能直接访问 Window ,对 Window 的访问必须通过 WindowManager。
2.1 Window 的添加过程
Window 的添加过程需要通过 WindowManager 的 addView 来实现,WindowManager 是一个接口。它的真正实现是 WindowManagerImpl 类。在 WindowManagerImpl 的三大操作的实现如下:
*/
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
private IBinder mDefaultToken;
public WindowManagerImpl(Context context) {
this(context, null);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
return new WindowManagerImpl(displayContext, mParentWindow);
}
public void setDefaultToken(IBinder token) {
mDefaultToken = token;
}
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
...
}
可以发现,WindowManagerImpl 并没有直接实现 Window 的三大操作,而是全部交给了 WindowManagerGlobal 来处理,WindowManagerGlobal 以工厂的形式向外提供自己的实例,在 WindowManagerGlobal 中有如下一段代码:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
WindowManagerImpl 的这种工作模式是典型的桥接模式,将所有的操作全部委托给 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");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent and we're running on L or above (or in the
// system context), assume we want hardware acceleration.
final Context context = view.getContext();
if (context != null
&& context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
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);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
如上代码,addView 主要分为如下几步:
- 检查参数是否合法,如果是子 Window 那么还需要调整一下布局参数
- 创建 ViewRootImpl 并将 View 添加到列表中。
- 通过 ViewRootImpl 来更新界面并完成 Window 的添加过程。
这个过程由 ViewRootImpl 的 setView 方法来完成。其代码如下:
//frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
...
}
这里主要就是调用了mWindowSession的addToDisplay方法。mWindowSession是IWindowSession类型的,它是一个Binder对象,用于进行进程间通信,IWindowSession是Client端的代理,它的Server端的实现为Session,此前包含ViewRootImpl在内的代码逻辑都是运行在本地进程的,而Session的addToDisplay方法则运行在WMS所在的进程。
//frameworks/base/services/core/java/com/android/server/wm/Session.java
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
这样一来,Window 的添加请求就交给 WindowManagerService 去处理了,在 WindowManagerService 内部为每一个应用保留一个单独的 Session。具体的添加过程这里不再分析。
2.2 Window 的删除过程
Window 的删除过程和添加过程一样,都是先通过 WindowManagerImpl 后,再通过 WindowManagerGlobal 来实现的。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);
}
}
在 removeView 中,先通过 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());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
removeViewLocked 是通过 ViewRootImpl 来完成删除操作的。在 WindowManager 中提供了两种接口:removeView 和 removeViewImmediate,分别表示同步和异步删除。这里主要说异步删除的过程,具体的删除由 ViewRootImpl 的 die 方法来完成。在异步删除的情况下,die 方法只是发送了一个请求删除的消息后就立刻返回了,这个时候 View 并没有完成删除操作,所以最后会将其添加到 mDyingViews 中,mDyingViews 表示待删除的 View 列表。ViewRootImpl 的 die 方法如下:
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
在 die 方法内部只是做了简单的判断,如果是异步操作,那么就发送一个 MSG_DIE 的消息,ViewRootImpl 中的 Handler 会处理此消息并调用 doDie 方法,如果是同步删除,那么就不发消息直接调用 doDie 方法。而 doDie 内部会调用 dispatchDetachedFromWindow 方法,
void dispatchDetachedFromWindow() {
mFirstInputStage.onDetachedFromWindow();
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
}
mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mAccessibilityManager.removeHighTextContrastStateChangeListener(
mHighContrastTextManager);
removeSendWindowContentChangedCallback();
destroyHardwareRenderer();
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 {
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
// Dispose the input channel after removing the window so the Window Manager
// doesn't interpret the input channel being closed as an abnormal termination.
if (mInputChannel != null) {
mInputChannel.dispose();
mInputChannel = null;
}
mDisplayManager.unregisterDisplayListener(mDisplayListener);
unscheduleTraversals();
}
这个方法主要实现了四件事情:
- 垃圾回收工作,如清除数据,移除回调。
- 通过 Session 的 remove 方法来移除 Window。
- 调用 View 的 dispathcDetachedFromWindow 方法。
- 调用 WindowManagerGloabl 的 doRemoveView 方法刷新数据。
2.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.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);
}
}
这里主要就是用新的 LayoutParams 替换掉旧的 LayoutParams,接着更新 ViewRootImpl 中的 LayoutParams,这一步是通过 ViewRootImpl 的 setLayoutParams 方法来实现的。在 ViewRootImpl 中会通过 scheduleTraversals 方法来对 View 重新布局,包括测量、布局、重绘这三个火车。除了 View 本身的重绘之外,ViewRootImpl 还会通过 WindowSession 来更新 Window 的视图。这个过程由 WindowManagerService 的 relayoutWIndow() 来实现,是一个 IPC 过程。
三、 Window 的创建过程
这里我们需要知道的是,在 Android 系统中,View 是 Android 中的视图的呈现方式,但是 View 不能单独存在,必须依附到 Window 上,也就是有视图的地方,就有 Window ,如 Activity、Dialog、Toast 以及 PopupWindow 等。
3.1 Activity 的 Window 的创建过程
要分析 Activity 中的 Window 的创建过程,就必须要了解 Acitvity 的启动过程,这里大致介绍一下就是,Activity 的启动过程最终室友 ActivityThread 中的 performLaunchAcitvity() 来完成整个启动过程的,这个方法内部会通过类加载器创建 Activity 的实例,并调用 attach 方法为其关联运行过程中的上下文环境。
ActivityThread#performLaunchActivity() 代码如下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//...
//通过类加载器创建Activity实例
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
//...
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
//关联运行过程中所依赖的一系列上下文环境变量
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.voiceInteractor);
mActivities.put(r.token, r);
return activity;
}
在attach方法里,系统会创建Activity所属的Window对象并为其设置回调接口。
attach 的实现如下:
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, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
//创建Window对象
mWindow = PolicyManager.makeNewWindow(this);
//设置回调 比如我们熟悉的onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent
//[重要] 当Window接收到外界的状态改变时就会调用Activity实现的回调
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(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();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
//...
//设置WindowManager
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;
}
再来看 PolicyManager.makeNewWindow(this),PilicyManager的真正实现类是Policy类。
位于source\frameworks\base\policy\src\com\android\internal\policy\impl\Policy.java
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
Window初始完毕后,再来看Activity的视图是如何依附在Window上的。Activity的视图通过setContent方法提供。
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以发现,Activity的setContentView将具体实现交由Window处理。
phoneWindow#setContentView的实现如下:
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
//1.创建DecorView (如果没有创建的话)
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
//2.将View添加到DecorView的mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
//3.回调Activity的onContentChanged()通知Activity视图已经发生改变
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
经过setContentView方法,DecorView已经被创建并初始化完毕,Activity的布局文件也已经成功添加到DecorView的mContentParent中,但这个时候DecorView还没有被WindowManager添加到Window中。
- Activity的attach()中,Window被创建并初始化
- 在Activity的setContentView中 (PhoneWindow#setContentView),DecorView被创建 (如果没被创建的话)
- 而在ActivityThread#handleResumeActivity首先会调用Activity的onResume方法中,接着会先将DecorView设为不可见(INVISIBLE),然后会调用Activity的makeVisible(),在makeVisible()中,将DecorView添加到Window并置为Visible。
void makeVisible() {
//将DecorView添加到Window
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
//将Activity的显示置为Visible
mDecor.setVisibility(View.VISIBLE);
}
正是在 makeVisible 方法中,DecorView 真正的完成了添加和显示过程,到这里 Activity 的视图才能被看到。
3.2 Dialog 的 Window 的创建过程
Dialog 的 Window 的创建过程和 Acivity 类似,主要有如下几个步骤:
1、创建 Window
Dialog 中的 Window的创建同样是通过 PolicyManager 的 makeNewWindow 来完成的,创建后的对象实际上就是 PhoneWindow,这个过程和 Acitivity 的 Window 的创建过程是一致的。这里不在说明了。
Dialog 的构造方法如下:
Dialog(Context context, int theme, boolean createContextThemeWrapper) {
//...
mContext = context;
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
//创建Window对象
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
2、初始化 DecorView 并将 Dialog 的视图添加到 DevorView 中
这个过程也和 Activity 类似,都是通过 Window 去添加指定的布局文件。
public void setContentView(int layoutResID) {
mWindow.setContentView(layoutResID);
}
3、将 DecorView 添加到 Window 中
在 Dialog 的 show 方法中,会通过 WindowManager 将 DecorView 添加到 Window 中,如下:
mWindowManager.addView(mDevor,1);
mShowing = true;
这里需要注意,普通的 Dialog 有一个特殊的地方,那就是必须采用 Activity 的 Context,如果采用 Application 的 Context,就会报错。原因是没有用 token 导致的,而应用 token 一般只有 Activity 所有,所以这里要用 Activity 做为 Contex 显示对话框。
最后,当Dialog dismiss时,会通过WindowManager来移除DecorView
@Override
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
void dismissDialog() {
//...
//移除DecorView
mWindowManager.removeViewImmediate(mDecor);
//...
}
3.3 Toast 的 Window 的创建过程
Toast 和 Dialog 不同,它的工作过程就稍微显得复杂。首先 Toast 也是基于 Window 来实现的,但是由于 Toast 具有定时取消的功能,所以系统采用了 Handler。在 Toast 的内部有两类 IPC 过程,第一类就是 Toast 访问 NotificationManagerService,第二类是 NotificationManagerService 回掉 Toast 里的 TN 接口。
Toast提供了show和cancel分别用于显示和隐藏Toast 。
show() 和 cancel
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;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
public void cancel() {
mTN.hide();
try {
getService().cancelToast(mContext.getPackageName(), mTN);
} catch (RemoteException e) {
// Empty
}
}
从上面代码可以看到,显示和隐藏 Toast 都需要通过 NMS 来实现,由于 NMS 运行在系统过程中,所以只能通过远程调用的方式来显示和隐藏 Toast 。需要注意的是 TN 这个类,是一个 Binder 类,在 Toast 和 NMS 进行 IPC 的过程中,当 NMS 处理 Toast 的显示和隐藏请求是都会回掉 TN 中的方法。这个时候,TN 运行在 Binder 线程池中,所以需要通过 hander 将其切换到当前线程中。
Toast 的显示过程,主要是调用了 NMS 中的 enqueueToast 方法。
/参数一:当前应用的包名
//参数二:远程回调
//参数三:Toast的时长
//enqueueToast首先将Toast封装为ToastRecord对象并将其添加到一个名为mToastQueue的队列中
service.enqueueToast(pkg, tn, mDuration);
当ToastRecord被添加到mToastQueue中后,Inotifacationmanager就会通过showNextToastLacked方法来显示当前的Toast。
NotificationManagerService#showNextToastLocked() 如下:
void showNextToastLocked() {
//获取下一个ToastRecord
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
try {
//record.callback就是Toast.java类中的TN (Binder) 对象
record.callback.show();
//发送延迟消息来移除toast
//scheduleTimeoutLocked -> mHandler.sendMessageDelayed -> cancelToastLocked -> record.callback.hide();
scheduleTimeoutLocked(record);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to show notification " + record.callback
+ " in package " + record.pkg);
// remove it from the list and let the process die
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
keepProcessAliveLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
可以发现,Toast的显示和移除都是通过Toast的TN类(Binder对象)来完成的。
Toast内部类TN 如下:
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
@Override
public void run() {
handleShow();
}
};
final Runnable mHide = new Runnable() {
@Override
public void run() {
handleHide();
// Don't do this in handleHide() because it is also invoked by handleShow()
mNextView = null;
}
};
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
//...
}
TN 中的 show 和 hide 方法对于的 Runnable 如下:
public void handleShow() {
//...
mWM = (WindowManager)context.getSystemService
//...
if (mView.getParent() != null) {
mWM.removeView(mView);
}
mWM.addView(mView, mParams);
//...
}
}
public void handleHide() {
if (mView != null) {
if (mView.getParent() != null) {
mWM.removeView(mView);
}
mView = null;
}
}
《Android 开发艺术探索》 学习整理