Android Framework-Activity,Dialog,Toast-添加window流程

一取董、概論

????通過上一篇文章(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)了 WindowCallback 接口,因此當(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)用到了getWindowsetContentView方法丰歌,而在 Android中Window的實現(xiàn)是 PhoneWindow灌旧。因此我們看到 PhoneWindowsetContentView方法毛甲。

##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;
}

緊接著上面的PhoneWindowsetContentView

  • 注釋2:將 activity 需要顯示的布局添加到 mContentParent 中春畔。
  • 注釋3:由于 activity 實現(xiàn)了 windowcallback 接口凤价,這里表示activity 的布局文件已經(jīng)被添加到 decorViewmParentView 中了,于是通知 ActivityonContentChanged接口。

經(jīng)過上面三個步驟,DecorView 已經(jīng)初始完成蒂秘,Activity 的布局文件以及加載到了DecorViewmParentView中了,但是這個時候DecorView還沒有被 WindowManager 正式添加到Window 中。

2.4 添加DecorView到Window

ActivityThreadhandleResumeActivity中,會調(diào)用 activityonResume 方法,接著就會將 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);就到了上一篇文章的windowManageraddView 流程了。

三笑窜、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)抬闯,DialogWindow 創(chuàng)建和ActivityWindow創(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提供了 showcancel分別用于顯示和隱藏 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)用到的是NMSenqueueToast方法:

##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);
        }
    }

我們來看一下NMSenqueueToast方法畦娄,這個方法中已經(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)用的是callbackshow方法泡嘴,這個 callback 就是在CustomToastRecord創(chuàng)建的時候傳入的 Tn了。這里回就調(diào)到了 Tnshow方法中逆济。

##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通過 WindowManagerview直接添加到了Window中扭吁,并沒有創(chuàng)建PhoneWindowDecorView,這點和ActivityDialog 不同。
Toast 的添加流程如圖:

Toast 流程圖

兩篇文章的總結(jié)是:

  • 每一個Window都對應(yīng)著一個View 和 一個 ViewRootImpl
  • Window 表示一個窗口的概念侥袜,也是一個抽象的概念蝌诡,它并不是實際存在的,它是以 View 的方式存在的枫吧。
  • WindowManager是我們訪問Window的入口
  • Window的具體實現(xiàn)位于WindowManagerService
  • WindowManagerWindowManagerService 交互是一個IPC的過程浦旱,最終的IPC 是在 RootViewImpl中完成的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末九杂,一起剝皮案震驚了整個濱河市颁湖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌例隆,老刑警劉巖甥捺,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異镀层,居然都是意外死亡涎永,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門鹿响,熙熙樓的掌柜王于貴愁眉苦臉地迎上來羡微,“玉大人,你說我怎么就攤上這事惶我÷杈螅” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵绸贡,是天一觀的道長盯蝴。 經(jīng)常有香客問我,道長听怕,這世上最難降的妖魔是什么捧挺? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮尿瞭,結(jié)果婚禮上闽烙,老公的妹妹穿的比我還像新娘。我一直安慰自己声搁,他們只是感情好黑竞,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著疏旨,像睡著了一般很魂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上檐涝,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天遏匆,我揣著相機(jī)與錄音法挨,去河邊找鬼。 笑死幅聘,一個胖子當(dāng)著我的面吹牛凡纳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喊暖,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼撕瞧!你這毒婦竟也來了陵叽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤丛版,失蹤者是張志新(化名)和其女友劉穎巩掺,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體页畦,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡胖替,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了豫缨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片独令。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖好芭,靈堂內(nèi)的尸體忽然破棺而出燃箭,到底是詐尸還是另有隱情,我是刑警寧澤舍败,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布招狸,位于F島的核電站,受9級特大地震影響邻薯,放射性物質(zhì)發(fā)生泄漏裙戏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一厕诡、第九天 我趴在偏房一處隱蔽的房頂上張望累榜。 院中可真熱鬧,春花似錦灵嫌、人聲如沸信柿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渔嚷。三九已至,卻和暖如春稠曼,著一層夾襖步出監(jiān)牢的瞬間形病,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留漠吻,地道東北人量瓜。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像途乃,于是被迫代替她去往敵國和親绍傲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容