1. 程式人生 > >Window和WindowManager(Android開發藝術探索學習筆記)

Window和WindowManager(Android開發藝術探索學習筆記)

概述

Window表示一個視窗的概念,它是一個抽象類,它的具體實現類是PhoneWindow

WindowManager是外界訪問Window的入口,Window的具體實現位於WindowManagerService中

WindowManager和WindowManagerService的互動其實是一個IPC過程。Android中所有檢視都是通過Window來呈現的,無論是Activity,Dialog還是Toast,它們的檢視都是附加在Window上的,因此Window是View的直接管理者。從事件分發的流程可以知道,觸控事件是由Window傳遞給DecorView的,Activity的setContentView()在底層也是通過Window來完成的。

WindowManager.LayoutParams

為了分析Window的工作機制,我們先了解WindowManager.LayoutParams,如下:

            mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
            mFloatingButton = new Button(this);
            mFloatingButton.setText("click me");
            mLayoutParams = new WindowManager.LayoutParams
( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT); mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR; mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; mLayoutParams.x = 100; mLayoutParams.y = 300; mFloatingButton.setOnTouchListener(this); mWindowManager.addView(mFloatingButton, mLayoutParams);

上述程式碼將一個Button新增到頻幕座標(100,300)的位置上。其中WindowManager.LayoutParams的兩個引數flagstype比較重要。

flag

flag表示Window的屬性,可以控制Window的顯示特性,下面介紹比較常用的幾個:

  • FLAG_NOT_FOCUSABLE
    設定之後Window不會獲取焦點,也不會接收各種輸入事件,最終事件會傳遞給在其下面的可獲取焦點的Window,這個flag同時會啟用 FLAG_NOT_TOUCH_MODAL flag。

  • FLAG_NOT_TOUCH_MODAL
    這個flag簡而言之就是說,當前Window區域以外的點選事件傳遞給下層Window,當前Window區域以內的點選事件自己處理。

  • FLAG_SHOW_WHEN_LOCKED
    一個特殊的flag,使得Window可以在鎖屏狀態下顯示,它使得Window比KeyGuard或其他鎖屏介面具有更高的層級。

type

type表示Window的型別,Window有三種類型,分別是應用Window,子Window和系統Window

應用類Window對應著一個Activity。子Window不能單獨存在,它需要附屬在特定的父Window中,比如Dialog就是一個子Window。系統Window是需要宣告許可權才能建立的Window,比如Toast和系統狀態列這些都是系統Window。

Window是分層的,每個Window都有對應的z-ordered,層級大的會覆蓋在層級小的Window上。在三類Window中,應用Window的層級範圍是1~99,子Window的層級範圍是1000~1999,系統Window的層級範圍是2000~2999。很顯然系統Window的層級是最大的,而且系統層級有很多值,一般我們可以選用TYPE_SYSTEM_ERROR或者TYPE_SYSTEM_OVERLAY,另外重要的是要記得在清單檔案中宣告許可權。

Window的內部機制

Window是一個抽象的概念,每一個Window都對應著一個View和一個ViewRootImpl,Window和View通過ViewRootImpl來建立聯絡,因此Window並不是實際存在的,它是以View的形式存在,這點從WindowManager的定義可以看出。WindowManager所提供的功能很簡單,常用的只有三個方法,即新增View、更新View和刪除View,這三個方法定義在ViewManager中,而WindowManager繼承了ViewManager。

public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

Window的新增過程

Window的新增過程通過WindowManager的addView來實現,WindowManager是一個介面,它的真正實現類是WindowManagerImpl。然而WindowManagerImpl並沒有直接實現Window的三大操作,而是全部交給WindowManagerGlobal來處理:

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, 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是什麼呢?
首先看到WindowManagerImpl中:

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

然後再看WindowManagerGlobal中:

    public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }

可以看到WindowManagerGlobal中獲取例項的方法是單例模式,所以其實多個WindowManagerImpl擁有同一個WindowManagerGlobal。

