Android 開發(fā)藝術(shù)探索筆記之八 -- 理解 Window 和 WindowManager

寫在最前:本文涉及到源碼的部分,查看的是 Android 8.1.0_r33 的源碼,部分與原文中代碼有出入。

附上查看源碼的網(wǎng)址:http://androidxref.com/


學(xué)習(xí)內(nèi)容:

  • Window 和 WindowManager
  • Window 的內(nèi)部工作原理
    • Window 的添加、更新和刪除
  • Actvitiy、Dialog 等類型的 Window 對象的創(chuàng)建過程

原文開篇部分:

  • Window 是一個抽象類,具體實(shí)現(xiàn)是 PhoneWindow
  • WindowManager 是外界訪問 Window 的入口,Window 的實(shí)現(xiàn)位于 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一個 IPC 過程
  • Window 實(shí)際是 View 的管理者,視圖都是通過 Window 呈現(xiàn)的。

1.Window 和 WindowManger

通過 WindowManager 添加一個 Window

mFloatingButton = new Button(this);
mFloatintButton.setText("button");

mLayoutParams = new WindowManager.LayoutParams(LayoutParams.RTAP_CONTENT,LayoutParams.WRAP_CONTENT,0,0,PixelFormat.TRANSPARENT);
mLayoutParams.flags = KatiytOarams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;

mWindowManager.addView(mFloatingButton, mLayoutParams);

通過以上代碼,可以將一個 Button 添加到坐標(biāo)為(100,300)的位置上。

下面說明 WindowManager.LayoutParams 中的 flagstype 這兩個參數(shù):

  1. flags 參數(shù)表示 Window 的屬性,下面列出一個常用的選項(xiàng):

    1. FLAG_NOT_FOCUSABLE
      表示 WIndow 不需要獲取焦點(diǎn),也不需要接收各種輸入事件,最終事件會傳遞給下層的具有焦點(diǎn)的 Window
  1. FLAG_NOT_TOUCH_MODAL
    系統(tǒng)會將當(dāng)前 Window 區(qū)域以外的單擊事件傳遞給底層的 Window,當(dāng)前 Window 區(qū)域以內(nèi)的事件則自己處理。一般開啟,否則其他 Window 將無法收到單擊事件
  1. FLAG_SHOW_WHEN_LOCKED
    開啟此模式可以讓 Window 顯示在鎖屏界面

  2. Type 參數(shù)表示 Window 的類型,Window 有三種類型:

    1. 應(yīng)用 Window:對應(yīng)一個 Activity;層級范圍是 1 ~ 99。
    2. 子 Window:不能單獨(dú)存在,需要附屬再特定的父 Window 上,比如常見的 Dialog;層級范圍是 1000 ~ 1999
    3. 系統(tǒng) Window:需要聲明權(quán)限才能創(chuàng)建的 Window,比如 Toast;層級范圍是 2000 ~ 2999

    關(guān)于上面提到的層級范圍,此處進(jìn)行說明:Window 是分層的,每個 Window 都有對應(yīng)的 z-ordered,層級大的會覆蓋再層級小的 Window 的上面。

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

//繼承關(guān)系
public interface WindowManager extends ViewManager {
    //...
}

原文中說道,"WindowManager 操作 Window 的過程更像是在操作 Window 中的 View"。


2. Window 的內(nèi)部機(jī)制

Window 是一個抽象概念,每一個 Window都對應(yīng)著一個 View 和一個 ViewRootImpl,WIndow 和 View 通過 ViewRootImpl 建立聯(lián)系,因此 Window 以 View 的形式存在,View 才是 Window 存在的實(shí)體

實(shí)際開發(fā)中,對 Window 的訪問必須通過 WindowManager。

2.1 Window 的添加過程

添加過程通過 WindowManager 的 addView 來實(shí)現(xiàn),具體實(shí)現(xiàn)類是 WindowManagerImpl 類。

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

可以看到,實(shí)際上 WindowManagerImp 采用了橋接模式,將具體的實(shí)現(xiàn) 委托 給了 mGlobal(WindowManagerGlobal)來處理,WindowManagerGlobal 以工廠的形式向外提供自己的實(shí)例。

