一些說明:ref
Window和View很多時候會彼此混雜,概念模糊不清。比如windowManager.addView實際上將View添加到WM對應的Window當中,並不是添加了一個新的Window,但是爲其指定的LayoutParams的type含義卻是Window的類型。
Window的存在意義,實際上是將View的創建、ViewRootImpl和View交互的操作,從Activity/Dialog等實體中剝離出來,減少耦合和複雜度。
Window中可以有多個View,如Activity中通過WindowManager添加View,實際就是添加到了Activity的PhoneWindow中。所以當說到Window的添加/刪除/更新,一般說的即是VIew,因爲Window的實例一般不能直接操作,當調用getWindowManager的時候,其實已經有一個PhoneWindow存在了。
源碼中對ViewRootImpl的解釋:
The top of a view hierarchy, implementing the needed protocol between View and the WindowManager. This is for the most part an internal implementation detail of WindowManagerGlobal.
本章很多時候在說的Window實際上就是View。
1.Window和WindowManager
向WindowManager中添加View:
WindowManager windowManager = getWindowManager();
Button button = new Button(this);
// public LayoutParams(int w, int h, int _type, int _flags, int _format)
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
0, 0, PixelFormat.TRANSPARENT);
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
layoutParams.x = 100;
layoutParams.y = 300;
windowManager.addView(button, layoutParams);
- FLAG_NOT_FOCUSABLE
表示Window不接收key event和button event,事件會傳遞到其下的focusable window。該flag會同時使能FLAG_NOT_TOUCH_MODAL。 - FLAG_NOT_TOUCH_MODAL
表示即使Window是focusable的,該window之外的事件可以傳遞給其下的window。同非模態對話框的意義。 FLAG_SHOW_WHEN_LOCKED
讓Window可以在鎖屏上顯示。deprecated in API level 27。- Window的type
- application window。對應着Activity。
- sub window。不能單獨存在,需要附屬於特定的父Window。對應Dialog等。
- system window。需要聲明權限才能創建的Window。對應Toast,status bar等。
- Window分層,通過z-ordered標識,層級大的覆蓋層級小的。application window層級1~99,sub window層級1000~1999,system window層級2000~2999。 這些層級範圍對應着WindowManager.LayoutParams中定義的各個TYPE值。
- system window type如
TYPE_SYSTEM_OVERLAY/TYPE_SYSTEM_ERROR(都已deprecated,非系統app使用TYPE_APPLICATION_OVERLAY),使用時要聲明相應的權限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
- 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);
}
2.Window的創建/更新/刪除
- Window的添加過程
WindowManager實際爲一個Interface,繼承自ViewManager,真正的實體類爲WindowManagerImpl。而其中具體的add/update/remove操作,均是通過WindowManagerGlobal來執行的(橋接模式):
在WindowManagerGlobal的addView中,首先檢查了參數的合法性。然後針對window的不同類型做一些操作:如果是sub window則需要調整一些佈局參數;不是sub window則依據是否開了硬件加速,設置相應的使能flag:private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); @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); }
然後創建ViewRootImpl,並將相關的數據存儲到WindowManagerGlobal內部維護的幾個list中。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, then hardware acceleration for this view is // set from the application's hardware acceleration setting. final Context context = view.getContext(); if (context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } }
root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams);
ViewRootImpl創建完成,調用其setView方法,將View綁定到ViewRootImpl。// window's View private final ArrayList<View> mViews = new ArrayList<View>(); // window' ViewRootImpl private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); // window's LayoutParams private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>(); // Views being removed(call removeView but the action is not finished) private final ArraySet<View> mDyingViews = new ArraySet<View>();
由以上註釋可以看出,ViewRootImpl#setView中真正開始了一系列的動作。// do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView);
首先是調用了自己的requestLayout方法,執行異步渲染:
其中scheduleTraversals方法實際是,將performTraversals方法包裹爲Runnable添加到了Choreographer的刷新隊列中,當performTraversals被回調,則開始View的measure->layout->draw過程:@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
requestLayout執行完畢,開始添加Window。mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
添加Window的工作由mWindowSession來執行,而其實際爲創建ViewRootImpl時,通過WindowManagerGlobal從遠程WindowManagerService得到的遠程Session實例(Binder對象)。所以添加Window實際爲一次IPC調用:res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mWinFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
Session中的addToDisplay方法,執行真正的add操作。:public ViewRootImpl(Context context, Display display) { ... mWindowSession = WindowManagerGlobal.getWindowSession();
具體操作由WindowManagerService執行。其內部爲每一個應用維護了一個Session。@Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel); }
- Window的刪除過程
同添加Window的過程,刪除Window由WindowManagerGlobal的removeWindow執行。首先通過findViewLocked方法,得到要刪除View的index,不存在則直接拋出異常(所以remove和add操作的View對象要一致),這裏的index就是在添加window時,View添加到WindowManagerGlobal內部維護的list中的index:
之後調用removeViewLocked方法,執行View對應的ViewRootImpl中的die方法。這裏傳入的immediate參數,指明瞭需要異步刪除還是同步刪除。同步刪除,則直接執行doDie方法;異步刪除,則將doDie添加到消息隊列中異步執行。同步和異步操作區別,體現在WindowManagerImpl提供的兩個remove方法:removeView方法對應異步刪除,removeViewImmediate方法對應同步刪除: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 int findViewLocked(View view, boolean required) { final int index = mViews.indexOf(view); if (required && index < 0) { throw new IllegalArgumentException("View=" + view + " not attached to window manager"); } return index; }
doDie方法中真正執行了一系列與刪除相關的操作: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; }
- 清除數據、消息,移除回調
- 通過Session這個遠程Binder,執行WindowManagerService的removeWindow方法
- 調用View的dispatchDetachedFromWindow方法,從而執行onDetachedFromWindow和onDetachedFromWindowInternal回調。當View從Window移除時,可以從這方法中做一些資源回收工作,如終止動畫、停止線程。
- 執行WindowManagerGlobal的doRemoveView方法,將mRoots/mParams/mDyingViews中的與該View對應的item移除。
- Window的更新過程
更新操作非常簡單,首先更新了View和mParams中的LayoutParams,然後調用ViewRootImpl的setLayoutParams方法,更新ViewRootImpl的LayoutParams,同時調用scheduleTraversals異步執行View的measure->layout->draw過程:
除了View的繪製,ViewRootImpl會通過Session來遠程更新Window的視圖: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); } }
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException { ... int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params, (int) (mView.getMeasuredWidth() * appScale + 0.5f), (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout, mPendingMergedConfiguration, mSurface);
3.不同場景中Window的創建過程
- Activity的Window
在Activity的attach方法中,會創建Activity的Window,並綁定回調接口。回調接口中包括的回調方法,就有onAttactedToWindow/onDetachedFromWindow/dispatchTouchEvent。當Window收到狀態改變時,就會回調對應的在Activity中定義的回調方法:
同時得到了一個本地WindowManager對象,即是WindowManagerImpl對象。每個Activity都擁有自己的WindowManagerImpl對象,通過該對象與遠程的WMS進行通信。而創建時使用的token爲Binder,通過該對象WMS可以管理Activity的View。mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this);
mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
Window創建完畢,然後是Activity View的創建過程,即setContentView方法:// Window#setWindowManager public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); }
之後的工作,便交由其Window即PhoneWindow的setContentView方法去執行。Activity的root view是DecorView,而setContentView所設置的即是其中的content部分,而這裏的mContentParent就是content部分的ViewGroup。public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
首先,如果content不存在,即DecorView不存在,則調用installDecor方法創建DecorView。該方法中通過generateDecor方法創建一個空白的DecorView實例,然後通過generateLayout爲其加載佈局,初始化其結構:@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. 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; }
具體的佈局文件由系統版本和主題決定。確定了需要加載的佈局之後,爲DecorView設置佈局(onResourcesLoaded方法中inflate了resource指定的佈局,然後添加至DecorView中),然後便可得到DecorView中content部分的parent(ID_ANDROID_CONTENT即content的ID):private 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);
content parent得到的初始化,此時便可其中添加content view了。最後PhoneWindow會回調onContentChanged方法,來通知Activity content view已經創建並添加到DecorView中了:public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; protected ViewGroup generateLayout(DecorView decor) { ... mDecor.startChanging(); mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); }
此時,PhoneWindow#setContentView執行完畢,但完成的工作僅僅是初始化了DecorView及其中的content view。Window更多表示的是一種抽象的功能集合,未添加到WindowManager中的Window無法提供任何功能,因爲其無法接收外界的輸入信息。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(); }
具體的add window操作,是在ActivityThread#handleResumeActivity中,首先調用Activity#onResume方法,然後調用Activity#makeVisible方法,在makeVisible中,DecorView真正添加到WindowManager中,並最終顯示出來:void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); }
- Dialog的Window
Dialog中Window的創建過程,與Activity非常相似。
在Dialog的構造方法中,創建了PhoneWindow,並綁定了回調接口:
setContentView的具體操作,也是由其Window去完成:mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); final Window w = new PhoneWindow(mContext); mWindow = w; w.setCallback(this); w.setOnWindowDismissedCallback(this); w.setOnWindowSwipeDismissedCallback(() -> { if (mCancelable) { cancel(); } }); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER);
最後,在Dialog的show方法中將DecorView添加到WindowManager:public void setContentView(@LayoutRes int layoutResID) { mWindow.setContentView(layoutResID); }
當Dialog被關閉,會通過WindowManager#removeViewImmediate來移除DecorView:mWindowManager.addView(mDecor, l);
另外,創建Dialog時指定的Context根據Window的類型有不一樣的限制:當Window爲sub window,Context必須爲Activity,否則將拋出沒有token的異常,因爲token一般爲Activity所有;當Widnow爲system window,對Context就沒有這個限制,因爲system window可以不需要token。void 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);
- Toast的Window
Toast屬於系統Window。他內部View的指定方式有兩種,一種是系統默認樣式,一種是通過setView設置自定義View,而這個內部View對應了Toast中的mNextView成員。
Toast內部有雙向IPC過程:Toast訪問NotificationManagerService(NMS),以及NMS回調Toast的TN接口。
Toast通過show方法顯示Toast。其中主要調用了遠程的NMS的enqueueToast方法,同時將自己的TN接口傳遞給遠程service:
在NMS中,根據包名爲不同App的Toast維護了一個ToastRecord的list。enqueueToast方法中,首先會根據包名和callback查找其對應的ToastRecord對象是否存在,如果不存在,則將Toast封裝爲ToastRecord對象,存入list;如果存在則直接更新對應的Toast,主要更新的duration和callback。ToastRecord創建或者更新完畢,此時就要顯示Toast了。此時如果index爲0,說明之前沒有正在顯示的Toast,則直接調用showNextToastLocked方法,顯示Toast。: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 } }
舊的Android版本中的實現,是對每個非系統應用的Toast數量做了不多於50個的限制。在獲取ToastRecord的index的時候,都是通過indexOfToastLocked方法校驗了傳入的包名和TN Binder對象(驗證了是否爲是同一個對象,每創建一個Toast就對應一個TN)。而新的實現,針對非系統應用,添加了一個indexOfToastPackageLocked方法,其中只校驗了包名,所以最終非系統應用的ToastRecord實例只存在一個。所以,舊版本中當連續show新的Toast導致Toast一直顯示的問題,新版本中不會存在,因爲此時每個App只有一個Toast實例,只會更新其View。@Override public void enqueueToast(String pkg, ITransientNotification callback, int duration) { ... try { ToastRecord record; int index; // All packages aside from the android package can enqueue one toast at a time if (!isSystemToast) { index = indexOfToastPackageLocked(pkg); } else { index = indexOfToastLocked(pkg, callback); } // If the package already has a toast, we update its toast // in the queue, we don't move it to the end of the queue. if (index >= 0) { record = mToastQueue.get(index); record.update(duration); try { record.callback.hide(); } catch (RemoteException e) { } record.update(callback); } else { Binder token = new Binder(); mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY); record = new ToastRecord(callingPid, pkg, callback, duration, token); mToastQueue.add(record); index = mToastQueue.size() - 1; } keepProcessAliveIfNeededLocked(callingPid); // If it's at index 0, it's the current toast. It doesn't matter if it's // new or just been updated. Call back and tell it to show itself. // If the callback fails, this will remove it from the list, so don't // assume that it's valid after this. if (index == 0) { showNextToastLocked(); } }
showNextToastLocked方法中,則直接調用了ToastRecord的callback的show方法,而這個callback就是TN的遠程Binder對象。show因爲是RPC調用,所以其實際運行在Binder線程池中,爲了使其能正常顯示出來,使用了Handler進行異步顯示,所以Toast只能應用於有Looper的線程中:@GuardedBy("mToastQueue") int indexOfToastLocked(String pkg, ITransientNotification callback) { IBinder cbak = callback.asBinder(); ArrayList<ToastRecord> list = mToastQueue; int len = list.size(); for (int i=0; i<len; i++) { ToastRecord r = list.get(i); if (r.pkg.equals(pkg) && r.callback.asBinder().equals(cbak)) { return i; } } return -1; } @GuardedBy("mToastQueue") int indexOfToastPackageLocked(String pkg) { ArrayList<ToastRecord> list = mToastQueue; int len = list.size(); for (int i=0; i<len; i++) { ToastRecord r = list.get(i); if (r.pkg.equals(pkg)) { return i; } } return -1; }
@GuardedBy("mToastQueue") void showNextToastLocked() { ToastRecord record = mToastQueue.get(0); while (record != null) { if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback); try { record.callback.show(record.token); scheduleDurationReachedLocked(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); } keepProcessAliveIfNeededLocked(record.pid); if (mToastQueue.size() > 0) { record = mToastQueue.get(0); } else { record = null; } } } }
Handler異步調用handleShow方法,將View添加至Window,最終將Toast顯示出來:// Toast.java$TN /** * schedule handleShow into the right thread */ @Override public void show(IBinder windowToken) { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.obtainMessage(SHOW, windowToken).sendToTarget(); }
show執行完畢,調用scheduleDurationReachedLocked方法,在一段延時之後,cancel當前的Toast。如果mToastQueue中還有其他的Toast,則繼續調用showNextToastLocked顯示下一個:mWM.addView(mView, mParams);
當隱藏Toast時,類似顯示的過程。Toast#cancel方法會直接調用TN#cancel,來隱藏Toast,具體的隱藏過程也是通過Handler異步進行的@GuardedBy("mToastQueue") private void scheduleDurationReachedLocked(ToastRecord r) { mHandler.removeCallbacksAndMessages(r); Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r); long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; mHandler.sendMessageDelayed(m, delay); } private void handleDurationReached(ToastRecord record) { if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback); synchronized (mToastQueue) { int index = indexOfToastLocked(record.pkg, record.callback); if (index >= 0) { cancelToastLocked(index); } } } @GuardedBy("mToastQueue") void cancelToastLocked(int index) { ... if (mToastQueue.size() > 0) { // Show the next one. If the callback fails, this will remove // it from the list, so don't assume that the list hasn't changed // after this point. showNextToastLocked(); } }
// Toast#cancel public void cancel() { mTN.cancel(); }
Handler異步調用handleHide方法,其中便通過WindowManager#removeViewImmediate方法移除了VIew。Toast隱藏之後,再向NMS通過cancelToast方法發起RPC將Toast相關的數據清除:// TN#cancel public void cancel() { if (localLOGV) Log.v(TAG, "CANCEL: " + this); mHandler.obtainMessage(CANCEL).sendToTarget(); }
case CANCEL: { handleHide(); // Don't do this in handleHide() because it is also invoked by // handleShow() mNextView = null; try { getService().cancelToast(mPackageName, TN.this); } catch (RemoteException e) { } break;
public void handleHide() { if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); if (mView != null) { // note: checking parent() just to make sure the view has // been added... i have seen cases where we get here when // the view isn't yet added, so let's try not to crash. if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeViewImmediate(mView); } // Now that we've removed the view it's safe for the server to release // the resources. try { getService().finishToken(mPackageName, this); } catch (RemoteException e) { } mView = null; } }