理解 Window 和 WindowManager

理解 Window 和 WindowManager.png

Window 表示一個(gè)窗口的概念,是一個(gè)抽象類鸭轮,它的具體實(shí)現(xiàn)是 PhoneWindow臣淤。

8.1 Window 和 WindowManager

WindowManager windowManager = getWindowManager();
        Button button = new Button(this);
        button.setText("一個(gè)按鈕");
        mLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
        mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
        mLayoutParams.x = 100;
        mLayoutParams.y = 300;
        windowManager.addView(button,mLayoutParams);
  • FLAG_NOT_TOUCH_MODAL
    表示 Window 不需要獲取焦點(diǎn),也不需要輸入時(shí)間窃爷,會(huì)啟用 FLAG_NOT_TOUCH_MODAL邑蒋,最終事件會(huì)直接傳遞給具有焦點(diǎn)的 Window。

  • FLAG_NOT_TOUCH_MODAL
    在此模式下按厘,系統(tǒng)會(huì)將當(dāng)前 Window 區(qū)域以外的點(diǎn)擊事件傳遞給底層的 Window医吊,區(qū)域以內(nèi)的單擊事件則自己處理。

  • FLAG_SHOW_WHEN_LOCKED
    開啟此模式可以讓 Winodw 顯示在鎖屏的界面上逮京。

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

  • 應(yīng)用 Window:對(duì)應(yīng)這一個(gè) Activity,層級(jí)范圍:1~99懒棉;

  • 子 Window:子 Window 不能單獨(dú)存在草描,它需要附屬在特定的父 Window 中,比如 Dialog策严。層級(jí)范圍:1000~1999穗慕;

  • 系統(tǒng) Window:需要聲明權(quán)限才能創(chuàng)建,比如 Toast 和系統(tǒng)狀態(tài)欄妻导。層級(jí)范圍:2000~2999逛绵;

層級(jí)大的覆蓋層級(jí)小的,對(duì)應(yīng)這 WindowManager.LayoutParams 的type 參數(shù)倔韭。系統(tǒng)的 WindowManager 層級(jí)是最大的暑脆,一般我們可以選用 TYPE_SYSTEM_OVERLAY 或者 TYPE_SYSTEM_ERROR

   mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;//對(duì)應(yīng)值:2006

使用 TYPE_SYSTEM_ERROR 必須聲明權(quán)限

   mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;//對(duì)應(yīng)值:2010
...
  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

WindowManager 所提供的功能,只有三個(gè)常用方法:

public interface ViewManager
{
    // 添加 View
    public void addView(View view, ViewGroup.LayoutParams params);
    // 更新 View
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    // 移除 View
    public void removeView(View view);
}

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


Window 是一個(gè)抽象的概念狐肢,每一個(gè) Window 都對(duì)應(yīng)著一個(gè) View 和 ViewRootImpl添吗,Window 和 View 通過 View RootIml來建立聯(lián)系,因此 View 并不是實(shí)際存的份名,它是以 View 的形式存在碟联,對(duì) Window 的訪問必須通過 WindowManager妓美。

8.2.1 Window 的添加過程

Window 的添加過程需要通過 WIndowManager 的 addView 來實(shí)現(xiàn)。

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

WindowManager 是一個(gè)接口鲤孵,它的真正實(shí)現(xiàn)是 WindowManagerImpl壶栋。

public interface WindowManager extends ViewManager {
...

WindowManagerImpl 類中并沒有直接實(shí)現(xiàn) Window 的三大操作,而是交給了 WindowManagerGlobal普监。

public final class WindowManagerImpl implements WindowManager {
    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);
    }
...

WindowManagerImpl 這種工作米時(shí)候是典型的橋接模式贵试,將所有方法都委托給 WindowManagerGlpbal 來實(shí)現(xiàn)。

  1. 檢查參數(shù)是否合法凯正,如果是子 Window 那么還需要調(diào)整一些布局參數(shù)
