概要
- Window表示一个窗口的概念,在日常开发中直接接触到Window的机会不多,但是在某些特殊的场合我们需要在桌面上显示一个类似悬浮窗的东西,那么这种效果就需要用到Window来实现。
- Window是一个抽象类,它的具体实现是PhoneWindow。创建一个Window是很简单的事,只需要通过WindowManager即可完成。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。
- android中所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,它们的视图实际上都是附加在Window上的,因此,Window实际是View的直接管理者。
- 从WMS的角度来看,一个窗口并不是Window类,而是一个View类。WMS接收到用户消息后,需要把消息派发到窗口,View类本身并不能直接接收WMS传递过来的消息,真正接收用户消息的必须是IWindow类,而实现IWindow类的是ViewRoot.W类,每一个W类内部都包含了一个View变量。
- WMS并不介意该窗口(View)是属于哪个应用程序的,WMS会按一定的规则判断哪个窗口处于活动状态,然后把用户消息给W类,W类再把用户消息传递给内部的View变量,剩下的消息处理就由View对象完成。
Window和WindowManager
Window是一个抽象的概念,每一个Window都对应这一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此Window并不是实际存在的,它是以View的形式存在。这点从WindowManager的定义可以看出,它提供的三个接口方法addView、updateViewLayout以及removeView都是针对View的,这说明View才是Window存在的实体。在实际使用中无法直接访问Window,对Window的访问必须通过WindowManager。
WindowManager.LayoutParams中的flags和type这两个参数比较重要,用来配置Window的属性和类型,并且通过不同的选项可以控制Window的显示特性。
flags参数(表示Window的属性)
FLAG_NOT_FOCUSABLE
表示Window不需要获取焦点,也不需要接收各种输入事件,此标记同时会启动FLAG_NOT_TOUCH_MODE,最终事件会直接传递给下层的具有焦点的Window。
FLAG_NOT_TOUCH_MODE
在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单机事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法收到单机事件。
FLAG_SHOW_WHEN_LOCKED
开启此模式可以让Window显示在锁屏的界面上。
type参数(表示Window的类型)
- 应用Window
- 子Window
- 系统Window
WindowManager所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager。
Window的添加过程
Window的添加过程需要通过WindowManager的addView来实现,WindowManager是一个接口,它是真正实现是WindowManagerImpl类。在WindowManagerImpl中Window的三大操作的实现如下:
12345678910111213141516public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);}public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.updateViewLayout(view, params);}public void removeView(View view) {mGlobal.removeView(view, false);}可以发现,WindowManagerImpl并没有直接实现Window的三大操作,而是全部交给了WindowManagerGlobal来处理,WindowManagerGlobal以工厂的形式向外提供了自己的实例。接下来开始分析WindowManagerGlobal对三大操作的处理。
WindowManagerGlobal的addView方法:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {// 1.检查参数是否合法,如果是子Window那么还需要调整一些布局参数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 {final Context context = view.getContext();if (context != null && (context.getApplicationInfo().flags& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;}}// 2.创建ViewRootImpl并将View添加到列表中ViewRootImpl root;View panelParentView = null;synchronized (mLock) {if (mSystemPropertyUpdater == null) {mSystemPropertyUpdater = new Runnable() {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)) {mRoots.get(index).doDie();} else {throw new IllegalStateException("View " + view+ " has already been added to the window manager.");}}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); // mView存储的是所有Window对应的ViewmRoots.add(root); // mRoots存储的是所有Window所对应的ViewRootImplmParams.add(wparams); // mParams存储的是所有Window所对应的布局参数}try {// 3.通过ViewRootImpl来更新界面并完成Window的添加过程root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {synchronized (mLock) {final int index = findViewLocked(view, false);if (index >= 0) {removeViewLocked(index, true);}}throw e;}}ViewRootImpl的setView()方法如下,主要通过requestLayout()来完成异步刷新请求。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {mView = view;mAttachInfo.mDisplayState = mDisplay.getState();mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);mViewLayoutDirectionInitial = mView.getRawLayoutDirection();mFallbackEventHandler.setView(view);mWindowAttributes.copyFrom(attrs);if (mWindowAttributes.packageName == null) {mWindowAttributes.packageName = mBasePackageName;}attrs = mWindowAttributes;setTag();...// 强制调整布局requestLayout();if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {mInputChannel = new InputChannel();}mForceDecorViewVisibility = (mWindowAttributes.privateFlags& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;try {mOrigWindowType = mWindowAttributes.type;mAttachInfo.mRecomputeGlobalAttributes = true;collectViewAttributes();// 通过WindowSession最终来完成Window的添加过程res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, 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);} finally {if (restore) {attrs.restore();}}...}}接下来看一下requestLayout()方法的实现。
12345678910public void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {// 检查是否在UI线程checkThread();mLayoutRequested = true;// View绘制的入口scheduleTraversals();}}再来分析一下WindowSession,WindowSession的类型是IWindowSession,是一个Binder对象,真正的实现类是Session,也就是Window的添加过程是一次IPC调用。在Session内部会通过WindowManagerService来实现Window的添加。
1234567public 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。具体Window在WindowManagerService内部的实现细节此处不做进一步分析。
Window的删除过程
Window的删除过程和添加过程一样,都是先通过WindowManagerImpl后,再进一步通过WindowManagerGlobal来实现的。
12345678910111213141516171819public void removeView(View view, boolean immediate) {if (view == null) {throw new IllegalArgumentException("view must not be null");}synchronized (mLock) {// 首先通过findViewLocked()来查找待删除的View的索引,这个查找过程就是建立的数组遍历int index = findViewLocked(view, true);View curView = mRoots.get(index).getView();// 然后调用removeViewLocked()来做进一步删除removeViewLocked(index, immediate);if (curView == view) {return;}throw new IllegalStateException("Calling with view " + view+ " but the ViewAncestor is attached to " + curView);}}removeViewLocked()是通过ViewRootImpl来完成删除操作的。
1234567891011121314151617181920private 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());}}// 具体的删除操作是由ViewRootImpl的die()方法来完成boolean deferred = root.die(immediate);if (view != null) {view.assignParent(null);if (deferred) {// 异步删除情况下,die()方法只是发送了一个请求删除的消息后就立刻返回了,这个时候View并没有完成删除操作,所以最后会将其添加到mDyingViews中,mDyingViews表示待删除的View列表mDyingViews.add(view);}}}接下来看看ViewRootImpl的die()方法。
123456789101112131415boolean die(boolean immediate) {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()方法。
123456789101112131415161718192021222324252627282930313233343536void doDie() {checkThread();if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);synchronized (this) {if (mRemoved) {return;}mRemoved = true;if (mAdded) {dispatchDetachedFromWindow();}if (mAdded && !mFirst) {destroyHardwareRenderer();if (mView != null) {int viewVisibility = mView.getVisibility();boolean viewVisibilityChanged = mViewVisibility != viewVisibility;if (mWindowAttributesChanged || viewVisibilityChanged) {try {if ((relayoutWindow(mWindowAttributes, viewVisibility, false)& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {mWindowSession.finishDrawing(mWindow);}} catch (RemoteException e) {}}mSurface.release();}}mAdded = false;}WindowManagerGlobal.getInstance().doRemoveView(this);}在doDie()方法内部会调用dispatchDetachedFromWindow()方法,真正删除View的逻辑在dispatchDetachedFromWindow()方法内部实现。dispatchDetachedFromWindow()方法主要做四件事:
①垃圾回收相关的工作,比如清除数据和消息、移除回调。
②通过Session的remove方法删除Window,mWindowSession.remove(mWindow),这是一个IPC操作,最终会调用WindowManagerService的removeWindow()方法。
③调用View的dispatchDetachedFromWindow方法,在内部会调用View的onDetachedFromWindow()方法和onDetachedFromWindowInternal()方法。
④调用WindowManagerGlobal的doRemoveView()方法刷新数据,包括mRoots、mParams以及mDyingViews,需要将当前Window所关联的这三类对象从列表中删除。
Window的更新过程
分析Window的更新过程要看WindowManagerGlobal的updateViewLayout()方法。
1234567891011121314151617181920public 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);}}updateViewLayout()方法首先更新View的LayoutParams并替换掉老的LayoutParams,接着再更新ViewRootImpl中的LayoutParams,这一步是通过ViewRootImpl的setLayoutParams()方法来实现的。在ViewRootImpl中会通过scheduleTraversals()方法来对View重新布局,包括测量、布局、绘制这三个过程。除了View本身的重绘,ViewRootImpl还会通过WindowSession来更新Window的视图,这个过程最终是由WindowManagerService的relayoutWindow()来具体实现的,它是一个IPC过程。
Window的创建过程
- View是android中的视图的呈现方式,但是View不能单独存在,它必须附着在Window这个抽象的概念上面,因此有视图的地方就有Window。
- android中可以提供视图的地方有Activity、Dialog、Toast,除此之外,还有一些依托于Window而实现的视图,比如PopUpWindow、菜单,它们也是视图。
接下来就开始分析这些视图元素中Window的创建过程
Activity的Window创建过程
每个应用类窗口都对应一个Activity对象,因此,创建应用类窗口就需要创建一个Activity对象。当AMS决定启动某个Activity的时候,就会通知客户端程序,而每个客户端程序都对应一个ActivityThread类,任何Activity都必须隶属一个应用程序,因此,启动Activity的任务最终由ActivityThread完成。Activity的启动过程很是复杂,最终会有ActivityThread中的performLaunchActivity()来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用其attach()方法为其关联运行过程中所依赖的一系列上下文环境。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556final 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) {attachBaseContext(context);mFragments.attachHost(null /*parent*/);mWindow = new PhoneWindow(this, window);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();mMainThread = aThread;mInstrumentation = instr;mToken = token;mIdent = ident;mApplication = application;mIntent = intent;mReferrer = referrer;mComponent = intent.getComponent();mActivityInfo = info;mTitle = title;mParent = parent;mEmbeddedID = id;mLastNonConfigurationInstances = lastNonConfigurationInstances;if (voiceInteractor != null) {if (lastNonConfigurationInstances != null) {mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;} else {mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,Looper.myLooper());}}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;}在Activity的attach()方法里,系统会创建Activity所属的Window对象并为其设置回调接口,Window对象的创建是通过PhoneWindow的构造方法来实现的。由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的方法。Callback接口中的方法很多,比如onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent等。到这里Window的创建过程完成了,下面分析Activity的视图是怎样附属在Window上的。
由于Activity的视图由setContentView()方法提供,下面来看一下这个方法的实现。
1234public void setContentView(@LayoutRes int layoutResID) {getWindow().setContentView(layoutResID);initWindowDecorActionBar();}Activity将具体实现交给了Window处理,而Window的具体实现是PhoneWindow,所以需要看一下PhoneWindow的相关逻辑。
12345678910111213141516171819202122public void setContentView(int layoutResID) {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);} else {mLayoutInflater.inflate(layoutResID, mContentParent);}mContentParent.requestApplyInsets();final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}mContentParentExplicitlySet = true;}PhoneWindow的setContentView()方法大致遵循如下几个步骤:
① 如果没有DecorView,那么就创建它
DecorView是一个FrameLayout,是Activity中的顶级View,一般来说它的内部包含标题栏和内容栏,但是会随着主题的变换而发生改变。不管怎么样,内容栏是一定存在的,并且内容栏具体固定的id,就是“content”,完整id是android.R.id.content。DecorView的创建过程由installDecor()方法来完成。
12345678910111213141516private void installDecor() {mForceDecorInstall = false;if (mDecor == null) {mDecor = generateDecor(-1);mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);mDecor.setIsRootNamespace(true);if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);}} else {mDecor.setWindow(this);}if (mContentParent == null) {mContentParent = generateLayout(mDecor);...}installDecor()方法内部会通过generateDecor()方法来直接创建DecorView,这个时候DecorView还只是一个空白的FrameLayout。为了初始化DecorView的结构,PhoneWindow还需要通过generateLayout()方法来加载具体的布局文件到DecorView中,具体的布局文件与系统版本与主题有关。
② 将View添加到DecorView的mContentParent中
这个过程就比较简单了,由于在步骤1中已经创建并初始化了DecorView,因此这一步直接将Activity的视图添加到DecorView的mContentParent中即可:mLayoutInflater.inflate(layoutResID, mContentParent)。至此,Activity的布局文件已经添加到DecorView里面了。
③ 回调Activity的onContentChanged方法通知Activity视图已经发生了改变
由于Activity实现了Window的Callback接口,这里表示Activity的布局文件已经被添加到DecorView的mContentParent中了,于是需要通知Activity,使其做相应的处理。Activity的onContentChanged方法是一个空实现,我们可以在子Activity中处理这个回调。
经过上面的三个步骤,到这里为止DecorView已经被创建并初始化完毕,Activity的布局文件也已经成功添加到了DecorView的mContentParnet中,但是这个时候DecorView还没有被WindowManager正式添加到Window中。这里需要正确理解Window的概念,Window更多表示的是一种抽象的功能集合,虽说早在Activity的attach()方法中Window就已经被创建了,但这个时候由于DecorView并没有被WindowManager识别,所以这个时候Window无法提供具体功能,因为它还无法接收外界的输入信息。在ActivityThread的handleResumeActivity()方法中,首先会调用Activity的onResume()方法,接着会调用Activity的makeVisible()方法,正是在makeVisible()方法中,真正地完成了添加和显示这两个过程,到这个Activity的视图才会被用户看到。
12345678void makeVisible() {if (!mWindowAdded) {ViewManager wm = getWindowManager();wm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);}到这里,Activity中的Window的创建过程就已经分析完毕了。
Dialog的Window创建过程
Dialog的Window创建过程与Activity类似。
①创建Window
Dialog中Window的创建同样是通过PhoneWindow的构造函数来完成的。
1234567891011121314151617181920212223Dialog(int themeResId, boolean createContextThemeWrapper) {Context context,if (createContextThemeWrapper) {if (themeResId == 0) {final TypedValue outValue = new TypedValue();context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);themeResId = outValue.resourceId;}mContext = new ContextThemeWrapper(context, themeResId);} else {mContext = context;}mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);final Window w = new PhoneWindow(mContext);mWindow = w;w.setCallback(this);w.setOnWindowDismissedCallback(this);w.setWindowManager(mWindowManager, null, null);w.setGravity(Gravity.CENTER);mListenersHandler = new ListenersHandler(this);}这个过程和Activity的Window创建过程是一致的。
②初始化DecorView并将Dialog的视图添加到DecorView中
这个过程和Activity的类似,都是通过Window去添加指定的布局文件。
123public void setContentView(@LayoutRes int layoutResID) {mWindow.setContentView(layoutResID);}③将DecorView添加到Window中并显示
在Dialog的show()方法中,会通过WindowManager将DecorView添加到Window中。
123456789101112131415161718192021222324252627282930313233343536373839404142434445public void show() {if (mShowing) {if (mDecor != null) {if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);}mDecor.setVisibility(View.VISIBLE);}return;}mCanceled = false;if (!mCreated) {dispatchOnCreate(null);} else {final Configuration config = mContext.getResources().getConfiguration();mWindow.getDecorView().dispatchConfigurationChanged(config);}onStart();mDecor = mWindow.getDecorView();if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {final ApplicationInfo info = mContext.getApplicationInfo();mWindow.setDefaultIcon(info.icon);mWindow.setDefaultLogo(info.logo);mActionBar = new WindowDecorActionBar(this);}WindowManager.LayoutParams l = mWindow.getAttributes();if ((l.softInputMode& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {WindowManager.LayoutParams nl = new WindowManager.LayoutParams();nl.copyFrom(l);nl.softInputMode |=WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;l = nl;}mWindowManager.addView(mDecor, l);mShowing = true;sendShowMessage();}当Dialog被关闭后,会通过WindowManager来移除DecorView。
123456789101112131415161718192021222324void dismissDialog() {if (mDecor == null || !mShowing) {return;}if (mWindow.isDestroyed()) {Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");return;}try {mWindowManager.removeViewImmediate(mDecor);} finally {if (mActionMode != null) {mActionMode.finish();}mDecor = null;mWindow.closeAllPanels();onStop();mShowing = false;sendDismissMessage();}}Toast的Window创建过程
Toast和Dialog不同,它的工作过程稍显复杂。首先Toast也是基于Window来实现的,但是由于Toast具有定时取消这一功能,所以系统采用了Handler。在Toast内部有两类IPC过程,第一类是Toast访问NotificationManagerService,第二类是NotificationManagerService回调Toast里的TN接口。
Toast属于系统Window,它内部的试图由两种方式指定,一种是系统默认的方式,另一种是通过setView方法来指定一个自定义View,不管如何,它们都对应Toast的一个View类型的内部成员mNextView。Toast提供了show和cancle分别用于显示和隐藏Toast,它的内部是一个IPC过程。
12345678910111213141516public 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}}123456789public void cancel() {mTN.hide();try {getService().cancelToast(mContext.getPackageName(), mTN);} catch (RemoteException e) {// Empty}}从Toast的show()方法和cancel()方法可以看到,显示和隐藏Toast都需要通过NMS来实现,由于NMS运行在系统的进程中,所以只能通过远程调用的方式来显示和隐藏Toast。需要注意的是TN这个类,它是一个Binder类,在Toast和NMS进行IPC的进程中,当NMS处理Toast的显示或隐藏请求时会跨进程回调TN中的方法,这个时候由于TN运行在Binder线程池中,所以需要通过Handler将其切换到当前线程中。这里的当前线程是指发送Toast请求所在的线程。由于这里使用了Handler,所以意味着Toast无法在没有Looper的线程中弹出,这是因为Handler需要使用Looper才能完成切换线程的功能。
首先看Toast的显示过程,它调用了NMS的enqueueToast()方法。NMS的enqueueToast()方法的第一个参数表示当前应用的包名,第二个参数tn表示远程回调,第三个参数表示Toast的时长。enqueueToast()首先将Toast请求封装成ToastRecord对象并将其添加到一个名为mToastQueue的队列中,mToastQueue其实是一个ArrayList。对于非系统应用来说,mToastQueue中最多能同时存在50个ToastRecord,这样做是为了防止DOS(Denial of Service)。正常情况下,一个应用不可能达到上限,当ToastRecord被添加到mToastRecord中后,NMS就会通过showNextToastLocked方法来显示当前的Toast。Toast的显示是由ToastRecord的callback来完成的,这个callback实际上就是Toast中的TN对象的远程Binder,通过callback来访问TN中的方法是需要跨进程来完成的,最终被调用的TN中的方法会运行在发起Toast请求的应用的Binder线程池中。Toast显示之后,NMS还会通过scheduleTimeoutLocked方法来发送一个延迟消息,具体的延时取决于Toast的时长。延迟相应的时间后,NMS会通过cancleToastLocked方法来隐藏Toast并将其从mToastQueue中移除,这个时候如果mToastQueue中还有其他Toast,那么NMS就继续显示其他Toast。Toast的隐藏也是通过ToastRecord的callback来完成的,这也是一次IPC过程。
通过上面的分析可知,Toast的显示和隐藏过程实际上是通过Toast中的TN这个类来实现的,它有两个方法show()和hide(),分别对应Toast的显示和隐藏。由于这两个方法是被NMS以跨进程的方式调用的,因此它们运行在Binder线程池中。为了将执行环境切换到Toast请求所在的线程,在它们的内部使用了Handler。