WindowManagerImpl這種工作模式是典型的橋接模式,將所有的操作委託給WindowManagerGlobal來實現。

WindowManagerGlobal的addView()

    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);
        } 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 root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

其中第一步對引數的合法性進行了校驗,如果是子Window還需要調整佈局引數。

第二步建立ViewRootImpl並將資訊儲存到集合中:

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

我們看到這是WindowManagerGlobal中對三個集合的宣告,mViews 儲存的是所有Window對應的View,mRoots 儲存的是所有Window對應的ViewRootImpl,mParams 儲存的是所有Window對應的佈局引數。

第三步通過ViewRootImpl來更新介面並完成Window的新增。這個步驟由ViewRootImpl的setView()來完成,setView()內部呼叫requestLayout()來完成非同步重新整理請求。接著會通過WindowSession最終來完成Window的新增過程,如下是setView()的部分程式碼:

                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);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

mWindowSession的型別是IWindowSession,它是一個Binder物件,真正的實現類是Session,也就是Window的新增過程是一次IPC呼叫。

Session內部通過WindowManagerService來實現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內部會為每一個應用保留一個單獨的Session。具體Window在WindowManagerService內部怎麼新增的,這裡就不做分析了。

Window的刪除過程

Window的刪除過程和新增過程一樣,通過WindowManagerImpl呼叫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的索引,然後再呼叫removeViewLocked()來做進一步的刪除,如下:

    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來完成刪除操作的。WindowManager中提供了兩種刪除介面,removeView()removeViewImmediate(),他們分表表示非同步刪除和同步刪除。具體的刪除操作由ViewRootImpl的die()來完成:

    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(TAG, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

在非同步刪除的情況下,die()只是傳送了一個請求刪除的訊息就返回了,這時候View還沒有完成刪除操作,所以最後將它新增到mDyingViews中,mDyingViews表示待刪除的View的集合。如果是同步刪除,不傳送訊息就直接呼叫dodie()

    void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }

            if (mAdded && !mFirst) {
                destroyHardwareRenderer();

                if (mView != null) {
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                        // If layout params have been changed, first give them
                        // to the window manager to make sure it has the correct
                        // animation info.
                        try {
                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                mWindowSession.finishDrawing(mWindow);
                            }
                        } catch (RemoteException e) {
                        }
                    }

                    mSurface.release();
                }
            }

            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

在dodie()內部會呼叫dispatchDetachedFromWindow(),真正刪除View的邏輯就在dispatchDetachedFromWindow()中:

    void dispatchDetachedFromWindow() {
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            mView.dispatchDetachedFromWindow();
        }

        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();

        destroyHardwareRenderer();

        setAccessibilityFocus(null, null);

        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;

        mSurface.release();

        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }
        try {
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }

        // Dispose the input channel after removing the window so the Window Manager
        // doesn't interpret the input channel being closed as an abnormal termination.
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }

        mDisplayManager.unregisterDisplayListener(mDisplayListener);

        unscheduleTraversals();
    }

dispatchDetachedFromWindow()主要做幾件事情:

  • 垃圾回收相關操作,比如清除資料和訊息,移除回撥。
  • 通過Session的remove()刪除Window,這同樣是一個IPC過程,最終會呼叫WindowManagerService的removeWindow()
  • 呼叫View的dispatchDetachedFromWindow(),進而呼叫View的onDetachedFromWindow(),onDetachedFromWindowInternal()。
    onDetachedFromWindow()對於大家來說一定不陌生,我們可以在這個方法內部做一些資源回收工作,比如終止動畫、停止執行緒等。