public final class WindowManagerGlobal {
  public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        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 添加到列表中
    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
    private final ArraySet<View> mDyingViews = new ArraySet<View>();

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
            ...
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);
            // 將一系列對(duì)象添加到列表中
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
  • mViews:儲(chǔ)存的是所有Window 所對(duì)應(yīng)的 View毙玻;
  • mRoots:存儲(chǔ)的是所有 Window 所對(duì)應(yīng)的 ViewRootImpl;
  • ** mParams:**存儲(chǔ)的是所有 Window 的布局參數(shù)廊散;
  • mDyingViews:存儲(chǔ)了那些正在被刪除的 View 對(duì)象桑滩,調(diào)用 removeView 但是刪除操作還未完成的 Window 對(duì)象;
  1. 通過 ViewTootImpl 來更新界面并完成 Window 的添加過程
    這個(gè)步驟由 ViewRootImpl 的 setView 方法來完成允睹,在 setView 內(nèi)部會(huì)通過 requestLayout 來完成異步刷新請(qǐng)求运准。
public final class WindowManagerImpl implements WindowManager {
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
...
        requestLayout();
....

scheduleTraversals 實(shí)際是 View 繪制的入口;

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

接著會(huì)通過 WindowSession 來最終完成 Window 的添加過程缭受,WindowSession 是一個(gè) Binder 對(duì)象胁澳,真正的實(shí)現(xiàn)類是 Session,也就是 Window 的添加過程是一次 IPC 調(diào)用米者。在Session 內(nèi)部會(huì)通過 WIndowManagerService 來實(shí)現(xiàn) Window 的添加韭畸。 如此一來,Window 的添加請(qǐng)求就交給 WindowManagerService 去處理了塘雳。WindowManagerService 內(nèi)部就不用去深入分析了陆盘。(此處看不到源碼普筹。)

8.2.2 Window 的刪除過程

Window 的刪除過程和添加過程一樣败明,都是先通過 WIndowManagerImpl 后,在進(jìn)一步通過 WindowManagerGlobal 來實(shí)現(xiàn)的太防。

public final class WindowManagerGlobal {
...
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);
        }
    }

removeView 的邏輯很清晰妻顶,首先通過 findViewLocked 來查找待刪除的 View 的索引,過程就是調(diào)用數(shù)組遍歷蜒车,再調(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);
             //異步刪除時(shí),添加到 mDyingViews 中
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

removeViewLocked 是通過 ViewRootImpl 來完成刪除操作的酿愧,在 WindowManager 中提供兩種刪除接口:

  • removeView:異步刪除(常用)
    在 die 方法中異步刪除沥潭,那么就發(fā)送一個(gè) MSG_DIE 的消息,ViewRootImpl 中的 Handler 會(huì)處理此消息并調(diào)用 doDie 方法嬉挡。

  • removeViewImmediate:同步刪除(基本用不到)
    在 die 方法中同步刪除钝鸽,會(huì)直接就直接調(diào)用 doDie 方法汇恤。

這里主要說明異步刪除,具體的刪除操作由 ViewRootImpl 的 die 方法來完成拔恰,這里異步刪除會(huì)返回到 removeViewLocked 方法中繼續(xù)處理因谎,而同步刪除則不會(huì)返回。

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

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

  1. 垃圾回收相關(guān)的工作,比如清除數(shù)據(jù)和消息河爹、移除回調(diào)匠璧。
  2. 通過 Session 的 remove 方法刪除 Window:mWindowSession.remove(mWindw),這同樣是一個(gè) IPC 過程昌抠,最終會(huì)調(diào)用 WindowManagerService 的 removeWindow 方法患朱。
  3. 調(diào)用 VIew 的dispatahDetachedFromWindow 方法,在內(nèi)部會(huì)調(diào)用 View 的onDetachedFromWIndow()以及 onDetachedFromWindowInternal()炊苫。(onDetachedFromWIndow 當(dāng) View 被移除時(shí)會(huì)被調(diào)用)
  4. 調(diào)用 WindowManagerGlobal 的 doRemoveView 方法刷新數(shù)據(jù)裁厅,包括 mRoots、mParams侨艾、mDyingViews执虹,需要將當(dāng)前 Window 所關(guān)聯(lián)的這三類對(duì)象從列表中刪除。

