Android開發藝術探索 - 第8章 理解Window和WindowManager

一些說明: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的創建/更新/刪除

  1. Window的添加過程
    WindowManager實際爲一個Interface,繼承自ViewManager,真正的實體類爲WindowManagerImpl。而其中具體的add/update/remove操作,均是通過WindowManagerGlobal來執行的(橋接模式):
    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);
    }
    
    在WindowManagerGlobal的addView中,首先檢查了參數的合法性。然後針對window的不同類型做一些操作:如果是sub window則需要調整一些佈局參數;不是sub window則依據是否開了硬件加速,設置相應的使能flag:
    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;
        }
    }
    
    然後創建ViewRootImpl,並將相關的數據存儲到WindowManagerGlobal內部維護的幾個list中。
    root = new ViewRootImpl(view.getContext(), display);
    
    view.setLayoutParams(wparams);
    
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    
    // 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方法,將View綁定到ViewRootImpl。
    // do this last because it fires off messages to start doing things
    try {
        root.setView(view, wparams, panelParentView);
    
    由以上註釋可以看出,ViewRootImpl#setView中真正開始了一系列的動作。
    首先是調用了自己的requestLayout方法,執行異步渲染:
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    
    其中scheduleTraversals方法實際是,將performTraversals方法包裹爲Runnable添加到了Choreographer的刷新隊列中,當performTraversals被回調,則開始View的measure->layout->draw過程:
    mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    
    requestLayout執行完畢,開始添加Window。
    添加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);
    
    public ViewRootImpl(Context context, Display display) {
        ...
        mWindowSession = WindowManagerGlobal.getWindowSession();
    
    Session中的addToDisplay方法,執行真正的add操作。:
    @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);
    }
    
    具體操作由WindowManagerService執行。其內部爲每一個應用維護了一個Session。
  2. Window的刪除過程
    同添加Window的過程,刪除Window由WindowManagerGlobal的removeWindow執行。首先通過findViewLocked方法,得到要刪除View的index,不存在則直接拋出異常(所以remove和add操作的View對象要一致),這裏的index就是在添加window時,View添加到WindowManagerGlobal內部維護的list中的index:
    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;
    }
    
    之後調用removeViewLocked方法,執行View對應的ViewRootImpl中的die方法。這裏傳入的immediate參數,指明瞭需要異步刪除還是同步刪除。同步刪除,則直接執行doDie方法;異步刪除,則將doDie添加到消息隊列中異步執行。同步和異步操作區別,體現在WindowManagerImpl提供的兩個remove方法:removeView方法對應異步刪除,removeViewImmediate方法對應同步刪除:
    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;
    }
    
    doDie方法中真正執行了一系列與刪除相關的操作:
    • 清除數據、消息,移除回調
    • 通過Session這個遠程Binder,執行WindowManagerService的removeWindow方法
    • 調用View的dispatchDetachedFromWindow方法,從而執行onDetachedFromWindow和onDetachedFromWindowInternal回調。當View從Window移除時,可以從這方法中做一些資源回收工作,如終止動畫、停止線程。
    • 執行WindowManagerGlobal的doRemoveView方法,將mRoots/mParams/mDyingViews中的與該View對應的item移除。
  3. Window的更新過程
    更新操作非常簡單,首先更新了View和mParams中的LayoutParams,然後調用ViewRootImpl的setLayoutParams方法,更新ViewRootImpl的LayoutParams,同時調用scheduleTraversals異步執行View的measure->layout->draw過程:
    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);
        }
    }
    
    除了View的繪製,ViewRootImpl會通過Session來遠程更新Window的視圖:
    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的創建過程

  1. Activity的Window
    在Activity的attach方法中,會創建Activity的Window,並綁定回調接口。回調接口中包括的回調方法,就有onAttactedToWindow/onDetachedFromWindow/dispatchTouchEvent。當Window收到狀態改變時,就會回調對應的在Activity中定義的回調方法:
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    
    同時得到了一個本地WindowManager對象,即是WindowManagerImpl對象。每個Activity都擁有自己的WindowManagerImpl對象,通過該對象與遠程的WMS進行通信。而創建時使用的token爲Binder,通過該對象WMS可以管理Activity的View。
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);    
    
    // 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創建完畢,然後是Activity View的創建過程,即setContentView方法:
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    
    之後的工作,便交由其Window即PhoneWindow的setContentView方法去執行。Activity的root view是DecorView,而setContentView所設置的即是其中的content部分,而這裏的mContentParent就是content部分的ViewGroup。
    @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;
    }
    
    首先,如果content不存在,即DecorView不存在,則調用installDecor方法創建DecorView。該方法中通過generateDecor方法創建一個空白的DecorView實例,然後通過generateLayout爲其加載佈局,初始化其結構:
    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);
    
    具體的佈局文件由系統版本和主題決定。確定了需要加載的佈局之後,爲DecorView設置佈局(onResourcesLoaded方法中inflate了resource指定的佈局,然後添加至DecorView中),然後便可得到DecorView中content部分的parent(ID_ANDROID_CONTENT即content的ID):
    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");
        }
    
    content parent得到的初始化,此時便可其中添加content view了。最後PhoneWindow會回調onContentChanged方法,來通知Activity content view已經創建並添加到DecorView中了:
    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();
    }
    
    此時,PhoneWindow#setContentView執行完畢,但完成的工作僅僅是初始化了DecorView及其中的content view。Window更多表示的是一種抽象的功能集合,未添加到WindowManager中的Window無法提供任何功能,因爲其無法接收外界的輸入信息。
    具體的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);
    }
    
  2. Dialog的Window
    Dialog中Window的創建過程,與Activity非常相似。
    在Dialog的構造方法中,創建了PhoneWindow,並綁定了回調接口:
    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);
    
    setContentView的具體操作,也是由其Window去完成:
    public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
    }
    
    最後,在Dialog的show方法中將DecorView添加到WindowManager:
    mWindowManager.addView(mDecor, l);
    
    當Dialog被關閉,會通過WindowManager#removeViewImmediate來移除DecorView:
    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);
    
    另外,創建Dialog時指定的Context根據Window的類型有不一樣的限制:當Window爲sub window,Context必須爲Activity,否則將拋出沒有token的異常,因爲token一般爲Activity所有;當Widnow爲system window,對Context就沒有這個限制,因爲system window可以不需要token。
  3. 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:
    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
        }
    }
    
    在NMS中,根據包名爲不同App的Toast維護了一個ToastRecord的list。enqueueToast方法中,首先會根據包名和callback查找其對應的ToastRecord對象是否存在,如果不存在,則將Toast封裝爲ToastRecord對象,存入list;如果存在則直接更新對應的Toast,主要更新的duration和callback。ToastRecord創建或者更新完畢,此時就要顯示Toast了。此時如果index爲0,說明之前沒有正在顯示的Toast,則直接調用showNextToastLocked方法,顯示Toast。:
    @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();
            }
        }
    
    舊的Android版本中的實現,是對每個非系統應用的Toast數量做了不多於50個的限制。在獲取ToastRecord的index的時候,都是通過indexOfToastLocked方法校驗了傳入的包名和TN Binder對象(驗證了是否爲是同一個對象,每創建一個Toast就對應一個TN)。而新的實現,針對非系統應用,添加了一個indexOfToastPackageLocked方法,其中只校驗了包名,所以最終非系統應用的ToastRecord實例只存在一個。所以,舊版本中當連續show新的Toast導致Toast一直顯示的問題,新版本中不會存在,因爲此時每個App只有一個Toast實例,只會更新其View。
    @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;
    }
    
    showNextToastLocked方法中,則直接調用了ToastRecord的callback的show方法,而這個callback就是TN的遠程Binder對象。show因爲是RPC調用,所以其實際運行在Binder線程池中,爲了使其能正常顯示出來,使用了Handler進行異步顯示,所以Toast只能應用於有Looper的線程中
    @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;
                }
            }
        }
    }
    
    // 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();
    }
    
    Handler異步調用handleShow方法,將View添加至Window,最終將Toast顯示出來:
    mWM.addView(mView, mParams);
    
    show執行完畢,調用scheduleDurationReachedLocked方法,在一段延時之後,cancel當前的Toast。如果mToastQueue中還有其他的Toast,則繼續調用showNextToastLocked顯示下一個:
    @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時,類似顯示的過程。Toast#cancel方法會直接調用TN#cancel,來隱藏Toast,具體的隱藏過程也是通過Handler異步進行的
    // Toast#cancel
    public void cancel() {
        mTN.cancel();
    }
    
    // TN#cancel
    public void cancel() {
        if (localLOGV) Log.v(TAG, "CANCEL: " + this);
        mHandler.obtainMessage(CANCEL).sendToTarget();
    }
    
    Handler異步調用handleHide方法,其中便通過WindowManager#removeViewImmediate方法移除了VIew。Toast隱藏之後,再向NMS通過cancelToast方法發起RPC將Toast相關的數據清除:
    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;
        }
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章