最後再呼叫WindowManagerGlobal的doRemoveView()方法重新整理資料,包括mRoots、mParams、mViews和mDyingViews,將當前Window所關聯的物件從集合中刪除。

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 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()做的事情就比較簡單了,首先先更新View的LayoutParams,接著更新ViewRootImpl的LayoutParams。ViewRootImpl會在setLayoutParams()中呼叫scheduleTraversals()來對View重新佈局重繪。除此之外ViewRootImpl還會通過WindowSession來更新Window的檢視,這個過程最終由WindowManagerService的relayoutWindow()來具體實現,這同樣是一個IPC過程。

Window的建立過程

Activity的Window建立過程

Dialog的Window建立過程

Dialog的Window建立過程和Activity的類似。
首先建立Window,通過Dialog的構造方法:

    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == 0) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

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

然後初始化DecorView,並將Dialog的檢視新增到DecorView中:

    public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
    }

最後將DecorView新增到Window中並顯示:

    public void show() {
            ……
            mWindowManager.addView(mDecor, l);
            mShowing = true;
            ……     
    }

從上面三個步驟,可以看出Dialog的Window建立過程和Activity的基本一樣。

普通Dialog有一個特殊之處,那就是構造方法必須傳入Activity的Context,如果採用Application的就會報錯,因為需要應用的token

除此之外,系統的Window比較特殊,可以不需要應用的token。所以要將Dialog對應的Window指定為系統層級:

        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);

並且在清單檔案中宣告許可權:

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

Toast的Window建立過程

Toast和Dialog不同,它的工作過程稍顯複雜。首先Toast也是基於Window來實現的,但是由於Toast具有定時取消這一功能,所以系統採用了Handler。在Toast內部有兩個IPC過程,一個是Toast訪問NotificationManagerService,第二個是NotificationManagerService回撥Toast裡的TN介面。
Toast提供了show()和cancel()分別用來顯示和隱藏Toast,它們內部是一個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.hide();

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

從上面的程式碼來看,顯示和隱藏Toast都要通過NMS來實現,由於NMS執行在系統程序中,所以只能通過遠端呼叫的方式來顯示和隱藏Toast。TN是一個Binder類,在Toast和NMS進行IPC的過程中,當MNS處理Toast的請求時會跨程序回撥TN中的方法,這個時候由於TN執行在Binder執行緒池當中,所以需要通過Handler將其切換到當前執行緒中。注意由於使用了Handler,這意味著Toast無法在沒有Looper的執行緒中彈出

Toast的show()中呼叫了NMS的enqueueToast(),第一引數代表當前應用的包名,第二個引數代表遠端回撥,第三個引數代表Toast顯示的時長:

public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            if (DBG) {
                Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
                        + " duration=" + duration);
            }

            if (pkg == null || callback == null) {
                Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
                return ;
            }

            final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));

            if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {
                if (!isSystemToast) {
                    Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
                    return;
                }
            }

            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    int index = indexOfToastLocked(pkg, callback);
                    // If it's already in the queue, we update it in place, we don't
                    // move it to the end of the queue.
                    if (index >= 0) {
                        record = mToastQueue.get(index);
                        record.update(duration);
                    } else {
                        // Limit the number of toasts that any given package except the android
                        // package can enqueue.  Prevents DOS attacks and deals with leaks.
                        if (!isSystemToast) {
                            int count = 0;
                            final int N = mToastQueue.size();
                            for (int i=0; i<N; i++) {
                                 final ToastRecord r = mToastQueue.get(i);
                                 if (r.pkg.equals(pkg)) {
                                     count++;
                                     if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                         Slog.e(TAG, "Package has already posted " + count
                                                + " toasts. Not showing more. Package=" + pkg);
                                         return;
                                     }
                                 }
                            }
                        }

                        record = new ToastRecord(callingPid, pkg, callback, duration);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                        keepProcessAliveLocked(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();
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }

在enqueueToast()中,Toast請求被封裝成ToastRecord物件並被新增到一個名為mToastQueue的佇列中。並且mToastQueue最多能存下50個ToastRecord,這是由MAX_PACKAGE_NOTIFICATIONS決定的,防止破壞性的連續請求。之後通過showNextToastLocked()來顯示當前的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();
                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);
                }
                keepProcessAliveLocked(record.pid);
                if (mToastQueue.size() > 0) {
                    record = mToastQueue.get(0);
                } else {
                    record = null;
                }
            }
        }
    }