8.2.3 Window 的更新過程

public final class WindowManagerGlobal {
...
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);
        }
    }

updateViewLayout 的步驟:

  1. 更新 View 的 LayoutParams 并替換掉老的 LayoutParams唠梨。
  2. 再更新 ViewRootImpl 中的 LayoutParanms袋励,這一步通過 ViewRootImpl 的 setLayoutParams 方法來實(shí)現(xiàn)。

ViewRootImpl 的 setLayoutParams 方法會(huì)通過 scheduleTraversals 方法來對(duì) View 重新布局当叭,包括測(cè)量茬故、布局、重繪這三個(gè)過程蚁鳖。ViewRootImpl 還會(huì)通過 WIndowSession 來更新 Window 的視圖磺芭,這過程由 WindowManagerService 的 relayoutWindow()來具體實(shí)現(xiàn),是個(gè) IPC 過程醉箕。

8.3 Window 的創(chuàng)建過程


有視圖的地方就有 Window

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

要分析 Activity 中的 Window 創(chuàng)建過程就必須立交 Activity 的啟動(dòng)過程钾腺,這里先大概了解即可。Activity 的啟動(dòng)過程很復(fù)雜讥裤,最終會(huì)由 ActivityThread 中的 performLaunchActivity來完成整個(gè)啟動(dòng)過程放棒,在這個(gè)方法內(nèi)部會(huì)通過類加載器創(chuàng)建 Activity 的實(shí)例對(duì)象,并調(diào)用其 attach 方法為其關(guān)聯(lián)運(yùn)行過程中所依賴的一系列上下文環(huán)境變量己英。

public final class ActivityThread {
  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
          if (activity != null) {
                 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);

在 Activity 的 attch 方法里间螟,系統(tǒng)會(huì)創(chuàng)建 Activity 所屬的 Window 對(duì)象并為其設(shè)置回調(diào)接口,Window 對(duì)象的創(chuàng)建是通過 PolicyManager 的makeNewWindow 方法實(shí)現(xiàn)的。由于 Activity 實(shí)現(xiàn)了 Window 的 Callback 接口厢破,因此當(dāng) WIndow 接收到外界的狀態(tài)改變時(shí)就會(huì)回調(diào) Activity 的方法邮府。Callback 接口中的方法有很多,比如 onAttachedToWindow溉奕、onDetachedFromWindow褂傀、dispatchTouchEvent等待。

Activity 的 Window 是通過 PolicyManager 的一個(gè)工廠方法來創(chuàng)建的加勤,但是從 PolicayManager 的類名可以看出仙辟,他不是一個(gè)普通的類,它是一個(gè)策略類鳄梅。

public interface IPolicy {
    public Window makeNewWindow(Context context);

    public LayoutInflater makeNewLayoutInflater(Context context);

    public WindowManagerPolicy makeNewWindowManager();

    public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}

在實(shí)際的調(diào)用中叠国,PolicyManager是如何關(guān)聯(lián)到Policy類中的mackNewWindow方法來實(shí)現(xiàn)如下,由此可以發(fā)現(xiàn)戴尸,window的具體實(shí)現(xiàn)的確就是phoneWindow

public static Window makeNewWindow(Context context) {
        return new PhoneWindow(context);
}

關(guān)于PolicyManager是如何關(guān)聯(lián)到IPolicy中粟焊,這個(gè)無法從源碼中調(diào)用關(guān)系得到,這里的猜測(cè)可能是編譯環(huán)節(jié)動(dòng)態(tài)控制的孙蒙,到這里window已經(jīng)創(chuàng)建完成了项棠,下面分析activity的視圖是怎么依附在window上的由于activity的視圖是由setContentView開始的,所有我們先看下這個(gè)方法:

