一取董、概論
????通過上一篇文章(Window & WindowManager理解)中已經(jīng)知道了View
不能單獨存在经窖,必須依附在 Window
上面配乱,因此有視圖的地方就有 Window
忿檩。這些視圖包括 :Activity
班套,Dialog
理盆,Toast
坎拐,PopupWindow
等等积担。我們通過這篇文章深入理解這幾個東西是如何添加window
的
二的烁、Activity 添加 Window 流程分析
2.1 創(chuàng)建對應(yīng)Window & 回調(diào)
????在Activity
中的attach()
方法中,系統(tǒng)會創(chuàng)建 Activity
所屬的 Window
,并未其設(shè)置回調(diào)接口。由于 Activity
實現(xiàn)了 Window
的 Callback
接口,因此當(dāng) Window
接受到外接的狀態(tài)改變時就會回調(diào) Activity
中的方法击罪。
##Activity
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,
IBinder shareableActivityToken) {
//創(chuàng)建 PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
//設(shè)置 window 的回調(diào)
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
這個Callback
中的方法有很多,但是有些我們是非常熟悉的娃弓,例如
dispatchTouchEvent(MotionEvent event)
-
onAttachedToWindow()
等等挽霉。
2.2 創(chuàng)建Activity
Activity 的創(chuàng)建過程
比較復(fù)雜,最終會通過 ActivityThread
中的 performLaunchActivity()
來完成整個啟動過程,在這個方法中會通過類加載器創(chuàng)建 Activity 的實例對象
闪唆,并調(diào)用其 attach 方法
為其關(guān)聯(lián)所需的環(huán)境變量(看以下源碼):
##ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//.....
if (activity != null) {
appContext.setOuterContext(activity);
// 為activity對象綁定window所需環(huán)境變量
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
//....
}
return activity;
}
2.3 初始化Activity所屬DecorView
????由于Activity
的視圖是通過 setContentView
方法提供的,我們直接看 setContentView
即可:
##Activity
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
看代碼知道,調(diào)用到了getWindow
的setContentView
方法丰歌,而在 Android中Window
的實現(xiàn)是 PhoneWindow
灌旧。因此我們看到 PhoneWindow
的 setContentView
方法毛甲。
##PhoneWindow
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 1叮叹,創(chuàng)建 DecorView
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 {
//2 添加 activity 的布局文件
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
// 3.通知Activity onContentChanged 霉翔。
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
在上面代碼中谚中,如果沒有 DecorView
就創(chuàng)建它冠跷,一般來說它內(nèi)部包含了標(biāo)題欄
和內(nèi)容欄
仪糖,但是這個會隨著主題的改變而發(fā)生改變。但是不管怎么樣充蓝,內(nèi)容欄是一定存在的
孕暇,并且內(nèi)容欄有固定的 id content
簸州,完整的 id 是 android.R.id.content
读虏。
- 注釋1:通過
generateDecor
創(chuàng)建了DecorView
塑荒,接著會調(diào)用generateLayout
來加載具體的布局文件到DecorView
中他托,這個要加載的布局就和系統(tǒng)版本以及定義的主題有關(guān)了韧掩。加載完之后就會將內(nèi)容區(qū)域的View
返回出來,也就是mContentParent
如下源碼:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// DecorView 為null 就創(chuàng)建
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);
//.....
}
protected DecorView generateDecor(int featureId) {
//.....
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
//.......
return contentParent;
}
緊接著上面的PhoneWindow
的setContentView
- 注釋2:將
activity
需要顯示的布局添加到mContentParent
中春畔。 - 注釋3:由于
activity
實現(xiàn)了window
的callback
接口凤价,這里表示activity
的布局文件已經(jīng)被添加到decorView
的mParentView
中了,于是通知Activity
的onContentChanged
接口。
經(jīng)過上面三個步驟,DecorView
已經(jīng)初始完成蒂秘,Activity
的布局文件以及加載到了DecorView
的 mParentView
中了,但是這個時候DecorView
還沒有被 WindowManager
正式添加到Window
中。
2.4 添加DecorView到Window
在 ActivityThread
的 handleResumeActivity
中,會調(diào)用 activity
的 onResume
方法,接著就會將 DecorView
添加到 Window
中
##ActivityThread
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
//.....
//調(diào)用 activity 的 onResume 方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//DecorView 完成了添加和顯示的過程
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
//..........
}
看到上面的 wm.addView(decor, l);
就到了上一篇文章的windowManager
的 addView
流程了。
三笑窜、Dialog 添加 Window 流程分析
3.1 創(chuàng)建Window
Dialog
中創(chuàng)建Window
是在其構(gòu)造方法中完成垦垂,具體如下:
Dialog(@UiContext @NonNull Context context, @StyleRes int themeResId,
boolean createContextThemeWrapper) {
//...
//獲取 WindowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//創(chuàng)建 Window
final Window w = new PhoneWindow(mContext);
mWindow = w;
//設(shè)置 Callback
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
3.2 為DecorView添加視圖
初始化DecorView
饼暑,將 Dialog
的視圖添加到 DecorView 中
public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
}
這個和 activity 的類似,都是通過 Window 去添加指定的布局文件
3.3 添加DecorView到Window 顯示
public void show() {
//...
mDecor = mWindow.getDecorView();
mWindowManager.addView(mDecor, l);
//發(fā)送回調(diào)消息
sendShowMessage();
}
private static final class ListenersHandler extends Handler {
private final WeakReference<DialogInterface> mDialog;
public ListenersHandler(Dialog dialog) {
mDialog = new WeakReference<>(dialog);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DISMISS:
((OnDismissListener) msg.obj).onDismiss(mDialog.get());
break;
case CANCEL:
((OnCancelListener) msg.obj).onCancel(mDialog.get());
break;
case SHOW:
((OnShowListener) msg.obj).onShow(mDialog.get());
break;
}
}
}
從上面三個步驟可以發(fā)現(xiàn)抬闯,Dialog
的 Window
創(chuàng)建和Activity
的Window
創(chuàng)建很類似包雀,二者幾乎沒有什么區(qū)別。
當(dāng) dialog
關(guān)閉時,它會通過WindowManager
來移除DecorView
配椭, mWindowManager.removeViewImmediate(mDecor)
虫溜。具體看下面源碼:
@Override
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
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 {
// 通過 windowManager 進(jìn)行移除
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
普通的Dialog
有一個特殊的地方,就是必須采用Activity 的 Context
股缸,如果采用Application 的 Context
衡楞,就會報錯:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
錯誤信息很明確,是沒有Token
導(dǎo)致的敦姻,而Token
一般只有 Activity
擁有瘾境,所以這里只需要用 Activity
作為Context
即可歧杏。
另外,系統(tǒng) Window
比較特殊迷守,他可以不需要Token
犬绒,我們可以將Dialog 的 Window Type
修改為系統(tǒng)類型
就可以了,如下所示:
val dialog = Dialog(application)
dialog.setContentView(textView)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
dialog.window?.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY)
}else{
dialog.window?.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
}
dialog.show()
需要注意的是兑凿,彈出系統(tǒng)級別的彈框需要申請 懸浮窗權(quán)限懂更。
四、Toast 添加 Window 流程分析
3.1 概論
Toast
也是基于Window
來實現(xiàn)的急膀,但是他的工作過程有些復(fù)雜沮协。在Toast
的內(nèi)部有兩類 IPC
的過程,第一類是 Toast
訪問NotificationManagerService
過程卓嫂。第二類是NotificationManagerServer
回調(diào) Toast
里的TN
接口慷暂。下面將NotificationManagerService
簡稱為NMS
。
Toast
屬于系統(tǒng)Window
晨雳,內(nèi)部視圖有兩種定義方式行瑞,一種是系統(tǒng)默認(rèn)的
,另一種是通過 setView 方法來指定一個 View(setView 方法在 android 11 以后已經(jīng)廢棄了餐禁,不會再展示自定義視圖)
血久,他們都對應(yīng) Toast 的一個內(nèi)部成員mNextView
。
3.2 展示show()
Toast
提供了 show
和 cancel
分別用于顯示和隱藏 Toast
帮非,它們的內(nèi)部是一個IPC
的過程氧吐,實現(xiàn)如下:
public void show() {
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
final int displayId = mContext.getDisplayId();
try {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
if (mNextView != null) {
service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
} else {
// ...
}
}
}
//....
}
public void cancel() {
try {
getService().cancelToast(mContext.getOpPackageName(), mToken);
} catch (RemoteException e) {
// Empty
}
//....
}
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
return sService;
}
從上面代碼中可以看出,顯示和影藏都需要通過NMS
來實現(xiàn)末盔,由于NMS
運(yùn)行在系統(tǒng)進(jìn)程中筑舅,所以只通過能跨進(jìn)程的調(diào)用方式來顯示和隱藏Toast
。
首先看 Toast
顯示的過程陨舱,它調(diào)用了NMS
中的 enqueueToast
方法翠拣,上面的INotificationManager
只是一個AIDL
接口, 這個接口使用來和 NMS
進(jìn)行通信的游盲,實際調(diào)用到的是NMS
的enqueueToast
方法:
##NotificationManagerService
static final int MAX_PACKAGE_TOASTS = 5;
public void enqueueToast(String pkg, IBinder token, ITransientNotification callback,
int duration, int displayId) {
enqueueToast(pkg, token, null, callback, duration, displayId, null);
}
private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration, int displayId,
@Nullable ITransientNotificationCallback textCallback) {
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
final long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, token);
//如果隊列中有误墓,就更新它,而不是重新排在末尾
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else {
int count = 0;
final int N = mToastQueue.size();
for (int i = 0; i < N; i++) {
final ToastRecord r = mToastQueue.get(i);
//對于同一個應(yīng)用益缎,taost 不能超過 5 個
if (r.pkg.equals(pkg)) {
count++;
if (count >= MAX_PACKAGE_TOASTS) {
Slog.e(TAG, "Package has already queued " + count
+ " toasts. Not showing more. Package=" + pkg);
return;
}
}
}
//創(chuàng)建對應(yīng)的 ToastRecord
record = getToastRecord(callingUid, callingPid, pkg, isSystemToast, token,text, callback, duration, windowToken, displayId, textCallback);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
keepProcessAliveForToastIfNeededLocked(callingPid);
}
// ==0 表示只有一個 toast了谜慌,直接顯示,否則就是還有toast链峭,真在進(jìn)行顯示
if (index == 0) {
showNextToastLocked(false);
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
private ToastRecord getToastRecord(int uid, int pid, String packageName, boolean isSystemToast,
IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback,
int duration, Binder windowToken, int displayId,
@Nullable ITransientNotificationCallback textCallback) {
if (callback == null) {
return new TextToastRecord(this, mStatusBar, uid, pid, packageName,
isSystemToast, token, text, duration, windowToken, displayId, textCallback);
} else {
return new CustomToastRecord(this, uid, pid, packageName,
isSystemToast, token, callback, duration, windowToken, displayId);
}
}
我們來看一下NMS
的 enqueueToast
方法畦娄,這個方法中已經(jīng)屬于別的進(jìn)程了又沾。調(diào)用的時候傳了 五個參數(shù)
弊仪,
- 第一個表示當(dāng)前應(yīng)用的包名
- 第二個
token
- 第三個
Tn
表示遠(yuǎn)程回調(diào) - 也是一個
IPC
的過程 - 第四個 時長
- 第五個是顯示的
id
1) 上面代碼中對給定應(yīng)用的toast
數(shù)量進(jìn)行判斷熙卡,如果超過50
條,就直接退出励饵,這是為了防止DOS
驳癌,如果某個應(yīng)用一直循環(huán)彈出taost
就會導(dǎo)致其他應(yīng)用無法彈出,這顯然是不合理的役听。
2) 判斷完成之后颓鲜,就會創(chuàng)建ToastRecord
,它分為兩種典予,一種是TextToastRecord
甜滨,還有一種是CustomToastRecord
。由于調(diào)用enqueueToast
的時候傳入了Tn
瘤袖,所以getToastRecord
返回的是CustomToastRecord
對象衣摩。
3) 最后判斷只有一個toast
,就調(diào)用showNextToastLocked
顯示捂敌,否則就是還有好多個taost
真在顯示艾扮。
void showNextToastLocked(boolean lastToastWasTextRecord) {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
//...
if (tryShowToast(
record, rateLimitingEnabled, isWithinQuota, isPackageInForeground)) {
scheduleDurationReachedLocked(record, lastToastWasTextRecord);
mIsCurrentToastShown = true;
if (rateLimitingEnabled && !isPackageInForeground) {
mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG);
}
return;
}
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
//是否還有剩余的taost需要顯示
record = (mToastQueue.size() > 0) ? mToastQueue.get(0) : null;
}
}
private boolean tryShowToast(ToastRecord record, boolean rateLimitingEnabled,
boolean isWithinQuota, boolean isPackageInForeground) {
//.....
return record.show();
}
上面代碼中最后調(diào)用的是record.show()
這個record
也就是 CustomToastRecord
了。
接著我們來看一下他的 show
方法:
##CustomToastRecord
public final ITransientNotification callback;
@Override
public boolean show() {
if (DBG) {
Slog.d(TAG, "Show pkg=" + pkg + " callback=" + callback);
}
try {
callback.show(windowToken);
return true;
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to show custom toast " + token + " in package "+ pkg);
mNotificationManager.keepProcessAliveForToastIfNeeded(pid);
return false;
}
}
可以看到占婉,調(diào)用的是callback
的 show
方法泡嘴,這個 callback 就是在CustomToastRecord
創(chuàng)建的時候傳入的 Tn
了。這里回就調(diào)到了 Tn
的show
方法中逆济。
##Toast #Tn
TN(Context context, String packageName, Binder token, List<Callback> callbacks,
@Nullable Looper looper) {
mPresenter = new ToastPresenter(context, accessibilityManager, getService(),packageName);
@Override
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
mHandler = new Handler(looper, null) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
//.....
}
}
};
}
public void handleShow(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+ " mNextView=" + mNextView);
//...
if (mView != mNextView) {
// remove the old view if necessary
handleHide();
mView = mNextView;
mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
mHorizontalMargin, mVerticalMargin,
new CallbackBinder(getCallbacks(), mHandler));
}
}
由于 show
方法是被NMS
跨進(jìn)程的方式調(diào)用的酌予,所以他們運(yùn)行在Binder
線程池中,為了切換到Toast
請求所在的線程奖慌,這里使用了Handler
霎终。通過上面代碼,我們可以看出升薯,最終是交給ToastPresenter
去處理莱褒。
3.3 ToastPresenter最終處理
public class ToastPresenter {
//....
@VisibleForTesting
public static final int TEXT_TOAST_LAYOUT = R.layout.transient_notification;
/**
* Returns the default text toast view for message {@code text}.
*/
public static View getTextToastView(Context context, CharSequence text) {
View view = LayoutInflater.from(context).inflate(TEXT_TOAST_LAYOUT, null);
TextView textView = view.findViewById(com.android.internal.R.id.message);
textView.setText(text);
return view;
}
//....
public ToastPresenter(Context context, IAccessibilityManager accessibilityManager, INotificationManager notificationManager, String packageName) {
mContext = context;
mResources = context.getResources();
//獲取 WindowManager
mWindowManager = context.getSystemService(WindowManager.class);
mNotificationManager = notificationManager;
mPackageName = packageName;
mAccessibilityManager = accessibilityManager;
//創(chuàng)建參數(shù)
mParams = createLayoutParams();
}
private WindowManager.LayoutParams createLayoutParams() {
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST; //TYPE_TOAST:2005
params.setFitInsetsIgnoringVisibility(true);
params.setTitle(WINDOW_TITLE);
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
setShowForAllUsersIfApplicable(params, mPackageName);
return params;
}
public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity, int xOffset, int yOffset, float horizontalMargin, float verticalMargin, @Nullable ITransientNotificationCallback callback) {
show(view, token, windowToken, duration, gravity, xOffset, yOffset, horizontalMargin,
verticalMargin, callback, false /* removeWindowAnimations */);
}
public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,int xOffset, int yOffset, float horizontalMargin, float verticalMargin,@Nullable ITransientNotificationCallback callback, boolean removeWindowAnimations) {
//.....
addToastView();
trySendAccessibilityEvent(mView, mPackageName);
if (callback != null) {
try {
//回調(diào)
callback.onToastShown();
} catch (RemoteException e) {
Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e);
}
}
}
private void addToastView() {
if (mView.getParent() != null) {
mWindowManager.removeView(mView);
}
try {
// 將 Toast 視圖添加到 Window 中
mWindowManager.addView(mView, mParams);
}
}
}
3.3 Toast 總結(jié):
彈出Toast
也是一個IPC
的過程,最終通過Handler
切換到App
對應(yīng)線程涎劈。
使用IPC的原因是:
- 為了統(tǒng)一管理系統(tǒng)中所有 Toast 的消失與顯示.真正顯示和消失操作還是在 App 中完成的广凸。
其次,Toast
的窗口類型是 TYPE_TOAST
蛛枚,屬于系統(tǒng)類型
谅海,Toast
有自己的 token
,不受 Activity
控制蹦浦。
Toast
通過 WindowManager
將 view
直接添加到了Window
中扭吁,并沒有創(chuàng)建PhoneWindow
和 DecorView
,這點和Activity
與 Dialog
不同。
Toast 的添加流程如圖:
兩篇文章的總結(jié)是:
- 每一個
Window
都對應(yīng)著一個View
和 一個ViewRootImpl
-
Window
表示一個窗口的概念侥袜,也是一個抽象的概念蝌诡,它并不是實際存在的,它是以View
的方式存在的枫吧。 -
WindowManager
是我們訪問Window
的入口 -
Window
的具體實現(xiàn)位于WindowManagerService
中 -
WindowManager
和WindowManagerService
交互是一個IPC
的過程浦旱,最終的IPC
是在RootViewImpl
中完成的。