WindowManagerGlobal 的 addView 方法有如下幾步:

  1. 檢查參數(shù)是否合法,如果是子 Window 那么還需要調(diào)整一些布局參數(shù)

    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);
    }
    
  1. 創(chuàng)建 ViewRootImpl 并將 View 添加到列表中

    root = new ViewRootImpl(view.getContext(), display);
    
    view.setLayoutParams(wparams);
    
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    

    關(guān)于上面代碼中的 mViews,mRoots 等:

    //存儲所有 Window 所對應(yīng)的 View
    private final ArrayList<View> mViews = new ArrayList<View>();
    //存儲所有 Window 所對應(yīng)的 ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    //存儲所有 Window 中對應(yīng)的布局參數(shù)
    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
    //存儲正在被刪除的 View 對象,或者說已經(jīng)調(diào)用 removeView 方法但是刪除操作尚未完成 Window 對象
    private final ArraySet<View> mDyingViews = new ArraySet<View>();
    
  1. 通過 ViewRootImpl 來更新界面并完成 Window 的添加過程

    這個步驟通過 ViewRootImpl 的 setView 方法完成:

    //WindowManagerGlobal.addView() 方法內(nèi)部
    root.setView(view, wparams, panelParentView);
    
    //ViewRootImpl.setView(...) 方法內(nèi)部
    //...
    requestLayout();
    //...
    
    //ViewRootImpl
    @Override
    public void requestLayout() {
     if (!mHandlingLayoutInLayoutRequest) {
         checkThread();
         mLayoutRequested = true;
         scheduleTraversals();
     }
    }
    

    可以看到,通過 requestLayout 發(fā)起異步刷新請求,而其中的 scheduleTraversals 實(shí)際上是 View 繪制的入口。

    接著會通過 WindowSession 最終來完成 Window 的添加過程。

    //ViewRootImpl.setView(..)方法內(nèi)部
    try {
     mOrigWindowType = mWindowAttributes.type;
     mAttachInfo.mRecomputeGlobalAttributes = true;
     collectViewAttributes();
     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);
    }
    

    上面代碼中,mWindowSession 類型是 IWindowSession,是一個 Binder 對象,真正的實(shí)現(xiàn)類是 Session,也就是說 WIndow 的添加過程是一個 IPC 調(diào)用。

    在 Session 內(nèi)部會通過 WindowManagerService 實(shí)現(xiàn) Window 的添加:

    @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 中會為每一個應(yīng)用保留一個單獨(dú)的 Session,具體不再分析。
    (原文分析到此,認(rèn)為到此添加流程已經(jīng)很清晰,再深入 WindowManagerSercice 也只是一系列代碼細(xì)節(jié))

2.2 Window 的刪除過程

Window 的刪除過程和添加過程初始階段類似,直接分析 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);
    }
}

邏輯很清晰,首先通過 findViewLocked 方法查找待刪除的 View 的索引,然后調(diào)用 removeViewLocked 來做進(jìn)一步刪除:

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 來完成刪除操作的,調(diào)用了其 die 方法。

上面代碼中的 immediate 參數(shù)需要注意,該參數(shù)對應(yīng)了 WindowManager 中的兩種刪除接口 removeView(immediate 為 false) 和 removeViewImmediate(immediate 為 true),分別表示異步刪除和同步刪除。

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 方法內(nèi)部做了簡單判斷:

  • 如果是同步刪除,那么直接調(diào)用 doDie 方法;
  • 如果是異步刪除,則會發(fā)送一個 MSG_DIE 的請求刪除消息,ViewRootImpl 中的 Handler 會處理此消息并調(diào)用 doDie 方法;在此過程中,由于 View 尚未完成刪除操作,因此在上面 removeViewLocked 方法末尾部分代碼中,會將其添加到 mDyingViews 中。

在 doDie 方法內(nèi)部會調(diào)用 dispatchDetachedFromWindow 方法,在其中實(shí)現(xiàn)真正刪除 View 的邏輯,該方法中主要做 四件事:

  1. 垃圾回收相關(guān)的工作,比如清楚數(shù)據(jù)和消息、移除回調(diào)
  2. 通過 Session 的 remove 方法刪除Window,同樣是一個 IPC 過程,最終會調(diào)用 WindowManagerService 的 removeView 方法
  3. 調(diào)用 View 的 dispatchDetachedFromWindow 方法,內(nèi)部會調(diào)用 View 的 onDetachedFromWindow(View 從 Window 中移除時的回調(diào),做終止動畫、停止線程等一系列資源回收工作) 以及 onDetachedFromWindowInternal。
  4. 調(diào)用 WindowManagerGloabl 的 doRemoveView 方法刷新數(shù)據(jù),包括 mRoots、mParams 以及 mDyingViews,需要將當(dāng)前 Window 關(guān)聯(lián)的這三類對象從列表中刪除。

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

這個過程就比較簡單:

  • 首先更新 View 的 LayoutParams 并替換舊的 LayoutParams
  • 更新 ViewRootImpl 的 LayoutParams
  • ViewRootImpl 會通過 schedulTraversals 方法對 View 重新布局,包括 測量、布局、重繪三個過程
  • ViewRootImpl 還會通過 WindowSession 更新 Window 的視圖,這個過程最終由 WindowManagerService 的 relayoutWindow 具體實(shí)現(xiàn),同樣是一個 IPC 過程

3.Window 的創(chuàng)建過程

3.1 Activity 的 Window 的創(chuàng)建過程