 public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

PhoneWindow 的 setContentView 方法大致遵循如下幾個(gè)步驟:

  1. 如果沒有 DecorView挎峦,那么就創(chuàng)建它
    DecorView 是一個(gè) FrameLayout香追,是Activity 的頂級(jí) View,包含標(biāo)題欄和內(nèi)容欄坦胶。DecorView 的創(chuàng)建過程由 installDecor 方法來完成透典,在方法內(nèi)部會(huì)通過 generateDecor 方法來直接創(chuàng)建 DecorView,這時(shí)候 DecorView 還只是一個(gè)空白的 FrameLayout:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
...
protected DecorView generateDecor(int featureId) {
        ....
        return new DecorView(context, featureId, this, getAttributes());
    }

為了初始化 DecorView 的結(jié)構(gòu)顿苇,PhoneWIndow還需要通過 generateLayout 方法來加載具體的布局文件到 DecorView 中峭咒,具體的布局文件和系統(tǒng)版本以及主題有關(guān)。

protected ViewGroup generateLayout(DecorView decor) {
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...

其其中 ID_ANDROID_CONTENT 的定義如下纪岁,這個(gè)id對(duì)應(yīng)的就是 ViewGroup 的 mContentParent凑队。

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
  1. 將 View 添加到 DecorView 的 mContentParent 中
    這一步直接將 Activity 的視圖添加到 DecorView 的 mContentParent 中即可:mLayoutInflater.inflate(layoutResID, mContentParent)。
  2. 回調(diào) Activity 的 onCreateChanged 方法來通知 Activity 視圖已經(jīng)發(fā)生改變
    由于 Activity 實(shí)現(xiàn)了 Window 的 Callback 接口蜂科,這里表示 Activity 的布局文件以及被添加到 DecorVIew 的 mContentParent 中了顽决,于是需要通知 Acitivity 可以做相應(yīng)的處理短条。 Activity 的 onContentChanged 方法是個(gè)空實(shí)現(xiàn)导匣,我們可以在子 Activity 中處理這個(gè)回調(diào)。
@Override
    public void setContentView(int layoutResID) {
      ....
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
       ...
    }

經(jīng)過上面的三個(gè)步驟茸时,到這里為止 DecorView 以及被創(chuàng)建并初始化完畢贡定,Activity 的布局文件也以及成功添加到了 DecorView 的 mContentParent 中,但是這個(gè)時(shí)候DecorView還沒有被windowmanager添加到window中可都,這里需要正確的理解window的概念缓待,window更多的是表示一種抽象的功能集合蚓耽,雖然說早在activity的attch中window就已經(jīng)被創(chuàng)建了,但是這個(gè)時(shí)候由于DecorView還沒有被windowmanager識(shí)別旋炒,所有還不能提供具體的功能步悠,因?yàn)樗€無法接收外界的輸入,在 ActivityThread 的 handleResumeActivity 方法瘫镇,首先會(huì)調(diào)用 Acitivity 的onResume 方法沒接調(diào)用 makeVisible 方法 鼎兽。 在activityThread的makeVisible中,DecorView 才正在地完成了添加和顯示兩個(gè)過程铣除,到這里 Activity 的視圖才能被用戶看到:

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

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

Dialog 的 Window 創(chuàng)建過程和 Activity 類似谚咬,有幾個(gè)步驟:

  1. 創(chuàng)建 Window
public class Dialog implements....{
        Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        ...
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        //創(chuàng)建 PhoneWindow
        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);
    }
  1. 初始化 DecorView 并將 Dialog 的視圖添加到 DecorView 中
public void setContentView(@NonNull View view) {
        mWindow.setContentView(view);
    }
  1. 將 DecorView 添加到 Window 中并顯示
    在 Dialog 的 show 方法中,會(huì)通過 WindowManager 將 DecorView 添加到 Window 中尚粘。