從以上程式碼可以看到,Toast的顯示是由ToastRecord的callback來完成的,這個callback實際上就是Toast中的TN物件的遠端Binder,通過callback訪問TN中的方法需要跨程序來完成。Toast顯示以後,NMS還會通過scheduleTimeoutLocked()來發送一個延時訊息,具體的延時取決於Toast的時長:

    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並將對應的ToastRecord從mToastQueue中移除。Toast的隱藏也是通過ToastRecord的callback來完成的,同樣和Toast的顯示一樣也是一次IPC過程。

通過上面的分析大家知道,Toast的顯示和隱藏實際上是通過Toast中的TN這個類來實現的,它有兩個方法show()和hide():

        @Override
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }

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

由於這兩個方法被NMS以跨程序的方式呼叫,因此它們執行在Binder執行緒池中,為了執行切換到Toast發起請求的執行緒中,在它們的內部使用了Handler

以上程式碼中mShow和mHide是兩個Runnable,內部分別呼叫了handleShow()handleHide()

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

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

                mView = null;
            }
        }

以上兩個方法就是Toast正真完成顯示和隱藏的地方。

相關推薦

WindowWindowManager(Android開發藝術探索學習筆記)

概述 Window表示一個視窗的概念,它是一個抽象類,它的具體實現類是PhoneWindow。 WindowManager是外界訪問Window的入口,Window的具體實現位於WindowManagerService中。 WindowManager和Wi

Android開發藝術探索 學習筆記