首先應(yīng)該了解 Activity 的啟動過程,這一部分留待第九章再說。

簡單來說,最終會由 ActivityThread 的 performLaunchActivity 來完成整個啟動過程,此方法中會通過類加載器創(chuàng)建 Activity 的實(shí)例對象,并調(diào)用 attach 方法為其關(guān)聯(lián)運(yùn)行過程中所依賴的一系列上下文環(huán)境對象。

在 Activity 的 attach 方法里,系統(tǒng)會創(chuàng)建 Activity 所屬的 Window 對象并為其設(shè)置回調(diào)接口,Window 的對象創(chuàng)建由 PolicyManager 的 makeNewWindow 方法實(shí)現(xiàn)。同時當(dāng) Window 接收到外界的狀態(tài)改變時就會通過 Callback 接口,回調(diào) Activity 的方法。

關(guān)于 Window 的創(chuàng)建:

  • Activity 的 Window 通過 PolicyManager 的工廠方法創(chuàng)建,PolicyManager 是一個契約類,實(shí)現(xiàn)了 IPolicy 策略接口,該接口中定義了眾多工廠方法。
  • PolicyManager 的真正實(shí)現(xiàn)是 Policy 類,在 makeNewWindow 方法中,返回了 PhoneWindow 對象,由此得出 Window 的具體實(shí)現(xiàn)是 PhoneWindow。

關(guān)于 Activity 的視圖如何附屬到 Window:

Activity 的 setContentView 方法交由 Window 處理,直接分析 PhoneWindow 的 setContentView 即可

PhoneWindow 的 setContentView 方法遵循以下幾個步驟

  1. 如果沒有 DecorView,那么創(chuàng)建之。

    1. 通過 installDecor 方法內(nèi)部的 generateDecor 直接創(chuàng)建 DecorView
    2. 通過 generateLayout 方法加載具體的布局文件到 DecorView
  1. 將 View 添加到 DecorView 的 mContentParent 中

    到此步,Activity 的布局文件已經(jīng)添加到 DecorView 里面。

  1. 回調(diào) Activity 的 onContentChanged 方法通知 Activity 視圖已經(jīng)發(fā)生改變
    Activity 的 onContentChanged 方法是個空實(shí)現(xiàn),可以在 子Activity 中處理該回調(diào)。

但是!!

經(jīng)過上面的三個步驟,DecorView 尚未被 WindowManager 正式添加到 Window。由于此時 DecorView 未被 WindowManager 識別,所以這個時候的 Window 無法提供具體功能,因?yàn)樗€無法接收外界的輸入信息。

在 ActivityThread 的 handleResumeActivity 方法中,首先調(diào)用 Activity 的 onResume 方法,接著調(diào)用 Activity 的 makeVisible(),在 makeVisible 方法中,DecorView 真正完成添加和顯式這兩個過程,此時 Activity 的視圖才能被用戶看到,如下所示:

void makeVisible() {
    if(!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAtrributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

3.2 Dialog 的 Window 的創(chuàng)建過程

Dialog 的 Window 創(chuàng)建過程和 Activity 類似,有如下幾個步驟:

1. 創(chuàng)建 Window
2. 初始化 DecorView 并將 Dialog 的視圖添加到 DecorView 中
3. 將DecorView 添加到 Window 中顯示

? 在 Dialog 的 show 方法中,通過 WindowManager 的 addView 方法將 DecorView 添加到 Window 中。

Dialog 的 Window 創(chuàng)建和 Activity 的 Window 創(chuàng)建過程很類似,幾乎沒有什么區(qū)別;當(dāng) Dialog 被關(guān)閉時,會通過 WindowManager 的 removeViewImmediate(mDecor) 移除 DecorView。

需要注意

  • 普通 Dialog 必須采用 Activity 的 Context,如果采用 Application 的 Context 會報錯,因?yàn)樾枰?應(yīng)用token,而只有 Activity 擁有
  • 系統(tǒng) Window 比較特殊,不需要 token,所以也可以采用 Application 的 Context 的同時,指定對話框的 Window 為系統(tǒng)類型即可正常彈出對話框

3.3 Toast 的 Window 的創(chuàng)建過程

首先 Toast 也是基于 Window 實(shí)現(xiàn)的,但是由于 Toast 具有定時取消這一功能,所以系統(tǒng)采用了 Handler。

Toast 內(nèi)部有兩類 IPC 過程:

  1. Toast 訪問 NotificationManagerService(NMS)
  2. NMS 回調(diào) Toast 里的 TN 接口

Toast 提供 show 和 cancel 分別用于顯示和隱藏 Toast,二者內(nèi)部都是一個 IPC 過程:

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.cancel();
}

顯示和隱藏都需要 NMS 實(shí)現(xiàn),而 NMS 無法運(yùn)行在系統(tǒng)的進(jìn)程中,所以需要通過遠(yuǎn)程調(diào)用的方式。