public void show() {
...
        mWindowManager.addView(mDecor, l);
        mShowing = true;

當(dāng) Dialog 被關(guān)閉時(shí)择卦,它會(huì)通過 WIndowManager 來移除 DecorView。

void dismissDialog() {
       ...
            mWindowManager.removeViewImmediate(mDecor);

普通 Dialog 必須采用 Activity 的 Context郎嫁,如果采用 Application 的Context秉继,那么會(huì)報(bào)錯(cuò)。

        Dialog dialog = new Dialog(this.getApplicationContext());
        TextView textView = new TextView(this);
        textView.setText("this is a toast");
        dialog.setContentView(textView);
        dialog.show()
image.png

錯(cuò)誤信息提示:是沒有應(yīng)用 token 所導(dǎo)致泽铛,而應(yīng)用 token 一般只有 Activity 擁有秕噪。另外系統(tǒng) Window 它可以不需要 token,只需要設(shè)定一下 Window 的層級(jí)范圍為 2000~2999即可:

 mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;//對(duì)應(yīng)值:2010
...
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

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

Toast 也是基于 Window 來實(shí)現(xiàn)的厚宰,但是由于 Toast 具有定時(shí)取消這一功能腌巾,所以系統(tǒng)采用了 Handler。在 Toast 的內(nèi)部有兩類 IPC 過程铲觉。

  • 第一類:Toast 訪問 NotificaitonManagerService(簡(jiǎn)稱:NMS)
  • 第二類:NotificationManagerService 回調(diào) Toast 里的 TN 接口

Toast 屬于系統(tǒng) Window澈蝙,它內(nèi)部的視圖由兩種方式指定:

  • 一種是系統(tǒng)默認(rèn)
  • 一種是通過 setView 方法來指定一個(gè)自定義 View

他們都對(duì)應(yīng) Toast 的一個(gè)View 類型的內(nèi)部成員 mNewView。Toast 提供了 show 和cancel 撵幽,他們內(nèi)部是一個(gè) IPC 過程灯荧。

public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

    public void cancel() {
        mTN.hide();

        try {
            getService().cancelToast(mContext.getPackageName(), mTN);
        } catch (RemoteException e) {
            // Empty
        }
    }

Toast 的 show 和 cancel 都要通過 NMS 來實(shí)現(xiàn),NMS 運(yùn)行在系統(tǒng)的進(jìn)程中盐杂,所以只能通過遠(yuǎn)程調(diào)用的方式來顯示和隱藏 Toast逗载。TN 這個(gè)類是一個(gè)Binder 類,在 Toast 和 NMS 進(jìn)行 IPC 的過程中链烈,當(dāng) NMS 處理 Toast 的請(qǐng)求時(shí)會(huì)跨進(jìn)程回調(diào) TN 中的方法厉斟,由于 TN 運(yùn)行在 Binder 線程池中,所以需要通過 Handler 將其切換當(dāng)發(fā)送 Toast 請(qǐng)求所在的線程强衡。這里使用 Handler 擦秽,意味著 Toast 無法在沒有 Looper 的線程中彈出,因?yàn)?Handler 需要 Looper 才能完成切換線程的功能。

Toast 的 show 過程感挥。它調(diào)用了 enqueneToast 方法:

 INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            // 參數(shù) :包名缩搅、遠(yuǎn)程回調(diào)、Toast 的時(shí)常
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }

enqueneToast 首先將 Toast 請(qǐng)求封裝為 ToastRecord 對(duì)象并將其添加到一個(gè)名為 mToastQuene 的隊(duì)列中触幼。mToastRecord 其實(shí)是一個(gè) ArrayList硼瓣。對(duì)于非系統(tǒng)應(yīng)用來說,mToastQuene 中最多能同時(shí)存在 50 個(gè) ToastRecord 置谦,這樣做是防止 Dos(Denial of Service)巨双,防止其他應(yīng)用沒有機(jī)會(huì)彈出 Toast。

此處無源碼(要翻墻),摘一條重要的
final ToastRecord r = mToastQueue.get(i);