1、Android多程序 ~1 Android多程序模式的開啟 ~~Android多程序 四個元件指定process屬性 ~~使用多程序只有一種方法,無法給一個執行緒或者一個實體類指定其執行所在的程序。(特殊情況:通過JNI在native層區域fork一

android開發藝術探索學習 之 結合Activity的生命周期了解Activity的LaunchMode

友情 dsm ask resume () new onstop androi sum 轉載請標明出處: http://blog.csdn.net/lxk_1993/article/details/50749728 本文出自:【lxk_1993的博客】;

Android開發藝術探索學習-IPC之Binder(一)

1. Binder簡介 1.1 What is Binder?     Android Dev Doc:Base class for a remotable object, the core part of a lightweight remote procedure

Android開發藝術探索讀書筆記----View事件體系1

View的概念:View是android中所有控制元件的基類。ViewGroup繼承自View,內部可以有多個控制元件也可以由Viewgroup(譬如LinearLayout) View的位置引數:top:左上角縱座標,left:右上角橫座標,right:右下角橫座標,bo

讀《android開發藝術探索筆記一:View的事件分發機制

View的事件分發機制:對於一個根viewgroup來說,點選事件產生後,首先會傳遞給它,這是它的dispatchTouchEvent就會被呼叫,如果這個viewGroup的OnInterceptTouchEvent方法返回true就表示它要攔截當前事件,接著事

Android開發藝術探索讀書筆記(一)

           首先向各位嚴重推薦主席這本書《Android開發藝術探索》。 再感謝主席邀請寫這篇讀書筆記 + 書評。書已經完整的翻完一遍了,但是還沒有細緻的品讀並run程式碼,最近有時間正好系統的把整本書從內容到程式碼都梳理一遍,一方面方便自己總結,一方面也為主席宣

android開發藝術探索筆記:Activity介面跳轉到透明主題介面,不呼叫onStop()方法

   《探索》上說跳轉透明主題的activity屬於特殊情況,不呼叫當前activity的onstop()方法,當返回介面時候又會直接呼叫onResume()。這裡做一個簡單例子以增加對這種情況的理解:正常介面跳轉情況下activity跳轉生命週期函式的呼叫(SecActiv

Android開發藝術探索筆記

9 四大元件的工作過程 本章的意義在於加深對四大元件工作方式的認識,有助於加深對Android整體的體系結構的認識。很多情況下,只有對Android的體系結構有一定認識,在實際開發中才能寫出優秀的程式碼。 讀者對四大元件的工作過程有一個感性的認識並且能夠給予上層開發一些指導意義。 9.1

Android開發藝術探索學習筆記Android的執行緒執行緒池

一、概述 1、主執行緒與子執行緒 主執行緒 又叫UI執行緒 主要作用是執行四大元件以及處理它們和使用者的互動,主要用來處理和介面相關的事情 子執行緒 執行耗時任務,比如網路請求、I/O操作等

Android開發藝術探索》第11章 Android的線程線程池

ctas serial 主線程 message minute 方法 同時 pre 控制線 第11章 Android的線程和線程池 11.1 主線程和子線程 (1)在Java中默認情況下一個進程只有一個線程,也就是主線程,其他線程都是子線程,也叫工作線程。Android中的主

Android開發藝術探索學習筆記Android的訊息機制.md

《Android開發藝術探索》學習筆記之Android的訊息機制 一、概述 1、Handler的主要作用是將某個任務切換到指定的執行緒中去執行 eg:子執行緒中無法更新UI,需切換到主執行緒 V

Android 開發藝術探索》 第11章 --- android 執行緒執行緒池

如果程序中沒有四大元件,其優先順序將會降低,intentservice 是service封裝了handerthread ,這是intentservice的優點 執行緒是作業系統的最小排程單元,是系統的一種受限制的系統資源,建立和銷燬執行緒都將有對應的開銷,所以使用執行緒池來避免這種開銷 Andr

Android 開發藝術探索》讀書筆記(一)——Activity 的生命週期啟動模式

Activity 作為 Android 四大元件之首,它作為和使用者互動的介面,在開發中使用得可謂極其頻繁,所以弄清楚 Activity 的生命週期和啟動方式是非常重要的,要牢記。 1 Activity 的生命週期全面分析 1.1 典型情況下的生命週期分析 onCrea

Android開發藝術探索筆記(一) Activity的生命週期啟動模式(1)

Activity作為Android開發中最常用的一個元件,是Android開發人員必須熟悉且掌握的重要內容。同時Activity也是在面試中經常被問到的一個方向。因此,掌握Activity的重要性也不言而喻。這或許也是為什麼任大神會在《Android開發藝術探索

關於IPC機制 相關學習Android開發藝術探索

主要包含三個內容  ,Serializable 介面,parcelable介面以及Binder,Serializable介面和parcelable介面可以完成物件的序列化過程,當我們需要通過intent和binder 傳輸資料時候就需要用parcelable 或者serializable。 Serial

Android 開發藝術探索筆記之一 -- Android 的生命週期啟動模式

學習內容: Activity 的生命週期和啟動模式以及 IntentFilter 的匹配規則分析 異常情況下的生命週期 Activity 的啟動模式以及 Flags 隱式啟動下的 Intent 匹配 Activity 的生命週期全面分析

Android開發藝術探索學習總結7 Android中為實現IPC時Binder連線池的使用

     學習基礎和材料來源於《Android開發藝術探索》;本篇主要簡單介紹一下在Android中實現IPC是Binder連線池的使用;原理性的東西介紹不多主要介紹一下使用場景和使用步驟;其實看懂下面兩張圖感覺就沒有什麼好說的;除了Binder連線池核心程式碼注意一下,

Android開發藝術探索 圖書勘誤

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android開發藝術探索——————要點總結

  Activity異常情況的生命週期: 預設情況下,Activity不做特殊處理,當系統配置發生改變後,Activity會被銷燬並重建 。由於Activity是在異常情況下終止的,系統會呼叫onSaveInstanceState來儲存當前Activity的狀態(在onStop之前)。當Acti