關(guān)于 TN 這個類,它是一個 Binder 類,在 Toast 和 NMS 進(jìn)行 IPC 的過程中,當(dāng) NMS 處理 Toast 的顯示或隱藏請求時會跨進(jìn)程回調(diào) TN 中的方法,此時由于 TN 運(yùn)行在 Binder 線程池中,所以需要通過 Handler 將其切換到當(dāng)前線程中。同時這也意味著 Toast 無法在沒有 Looper 的線程中彈出。

首先分析 Toast 的顯示過程( show 方法)

  • 調(diào)用了 NMS 中的 enqueueToast 方法

  • enqueueToast 首先將 Toast 請求封裝為 ToastRecord 對象并將其添加到 mToastQueue 隊列中。

    mToastQueue 是一個 ArrayList,對非系統(tǒng)應(yīng)用而言,mToastQueue 最多同時存在 50 個 ToastRecord,此舉是為了防止 DOS 拒絕服務(wù)攻擊,避免其他應(yīng)用沒有機(jī)會彈出 Toast

  • 添加后,NMS 通過 showNextToastLocked 方法顯示當(dāng)前 Toast。

    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);
              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);
                }
                keepProcessAliveIfNeededLocked(record.pid);
                if (mToastQueue.size() > 0) {
                    record = mToastQueue.get(0);
                } else {
                    record = null;
                }
          }
      }
    }
    

    上面的代碼很簡單,Toast 的顯示通過 ToastRecord 的 callback 來完成,這個callback 實(shí)際上就是 Toast 的 TN 對象的遠(yuǎn)程 Binder,通過 callback 來訪問 TN 中的方法是需要跨進(jìn)程來完成的,最終被調(diào)用的 TN 中的方法會運(yùn)行在發(fā)起 Toast 請求的應(yīng)用的 Binder 線程池中。

  • Toast 顯示之后,NMS 會通過 scheduleTimeoutLocked 方法發(fā)送延時消息,具體延時取決于 Toast 的時長:LONG_DELAY 是 3.5s,SHORT_DELAY 是 2s

  • private void scheduleTimeoutLocked(ToastRecord r)
    {
      mHandler.removeCallbacksAndMessages(r);
      Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
      long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
      mHandler.sendMessageDelayed(m, delay);
    }
    

    延時時間過后,NMS 通過 cancelToastLocked 方法隱藏 Toast 并將其從 mToastQueue 中移除,此時如果 mToastQueue 中還有其他 Toast,那么 NMS 就繼續(xù)顯示其他 Toast。

    void cancelToastLocked(int index) {
      ToastRecord record = mToastQueue.get(index);
      try {
          record.callback.hide();
      } catch (RemoteException e) {
      Slog.w(TAG, "Object died trying to hide notification " + record.callback + " in package " + record.pkg);
      // don't worry about this, we're about to remove it from
      // the list anyway
      }
    
        ToastRecord lastToast = mToastQueue.remove(index);
        mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY);
        keepProcessAliveIfNeededLocked(record.pid);
      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 方法)

  • 同樣是通過 ToastRecord 的 callback 完成的,同樣是一次 IPC 過程,其工作過程和 Toast 的顯示過程是類似的

    try {
        record.callback.hide();
    } catch (RemoteException e) {
        Slog.w(TAG, "Object died trying to hide notification " + record.callback + " in package " + record.pkg);
        // don't worry about this, we're about to remove it from
        // the list anyway
    }
    

再看 TN 類

通過上面的分析,Toast 的顯示和隱藏實(shí)際上是通過 Toast 的 TN 這個類來實(shí)現(xiàn)的,它有兩個方法 show 和 hide,分別對應(yīng) Toast 的顯示和隱藏。這兩個方法運(yùn)行在 Binder 線程池中,內(nèi)部使用了 Handler。

/**
* schedule handleShow into the right thread
*/
@Override
public void show(IBinder windowToken) {
    if (localLOGV) Log.v(TAG, "SHOW: " + this);
    mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}

/**
* schedule handleHide into the right thread
*/
@Override
public void hide() {
    if (localLOGV) Log.v(TAG, "HIDE: " + this);
    mHandler.obtainMessage(HIDE).sendToTarget();
}

上面代碼中,分別發(fā)送了 SHOW 和 HIDE 兩種消息,在 handleMessage 中分別調(diào)用 handleShow 和 handleHide 方法,這二者才是真正完成顯示和隱藏 Toast 的地方。

  • handleShow 中 addView 方法將 Toast 的視圖添加到 Window 中
  • handleHide 中調(diào)用 remoteView 方法將 Toast 的視圖從 Window 中移除

到這里 Toast 地 Window 的創(chuàng)建過程就分析完了。


本章結(jié)束。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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