正常情況下霉祸,一個(gè)應(yīng)用不可能達(dá)到上限筑累,當(dāng)ToastRecord被添加到mToastQueue中后,NMS就會(huì)通過showNextToastLocked方法來顯示Toast丝蹭,下面的代碼很好理解慢宗,需要注意的是,Toast的顯示是由ToastRecord的callback來完成的奔穿,這個(gè)callback實(shí)際上就是TN對(duì)象的遠(yuǎn)程Binder镜沽,通過callback來訪問TN中的方法是需要跨進(jìn)程來完成的,最終被調(diào)用的對(duì)象是TN中方法發(fā)起在Binder線程池中贱田。

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 顯示以后缅茉,NMS 還會(huì)通過 scheduleTimeoutLocked 方法來發(fā)送一個(gè)延時(shí)消息:

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

LONG_DELAY 是3.5s,SHORT_DELAY 是 2s男摧,NMS會(huì)通過 cancelToastLocked來隱藏 toast 并且清除隊(duì)列

通過上面的分析蔬墩,大家知道toast的顯示隱藏實(shí)際上是toast的TN這個(gè)類來實(shí)現(xiàn)的,分別對(duì)應(yīng)的show/hide耗拓,由于這兩個(gè)方法都是NMS以跨進(jìn)程的方式調(diào)用的拇颅,因此他運(yùn)行在Binder池中,為了切換乔询,在他們內(nèi)部使用了handler:

private static class TN extends ITransientNotification.Stub {
        final Runnable mShow = ()—>{ handleShow(); }  };

        final Runnable mHide = ()—> { handleHide();
                mNextView = null;
            }
        };
         @Override
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }

        @Override
        public void hide() {
            if (localLOGV) Log.v(TAG, "HIDE: " + this);
            mHandler.post(mHide);
        }

上述代碼中樟插,mShow 和 mHide 是兩個(gè) Runnable,他們內(nèi)部分別調(diào)用 handleShow 和 handleHide 方法竿刁。TN 的 handleShow 中會(huì)將 Toast 的視圖添加到 Window 中:

public void handleShow() {
...
              if (mView != mNextView) {
                handleHide();
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
...
                mWM.addView(mView, mParams);
              ...
            }
        }

而 TN 的handleHide 中會(huì)將 Toast 的視圖從 Window 中移除:

public void handleHide() {
            if (mView != null) {
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                mView = null;
            }
        }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末黄锤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子食拜,更是在濱河造成了極大的恐慌鸵熟,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件监婶,死亡現(xiàn)場(chǎng)離奇詭異旅赢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)惑惶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門煮盼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人带污,你說我怎么就攤上這事僵控。” “怎么了鱼冀?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵报破,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我千绪,道長(zhǎng)充易,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任荸型,我火速辦了婚禮盹靴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瑞妇。我一直安慰自己稿静,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布辕狰。 她就那樣靜靜地躺著改备,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蔓倍。 梳的紋絲不亂的頭發(fā)上悬钳,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音偶翅,去河邊找鬼他去。 笑死,一個(gè)胖子當(dāng)著我的面吹牛倒堕,可吹牛的內(nèi)容都是我干的灾测。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼垦巴,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼媳搪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起骤宣,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤秦爆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后憔披,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體等限,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爸吮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了望门。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片形娇。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖筹误,靈堂內(nèi)的尸體忽然破棺而出桐早,到底是詐尸還是另有隱情,我是刑警寧澤厨剪,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布哄酝,位于F島的核電站,受9級(jí)特大地震影響祷膳,放射性物質(zhì)發(fā)生泄漏陶衅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一直晨、第九天 我趴在偏房一處隱蔽的房頂上張望万哪。 院中可真熱鬧,春花似錦抡秆、人聲如沸奕巍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)的止。三九已至,卻和暖如春着撩,著一層夾襖步出監(jiān)牢的瞬間诅福,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工拖叙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氓润,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓薯鳍,卻偏偏與公主長(zhǎng)得像咖气,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挖滤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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