【W(wǎng)indow系列】——Window中的Token

本系列博客基于android-28版本
【W(wǎng)indow系列】——Toast源碼解析
【W(wǎng)indow系列】——PopupWindow的前世今生
【W(wǎng)indow系列】——Dialog源碼解析
【W(wǎng)indow系列】——Window中的Token

前言

距離上一次發(fā)博客已經(jīng)過(guò)了10個(gè)月了~中途因?yàn)閾Q了工作,所以一直在忙工作的事,后面會(huì)慢慢恢復(fù)博客的進(jìn)度,博客還是會(huì)堅(jiān)持的!!!
本次博客還是接著上次博客的介紹,這篇博客應(yīng)該是對(duì)Window系列的收尾,前三篇博客都提到了關(guān)于Token變量,這篇博客就來(lái)分析一下Token在Window的使用中的重要性。

源碼分析

addView的主體流程

看過(guò)前面三篇博客的應(yīng)該都會(huì)發(fā)現(xiàn),無(wú)論是PopupWindow還是Dialog還是Toast,三者的最終原理都是使用WindowManager.addView來(lái)加入View的。而我們這回要學(xué)習(xí)的Token其實(shí)就是和這個(gè)方法的流程有關(guān),所以要搞清楚Token的作用,肯定首先要了解WindowManager.addView的原理。
既然是看方法的作用,那么我們首先來(lái)看一下WindowManager的構(gòu)建過(guò)程。看過(guò)前三篇博客應(yīng)該都了解WindowManager的創(chuàng)建一般都是這樣。

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

既然是Context,那么這里就來(lái)看一下ActivitygetSystemService方法(這里為什么要看Activity后面會(huì)有講解)

@Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }
        //如果是WindowService,直接返回Activity的實(shí)例
        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }

可以看到Google也會(huì)有ifelse~~~,這里其實(shí)是做了特殊判斷,如果是WINDOW_SERVICE,那么就會(huì)返回Activity中的mWindowManager。那么我們可以看一下Activity中這個(gè)對(duì)象是怎么創(chuàng)建的。

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ...

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        //創(chuàng)建WindowManager
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);
    }

可以看到在Activityattach方法中可以看到WindowManager的構(gòu)建,那我們繼續(xù)來(lái)看一下Window中的方法,這里其實(shí)也可以看到Window的實(shí)現(xiàn)類(lèi)其實(shí)是PhoneWindow

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);
        }
        //創(chuàng)建WindowManager
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

這里其實(shí)通過(guò)強(qiáng)轉(zhuǎn)已經(jīng)可以看到我們需要了解WindowManager的實(shí)現(xiàn)類(lèi)就是WindowManagerImpl,于是我們可以看看addView是怎么實(shí)現(xiàn)的

@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

好吧,又通過(guò)其他類(lèi)代理了,那么繼續(xù)看一下mGlobal對(duì)象是什么。

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
}

這里就簡(jiǎn)單多了,可以看到mGlobal是一個(gè)WindowManagerGlobal對(duì)象,并且這個(gè)對(duì)象是一個(gè)final對(duì)象。在WindowManagerGlobal對(duì)象中我們終于看到了addView的實(shí)現(xiàn)

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ....

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
        //設(shè)置token
            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;
       ...
        //創(chuàng)建ViewRootImpl
        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 {
            //setView方法
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

這里其實(shí)有三個(gè)重要的步驟

    1. 設(shè)置Token
    1. 初始化ViewRootImpl
    1. 調(diào)用setView方法

其中第一個(gè)步驟parentWindow.adjustLayoutParamsForSubWindow(wparams);通過(guò)方法名我們可以簡(jiǎn)單的看出來(lái)這個(gè)方法的作用是通過(guò)parent給子Window設(shè)置param,這里先不詳細(xì)展開(kāi)方法的內(nèi)部(后面會(huì)分析到)。
而第二個(gè)和第三個(gè)步驟,熟悉Activity的View的繪制流程的其實(shí)應(yīng)該對(duì)于這兩個(gè)方法很熟悉,這個(gè)其實(shí)就是我們View繪制起始的地方。由于本篇博客不是詳細(xì)講解View的繪制流程的,所以這里不對(duì)于這個(gè)方法詳細(xì)展開(kāi),后面如果有必要會(huì)專(zhuān)門(mén)寫(xiě)博客分析這個(gè)地方(因?yàn)榫W(wǎng)上這類(lèi)的博客實(shí)在太多了),這里先說(shuō)一下結(jié)論吧,我們調(diào)用setView是怎么能顯示到頁(yè)面中的呢,在setView中會(huì)調(diào)用會(huì)有這樣一個(gè)代碼。

try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    //通過(guò)IPC加入View
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e)

這里會(huì)通過(guò)IPC調(diào)用mWindowSession.addToDisplay,簡(jiǎn)單說(shuō)一下調(diào)用鏈,mWindowSessionaddToDisplay使用的是SessionaddToDisplay方法

public class Session extends IWindowSession.Stub
        implements IBinder.DeathRecipient {
    final WindowManagerService mService;
    @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);
    }
}

終于看到這里的主人公了,這里可以看到調(diào)用的WindowManagerServiceaddWindow方法。

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
        int[] appOp = new int[1];
        //校驗(yàn)權(quán)限
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }

        boolean reportNewConfig = false;
        WindowState parentWindow = null;
        long origId;
        final int callingUid = Binder.getCallingUid();
        final int type = attrs.type;

        synchronized(mWindowMap) {
            if (!mDisplayReady) {
                throw new IllegalStateException("Display has not been initialialized");
            }

            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
            if (displayContent == null) {
                Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
                        + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            if (!displayContent.hasAccess(session.mUid)
                    && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {
                Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
                        + "does not have access: " + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            if (mWindowMap.containsKey(client.asBinder())) {
                Slog.w(TAG_WM, "Window " + client + " is already added");
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }
            //type判斷
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }

            if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
                Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display.  Aborting.");
                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
            }

            AppWindowToken atoken = null;
            final boolean hasParent = parentWindow != null;
            // Use existing parent window token for child windows since they go in the same token
            // as there parent window so we can apply the same policy on them.
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
            // If this is a child window, we want to apply the same type checking rules as the
            // parent window type.
            final int rootType = hasParent ? parentWindow.mAttrs.type : type;

            boolean addToastWindowRequiresToken = false;

            if (token == null) {
                if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_INPUT_METHOD) {
                    Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_WALLPAPER) {
                    Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_TOAST) {
                    // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                            parentWindow)) {
                        Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                }
                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                token = new WindowToken(this, binder, type, false, displayContent,
                        session.mCanAddInternalSystemWindow);
            } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                atoken = token.asAppWindowToken();
                if (atoken == null) {
                    Slog.w(TAG_WM, "Attempted to add window with non-application token "
                          + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } else if (atoken.removed) {
                    Slog.w(TAG_WM, "Attempted to add window with exiting application token "
                          + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_APP_EXITING;
                }
            } else if (rootType == TYPE_INPUT_METHOD) {
                if (token.windowType != TYPE_INPUT_METHOD) {
                    Slog.w(TAG_WM, "Attempted to add input method window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (rootType == TYPE_VOICE_INTERACTION) {
                if (token.windowType != TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (rootType == TYPE_WALLPAPER) {
                if (token.windowType != TYPE_WALLPAPER) {
                    Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (rootType == TYPE_DREAM) {
                if (token.windowType != TYPE_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
                if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_TOAST) {
                // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
                        callingUid, parentWindow);
                if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
                    Slog.w(TAG_WM, "Attempted to add a toast window with bad token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_QS_DIALOG) {
                if (token.windowType != TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (token.asAppWindowToken() != null) {
                Slog.w(TAG_WM, "Non-null appWindowToken for system window of rootType=" + rootType);
                // It is not valid to use an app token with other system types; we will
                // instead make a new token for it (as if null had been passed in for the token).
                attrs.token = null;
                token = new WindowToken(this, client.asBinder(), type, false, displayContent,
                        session.mCanAddInternalSystemWindow);
            }

            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
            if (win.mDeathRecipient == null) {
                // Client has apparently died, so there is no reason to
                // continue.
                Slog.w(TAG_WM, "Adding window client " + client.asBinder()
                        + " that is dead, aborting.");
                return WindowManagerGlobal.ADD_APP_EXITING;
            }

            if (win.getDisplayContent() == null) {
                Slog.w(TAG_WM, "Adding window to Display that has been removed.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            mPolicy.adjustWindowParamsLw(win.mAttrs);
            win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));

            res = mPolicy.prepareAddWindowLw(win, attrs);
            if (res != WindowManagerGlobal.ADD_OKAY) {
                return res;
            }

            final boolean openInputChannels = (outInputChannel != null
                    && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
            if  (openInputChannels) {
                win.openInputChannel(outInputChannel);
            }

            // If adding a toast requires a token for this app we always schedule hiding
            // toast windows to make sure they don't stick around longer then necessary.
            // We hide instead of remove such windows as apps aren't prepared to handle
            // windows being removed under them.
            //
            // If the app is older it can add toasts without a token and hence overlay
            // other apps. To be maximally compatible with these apps we will hide the
            // window after the toast timeout only if the focused window is from another
            // UID, otherwise we allow unlimited duration. When a UID looses focus we
            // schedule hiding all of its toast windows.
            if (type == TYPE_TOAST) {
                if (!getDefaultDisplayContentLocked().canAddToastWindowForUid(callingUid)) {
                    Slog.w(TAG_WM, "Adding more than one toast window for UID at a time.");
                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                }
                // Make sure this happens before we moved focus as one can make the
                // toast focusable to force it not being hidden after the timeout.
                // Focusable toasts are always timed out to prevent a focused app to
                // show a focusable toasts while it has focus which will be kept on
                // the screen after the activity goes away.
                if (addToastWindowRequiresToken
                        || (attrs.flags & LayoutParams.FLAG_NOT_FOCUSABLE) == 0
                        || mCurrentFocus == null
                        || mCurrentFocus.mOwnerUid != callingUid) {
                    mH.sendMessageDelayed(
                            mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),
                            win.mAttrs.hideTimeoutMilliseconds);
                }
            }

            // From now on, no exceptions or errors allowed!

            res = WindowManagerGlobal.ADD_OKAY;
            if (mCurrentFocus == null) {
                mWinAddedSinceNullFocus.add(win);
            }

            if (excludeWindowTypeFromTapOutTask(type)) {
                displayContent.mTapExcludedWindows.add(win);
            }

            origId = Binder.clearCallingIdentity();

            win.attach();
            mWindowMap.put(client.asBinder(), win);
            if (win.mAppOp != AppOpsManager.OP_NONE) {
                int startOpResult = mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(),
                        win.getOwningPackage());
                if ((startOpResult != AppOpsManager.MODE_ALLOWED) &&
                        (startOpResult != AppOpsManager.MODE_DEFAULT)) {
                    win.setAppOpVisibilityLw(false);
                }
            }

            final AppWindowToken aToken = token.asAppWindowToken();
            if (type == TYPE_APPLICATION_STARTING && aToken != null) {
                aToken.startingWindow = win;
                if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow: " + aToken
                        + " startingWindow=" + win);
            }

            boolean imMayMove = true;

            win.mToken.addWindow(win);
            if (type == TYPE_INPUT_METHOD) {
                win.mGivenInsetsPending = true;
                setInputMethodWindowLocked(win);
                imMayMove = false;
            } else if (type == TYPE_INPUT_METHOD_DIALOG) {
                displayContent.computeImeTarget(true /* updateImeTarget */);
                imMayMove = false;
            } else {
                if (type == TYPE_WALLPAPER) {
                    displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
                    // If there is currently a wallpaper being shown, and
                    // the base layer of the new window is below the current
                    // layer of the target window, then adjust the wallpaper.
                    // This is to avoid a new window being placed between the
                    // wallpaper and its target.
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                }
            }

            // If the window is being added to a stack that's currently adjusted for IME,
            // make sure to apply the same adjust to this new window.
            win.applyAdjustForImeIfNeeded();

            if (type == TYPE_DOCK_DIVIDER) {
                mRoot.getDisplayContent(displayId).getDockedDividerController().setWindow(win);
            }

            final WindowStateAnimator winAnimator = win.mWinAnimator;
            winAnimator.mEnterAnimationPending = true;
            winAnimator.mEnteringAnimation = true;
            // Check if we need to prepare a transition for replacing window first.
            if (atoken != null && atoken.isVisible()
                    && !prepareWindowReplacementTransition(atoken)) {
                // If not, check if need to set up a dummy transition during display freeze
                // so that the unfreeze wait for the apps to draw. This might be needed if
                // the app is relaunching.
                prepareNoneTransitionForRelaunching(atoken);
            }

            if (displayContent.isDefaultDisplay) {
                final DisplayInfo displayInfo = displayContent.getDisplayInfo();
                final Rect taskBounds;
                if (atoken != null && atoken.getTask() != null) {
                    taskBounds = mTmpRect;
                    atoken.getTask().getBounds(mTmpRect);
                } else {
                    taskBounds = null;
                }
                if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, displayInfo.rotation,
                        displayInfo.logicalWidth, displayInfo.logicalHeight, outContentInsets,
                        outStableInsets, outOutsets)) {
                    res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
                }
            } else {
                outContentInsets.setEmpty();
                outStableInsets.setEmpty();
            }

            if (mInTouchMode) {
                res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
            }
            if (win.mAppToken == null || !win.mAppToken.isClientHidden()) {
                res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
            }

            mInputMonitor.setUpdateInputWindowsNeededLw();

            boolean focusChanged = false;
            if (win.canReceiveKeys()) {
                focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                        false /*updateInputWindows*/);
                if (focusChanged) {
                    imMayMove = false;
                }
            }

            if (imMayMove) {
                displayContent.computeImeTarget(true /* updateImeTarget */);
            }

            // Don't do layout here, the window must call
            // relayout to be displayed, so we'll do it there.
            displayContent.assignWindowLayers(false /* setLayoutNeeded */);

            if (focusChanged) {
                mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
            }
            mInputMonitor.updateInputWindowsLw(false /*force*/);

            if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
                    + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));

            if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false, displayId)) {
                reportNewConfig = true;
            }
        }

        if (reportNewConfig) {
            sendNewConfiguration(displayId);
        }

        Binder.restoreCallingIdentity(origId);

        return res;
    }

從上面我們可以關(guān)注到兩個(gè)地方,第一個(gè)是這里會(huì)先判斷權(quán)限,第二就是這里會(huì)判斷token,如果不滿(mǎn)足則直接返回,這里的返回則還會(huì)通過(guò)IPC返回到我們剛才的ViewRootImlsetView的方法里。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

               ...
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    //獲取addView的返回值
                    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();
                    }
                }

                if (mTranslator != null) {
                    mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
                }
                mPendingOverscanInsets.set(0, 0, 0, 0);
                mPendingContentInsets.set(mAttachInfo.mContentInsets);
                mPendingStableInsets.set(mAttachInfo.mStableInsets);
                mPendingVisibleInsets.set(0, 0, 0, 0);
                mAttachInfo.mAlwaysConsumeNavBar =
                        (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR) != 0;
                mPendingAlwaysConsumeNavBar = mAttachInfo.mAlwaysConsumeNavBar;
                if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
                //判斷返回值是否異常
                if (res < WindowManagerGlobal.ADD_OKAY) {
                    mAttachInfo.mRootView = null;
                    mAdded = false;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    switch (res) {
                        case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                        case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not valid; is your activity running?");
                        case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not for an application");
                        case WindowManagerGlobal.ADD_APP_EXITING:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- app for token " + attrs.token
                                    + " is exiting");
                        case WindowManagerGlobal.ADD_DUPLICATE_ADD:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- window " + mWindow
                                    + " has already been added");
                        case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
                            // Silently ignore -- we would have just removed it
                            // right away, anyway.
                            return;
                        case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                    + mWindow + " -- another window of type "
                                    + mWindowAttributes.type + " already exists");
                        case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                    + mWindow + " -- permission denied for window type "
                                    + mWindowAttributes.type);
                        case WindowManagerGlobal.ADD_INVALID_DISPLAY:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                    + mWindow + " -- the specified display can not be found");
                        case WindowManagerGlobal.ADD_INVALID_TYPE:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                    + mWindow + " -- the specified window type "
                                    + mWindowAttributes.type + " is not valid");
                    }
                    throw new RuntimeException(
                            "Unable to add window -- unknown error code " + res);
                }

                ...
            }
        }
    }

可以看到我們拿到返回值,如果有錯(cuò)誤,我們經(jīng)常碰到的BadTokenException異常就在這里碰到了。
到這里我們大體流程上應(yīng)該有一個(gè):

  • 1.無(wú)論是PopupWindow還是Dialog還是Toast,三者的最終原理都是使用WindowManager.addView來(lái)加入View的
  • 2.WindowManager是在Activity的attach方法創(chuàng)建的,Window的實(shí)現(xiàn)類(lèi)是PhoneWindow,而WindowManager的實(shí)現(xiàn)類(lèi)是WindowManagerGlobal
  • 3.最終addView的方法會(huì)調(diào)用ViewRootImlsetView方法
  • 4.setView會(huì)通過(guò)IPC最終調(diào)用WindowManagerServiceaddView方法。
  • 5.而WindowManagerServiceaddView方法會(huì)通過(guò)token進(jìn)行一系列的判斷,如果不符合條件則直接return
  • 6.ViewRootImlsetView會(huì)根據(jù)IPC返回的結(jié)果,如果不正確,則會(huì)拋出異常。

Token的創(chuàng)建流程

看了上面的流程我們大體上有了一個(gè)概念,就是我們經(jīng)常遇到的BadTokenException其實(shí)就是和我們的token有關(guān),那么這里的token是怎么創(chuàng)建的呢?這里我們就要回到剛才的地方。

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        ...
        mToken = token;
        ...

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        ...
    }

首先在Activityattach方法我們會(huì)看到這里Activity保存了傳入的token對(duì)象,并且還設(shè)置給了PhoneWindow
然后在調(diào)用WindowManagerGlobaladdView方法的時(shí)候、

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            //設(shè)置token的方法
            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;
            }
        }
        ...
    }

這里有一個(gè)parentWindow的概念

    1. 如果是應(yīng)用程序窗口的話,這個(gè)parentWindow就是activity的window
    1. 如果是子窗口的話,這個(gè)parentWindow就是activity的window
    1. 如果是系統(tǒng)窗口的話,那個(gè)parentWindow就是null
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        CharSequence curTitle = wp.getTitle();
        //子窗口
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
                    wp.token = decor.getWindowToken();
                }
            }
            ...
        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
            //系統(tǒng)類(lèi)型的Window
            // We don't set the app token to this system window because the life cycles should be
            // independent. If an app creates a system window and then the app goes to the stopped
            // state, the system window should not be affected (can still show and receive input
            // events).
            ...
        } else {
            //應(yīng)用程序窗口
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
            ...
    }

可以看到這里分別針對(duì)不同類(lèi)型的type來(lái)進(jìn)行判斷,給WindowManager.LayoutParams設(shè)置token,而設(shè)置完tokenparam后,這個(gè)token就會(huì)一直帶到我們剛才的流程中,進(jìn)行判斷。那么這里我們就來(lái)分別看一下幾種類(lèi)型的Windowtoken

public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable {
        //窗口的絕對(duì)XY位置,需要考慮gravity屬性
        public int x;
        public int y;
        //在橫縱方向上為相關(guān)的View預(yù)留多少擴(kuò)展像素,如果是0則此view不能被拉伸,其他情況下擴(kuò)展像素被widget均分
        public float horizontalWeight;
        public float verticalWeight;
        //窗口類(lèi)型
        //有3種主要類(lèi)型如下:
        //ApplicationWindows取值在FIRST_APPLICATION_WINDOW與LAST_APPLICATION_WINDOW之間,是常用的頂層應(yīng)用程序窗口,須將token設(shè)置成Activity的token;
        //SubWindows取值在FIRST_SUB_WINDOW和LAST_SUB_WINDOW之間,與頂層窗口相關(guān)聯(lián),需將token設(shè)置成它所附著宿主窗口的token;
        //SystemWindows取值在FIRST_SYSTEM_WINDOW和LAST_SYSTEM_WINDOW之間,不能用于應(yīng)用程序,使用時(shí)需要有特殊權(quán)限,它是特定的系統(tǒng)功能才能使用;
        public int type;

        //WindowType:開(kāi)始應(yīng)用程序窗口
        public static final int FIRST_APPLICATION_WINDOW = 1;
        //WindowType:所有程序窗口的base窗口,其他應(yīng)用程序窗口都顯示在它上面
        public static final int TYPE_BASE_APPLICATION   = 1;
        //WindowType:普通應(yīng)用程序窗口,token必須設(shè)置為Activity的token來(lái)指定窗口屬于誰(shuí)
        public static final int TYPE_APPLICATION        = 2;
        //WindowType:應(yīng)用程序啟動(dòng)時(shí)所顯示的窗口,應(yīng)用自己不要使用這種類(lèi)型,它被系統(tǒng)用來(lái)顯示一些信息,直到應(yīng)用程序可以開(kāi)啟自己的窗口為止
        public static final int TYPE_APPLICATION_STARTING = 3;
        //WindowType:結(jié)束應(yīng)用程序窗口
        public static final int LAST_APPLICATION_WINDOW = 99;

        //WindowType:SubWindows子窗口,子窗口的Z序和坐標(biāo)空間都依賴(lài)于他們的宿主窗口
        public static final int FIRST_SUB_WINDOW        = 1000;
        //WindowType: 面板窗口,顯示于宿主窗口的上層
        public static final int TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW;
        //WindowType:媒體窗口(例如視頻),顯示于宿主窗口下層
        public static final int TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1;
        //WindowType:應(yīng)用程序窗口的子面板,顯示于所有面板窗口的上層
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
        //WindowType:對(duì)話框,類(lèi)似于面板窗口,繪制類(lèi)似于頂層窗口,而不是宿主的子窗口
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
        //WindowType:媒體信息,顯示在媒體層和程序窗口之間,需要實(shí)現(xiàn)半透明效果
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4;
        //WindowType:子窗口結(jié)束
        public static final int LAST_SUB_WINDOW         = 1999;

        //WindowType:系統(tǒng)窗口,非應(yīng)用程序創(chuàng)建
        public static final int FIRST_SYSTEM_WINDOW     = 2000;
        //WindowType:狀態(tài)欄,只能有一個(gè)狀態(tài)欄,位于屏幕頂端,其他窗口都位于它下方
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
        //WindowType:搜索欄,只能有一個(gè)搜索欄,位于屏幕上方
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
        //WindowType:電話窗口,它用于電話交互(特別是呼入),置于所有應(yīng)用程序之上,狀態(tài)欄之下
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
        //WindowType:系統(tǒng)提示,出現(xiàn)在應(yīng)用程序窗口之上
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
        //WindowType:鎖屏窗口
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
        //WindowType:信息窗口,用于顯示Toast
        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;
        //WindowType:系統(tǒng)頂層窗口,顯示在其他一切內(nèi)容之上,此窗口不能獲得輸入焦點(diǎn),否則影響鎖屏
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
        //WindowType:電話優(yōu)先,當(dāng)鎖屏?xí)r顯示,此窗口不能獲得輸入焦點(diǎn),否則影響鎖屏
        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
        //WindowType:系統(tǒng)對(duì)話框
        public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;
        //WindowType:鎖屏?xí)r顯示的對(duì)話框
        public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;
        //WindowType:系統(tǒng)內(nèi)部錯(cuò)誤提示,顯示于所有內(nèi)容之上
        public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;
        //WindowType:內(nèi)部輸入法窗口,顯示于普通UI之上,應(yīng)用程序可重新布局以免被此窗口覆蓋
        public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;
        //WindowType:內(nèi)部輸入法對(duì)話框,顯示于當(dāng)前輸入法窗口之上
        public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
        //WindowType:墻紙窗口
        public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;
        //WindowType:狀態(tài)欄的滑動(dòng)面板
        public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
        //WindowType:安全系統(tǒng)覆蓋窗口,這些窗戶(hù)必須不帶輸入焦點(diǎn),否則會(huì)干擾鍵盤(pán)
        public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
        //WindowType:拖放偽窗口,只有一個(gè)阻力層(最多),它被放置在所有其他窗口上面
        public static final int TYPE_DRAG               = FIRST_SYSTEM_WINDOW+16;
        //WindowType:狀態(tài)欄下拉面板
        public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
        //WindowType:鼠標(biāo)指針
        public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
        //WindowType:導(dǎo)航欄(有別于狀態(tài)欄時(shí))
        public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;
        //WindowType:音量級(jí)別的覆蓋對(duì)話框,顯示當(dāng)用戶(hù)更改系統(tǒng)音量大小
        public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;
        //WindowType:起機(jī)進(jìn)度框,在一切之上
        public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
        //WindowType:假窗,消費(fèi)導(dǎo)航欄隱藏時(shí)觸摸事件
        public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22;
        //WindowType:夢(mèng)想(屏保)窗口,略高于鍵盤(pán)
        public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;
        //WindowType:導(dǎo)航欄面板(不同于狀態(tài)欄的導(dǎo)航欄)
        public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
        //WindowType:universe背后真正的窗戶(hù)
        public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25;
        //WindowType:顯示窗口覆蓋,用于模擬輔助顯示設(shè)備
        public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
        //WindowType:放大窗口覆蓋,用于突出顯示的放大部分可訪問(wèn)性放大時(shí)啟用
        public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
        //WindowType:......
        public static final int TYPE_KEYGUARD_SCRIM           = FIRST_SYSTEM_WINDOW+29;
        public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;
        public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
        public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
        //WindowType:系統(tǒng)窗口結(jié)束
        public static final int LAST_SYSTEM_WINDOW      = 2999;

        //MemoryType:窗口緩沖位于主內(nèi)存
        public static final int MEMORY_TYPE_NORMAL = 0;
        //MemoryType:窗口緩沖位于可以被DMA訪問(wèn),或者硬件加速的內(nèi)存區(qū)域
        public static final int MEMORY_TYPE_HARDWARE = 1;
        //MemoryType:窗口緩沖位于可被圖形加速器訪問(wèn)的區(qū)域
        public static final int MEMORY_TYPE_GPU = 2;
        //MemoryType:窗口緩沖不擁有自己的緩沖區(qū),不能被鎖定,緩沖區(qū)由本地方法提供
        public static final int MEMORY_TYPE_PUSH_BUFFERS = 3;

        //指出窗口所使用的內(nèi)存緩沖類(lèi)型,默認(rèn)為NORMAL 
        public int memoryType;

        //Flag:當(dāng)該window對(duì)用戶(hù)可見(jiàn)的時(shí)候,允許鎖屏
        public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;
        //Flag:讓該window后所有的東西都成暗淡
        public static final int FLAG_DIM_BEHIND        = 0x00000002;
        //Flag:讓該window后所有東西都模糊(4.0以上已經(jīng)放棄這種毛玻璃效果)
        public static final int FLAG_BLUR_BEHIND        = 0x00000004;
        //Flag:讓window不能獲得焦點(diǎn),這樣用戶(hù)快就不能向該window發(fā)送按鍵事
        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
        //Flag:讓該window不接受觸摸屏事件
        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
        //Flag:即使在該window在可獲得焦點(diǎn)情況下,依舊把該window之外的任何event發(fā)送到該window之后的其他window
        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
        //Flag:當(dāng)手機(jī)處于睡眠狀態(tài)時(shí),如果屏幕被按下,那么該window將第一個(gè)收到
        public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
        //Flag:當(dāng)該window對(duì)用戶(hù)可見(jiàn)時(shí),讓設(shè)備屏幕處于高亮(bright)狀態(tài)
        public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
        //Flag:讓window占滿(mǎn)整個(gè)手機(jī)屏幕,不留任何邊界
        public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
        //Flag:window大小不再不受手機(jī)屏幕大小限制,即window可能超出屏幕之外
        public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
        //Flag:window全屏顯示
        public static final int FLAG_FULLSCREEN      = 0x00000400;
        //Flag:恢復(fù)window非全屏顯示
        public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
        //Flag:開(kāi)啟抖動(dòng)(dithering)
        public static final int FLAG_DITHER             = 0x00001000;
        //Flag:當(dāng)該window在進(jìn)行顯示的時(shí)候,不允許截屏
        public static final int FLAG_SECURE             = 0x00002000;
        //Flag:一個(gè)特殊模式的布局參數(shù)用于執(zhí)行擴(kuò)展表面合成時(shí)到屏幕上
        public static final int FLAG_SCALED             = 0x00004000;
        //Flag:用于windows時(shí),經(jīng)常會(huì)使用屏幕用戶(hù)持有反對(duì)他們的臉,它將積極過(guò)濾事件流,以防止意外按在這種情況下,可能不需要為特定的窗口,在檢測(cè)到這樣一個(gè)事件流時(shí),應(yīng)用程序?qū)⒔邮杖∠\(yùn)動(dòng)事件表明,這樣應(yīng)用程序可以處理這相應(yīng)地采取任何行動(dòng)的事件,直到手指釋放
        public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;
        //Flag:一個(gè)特殊的選項(xiàng)只用于結(jié)合FLAG_LAYOUT_IN_SC
        public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
        //Flag:轉(zhuǎn)化的狀態(tài)FLAG_NOT_FOCUSABLE對(duì)這個(gè)窗口當(dāng)前如何進(jìn)行交互的方法
        public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
        //Flag:如果你設(shè)置了該flag,那么在你FLAG_NOT_TOUNCH_MODAL的情況下,即使觸摸屏事件發(fā)送在該window之外,其事件被發(fā)送到了后面的window,那么該window仍然將以MotionEvent.ACTION_OUTSIDE形式收到該觸摸屏事件
        public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
        //Flag:當(dāng)鎖屏的時(shí)候,顯示該window
        public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
        //Flag:在該window后顯示系統(tǒng)的墻紙
        public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
        //Flag:當(dāng)window被顯示的時(shí)候,系統(tǒng)將把它當(dāng)做一個(gè)用戶(hù)活動(dòng)事件,以點(diǎn)亮手機(jī)屏幕
        public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
        //Flag:消失鍵盤(pán)
        public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
        //Flag:當(dāng)該window在可以接受觸摸屏情況下,讓因在該window之外,而發(fā)送到后面的window的觸摸屏可以支持split touch
        public static final int FLAG_SPLIT_TOUCH = 0x00800000;
        //Flag:對(duì)該window進(jìn)行硬件加速,該flag必須在Activity或Dialog的Content View之前進(jìn)行設(shè)置
        public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
        //Flag:讓window占滿(mǎn)整個(gè)手機(jī)屏幕,不留任何邊界
        public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
        //Flag:請(qǐng)求一個(gè)半透明的狀態(tài)欄背景以最小的系統(tǒng)提供保護(hù)
        public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
        //Flag:請(qǐng)求一個(gè)半透明的導(dǎo)航欄背景以最小的系統(tǒng)提供保護(hù)
        public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
        //Flag:......
        public static final int FLAG_LOCAL_FOCUS_MODE = 0x10000000;
        public static final int FLAG_SLIPPERY = 0x20000000;
        public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 0x40000000;
        public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;

        //行為選項(xiàng)標(biāo)記
        public int flags;

        //PrivateFlags:......
        public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001;
        public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002;
        public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004;
        public static final int PRIVATE_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
        public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040;
        public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 0x00000080;
        public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
        public static final int PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR = 0x00000200;
        public static final int PRIVATE_FLAG_KEYGUARD = 0x00000400;
        public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;

        //私有的行為選項(xiàng)標(biāo)記
        public int privateFlags;

        public static final int NEEDS_MENU_UNSET = 0;
        public static final int NEEDS_MENU_SET_TRUE = 1;
        public static final int NEEDS_MENU_SET_FALSE = 2;
        public int needsMenuKey = NEEDS_MENU_UNSET;

        public static boolean mayUseInputMethod(int flags) {
            ......
        }

        //SOFT_INPUT:用于描述軟鍵盤(pán)顯示規(guī)則的bite的mask
        public static final int SOFT_INPUT_MASK_STATE = 0x0f;
        //SOFT_INPUT:沒(méi)有軟鍵盤(pán)顯示的約定規(guī)則
        public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
        //SOFT_INPUT:可見(jiàn)性狀態(tài)softInputMode,請(qǐng)不要改變軟輸入?yún)^(qū)域的狀態(tài)
        public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
        //SOFT_INPUT:用戶(hù)導(dǎo)航(navigate)到你的窗口時(shí)隱藏軟鍵盤(pán)
        public static final int SOFT_INPUT_STATE_HIDDEN = 2;
        //SOFT_INPUT:總是隱藏軟鍵盤(pán)
        public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
        //SOFT_INPUT:用戶(hù)導(dǎo)航(navigate)到你的窗口時(shí)顯示軟鍵盤(pán)
        public static final int SOFT_INPUT_STATE_VISIBLE = 4;
        //SOFT_INPUT:總是顯示軟鍵盤(pán)
        public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
        //SOFT_INPUT:顯示軟鍵盤(pán)時(shí)用于表示window調(diào)整方式的bite的mask
        public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
        //SOFT_INPUT:不指定顯示軟件盤(pán)時(shí),window的調(diào)整方式
        public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
        //SOFT_INPUT:當(dāng)顯示軟鍵盤(pán)時(shí),調(diào)整window內(nèi)的控件大小以便顯示軟鍵盤(pán)
        public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
        //SOFT_INPUT:當(dāng)顯示軟鍵盤(pán)時(shí),調(diào)整window的空白區(qū)域來(lái)顯示軟鍵盤(pán),即使調(diào)整空白區(qū)域,軟鍵盤(pán)還是有可能遮擋一些有內(nèi)容區(qū)域,這時(shí)用戶(hù)就只有退出軟鍵盤(pán)才能看到這些被遮擋區(qū)域并進(jìn)行
        public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
        //SOFT_INPUT:當(dāng)顯示軟鍵盤(pán)時(shí),不調(diào)整window的布局
        public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
        //SOFT_INPUT:用戶(hù)導(dǎo)航(navigate)到了你的window
        public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;

        //軟輸入法模式選項(xiàng)
        public int softInputMode;

        //窗口如何停靠
        public int gravity;
        //水平邊距,容器與widget之間的距離,占容器寬度的百分率
        public float horizontalMargin;
        //縱向邊距
        public float verticalMargin;
        //積極的insets繪圖表面和窗口之間的內(nèi)容
        public final Rect surfaceInsets = new Rect();
        //期望的位圖格式,默認(rèn)為不透明,參考android.graphics.PixelFormat
        public int format;
        //窗口所使用的動(dòng)畫(huà)設(shè)置,它必須是一個(gè)系統(tǒng)資源而不是應(yīng)用程序資源,因?yàn)榇翱诠芾砥鞑荒茉L問(wèn)應(yīng)用程序
        public int windowAnimations;
        //整個(gè)窗口的半透明值,1.0表示不透明,0.0表示全透明
        public float alpha = 1.0f;
        //當(dāng)FLAG_DIM_BEHIND設(shè)置后生效,該變量指示后面的窗口變暗的程度,1.0表示完全不透明,0.0表示沒(méi)有變暗
        public float dimAmount = 1.0f;

        public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f;
        public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;
        public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;
        public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;
        //用來(lái)覆蓋用戶(hù)設(shè)置的屏幕亮度,表示應(yīng)用用戶(hù)設(shè)置的屏幕亮度,從0到1調(diào)整亮度從暗到最亮發(fā)生變化
        public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;

        public static final int ROTATION_ANIMATION_ROTATE = 0;
        public static final int ROTATION_ANIMATION_CROSSFADE = 1;
        public static final int ROTATION_ANIMATION_JUMPCUT = 2;
        //定義出入境動(dòng)畫(huà)在這個(gè)窗口旋轉(zhuǎn)設(shè)備時(shí)使用
        public int rotationAnimation = ROTATION_ANIMATION_ROTATE;

        //窗口的標(biāo)示符
        public IBinder token = null;
        //此窗口所在的包名
        public String packageName = null;
        //屏幕方向
        public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
        //首選的刷新率的窗口
        public float preferredRefreshRate;
        //控制status bar是否顯示
        public int systemUiVisibility;
        //ui能見(jiàn)度所請(qǐng)求的視圖層次結(jié)構(gòu)
        public int subtreeSystemUiVisibility;
        //得到關(guān)于系統(tǒng)ui能見(jiàn)度變化的回調(diào)
        public boolean hasSystemUiListeners;

        public static final int INPUT_FEATURE_DISABLE_POINTER_GESTURES = 0x00000001;
        public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002;
        public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004;
        public int inputFeatures;
        public long userActivityTimeout = -1;

        ......
        public final int copyFrom(LayoutParams o) {
            ......
        }

        ......
        public void scale(float scale) {
            ......
        }

        ......
    }

引用翻譯

Dialog

首先關(guān)于Dialog的原理可以參考我原來(lái)的一篇文章【W(wǎng)indow系列】——Dialog源碼解析,這里面的最后有提到,為什么我們創(chuàng)建Dialog傳入的Context必須是Activity類(lèi)型的,這里我們看下Dialog的創(chuàng)建過(guò)程。

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        ...
        //獲取Activity的WindowManager
        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.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        //設(shè)置windowManger,注意后面兩個(gè)參數(shù)為null
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }

這里假定我們傳入的是Activity類(lèi)型的Context,前面我們有講到

@Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        ...
        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }

這里就會(huì)返回Activity內(nèi)部的WindowManager了,然后Dialog自己創(chuàng)建了Window對(duì)象,也就是說(shuō)Dialog是和Activity共用一個(gè)WindowManager,但Window不同

public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
        setWindowManager(wm, appToken, appName, false);
    }

setWindowManager方法里的入?yún)⒖梢钥吹剑?code>Dialog的這里傳入的token是null。
我們繼續(xù)看Dialogshow方法。

public void show() {
        ...
        onStart();
        mDecor = mWindow.getDecorView();

       ..
        WindowManager.LayoutParams l = mWindow.getAttributes();
       ...
        mWindowManager.addView(mDecor, l);
        mShowing = true;

        sendShowMessage();
    }

看到我們剛才分析到的用于保存token對(duì)象和type類(lèi)型的WindowManager.LayoutParams

Window.java

// The current window attributes.
    private final WindowManager.LayoutParams mWindowAttributes =
        new WindowManager.LayoutParams();

public final WindowManager.LayoutParams getAttributes() {
        return mWindowAttributes;
    }

因?yàn)槲覀儎偛欧治鲋溃?code>Dialog是自己本身創(chuàng)建了一個(gè)新的PhoneWindow,所以可以看到WindowManager.LayoutParams用的是默認(rèn)值。

public LayoutParams() {
            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            type = TYPE_APPLICATION;
            format = PixelFormat.OPAQUE;
        }

總算看到了type對(duì)象,所以我們可以得出一個(gè)結(jié)論:
Dialog是應(yīng)用程序類(lèi)型的Window
再結(jié)合我們上面的結(jié)論:應(yīng)用程序類(lèi)型的parentWindow不為空,并且使用的是parentWindow的token
這里一下就很清晰了,Dialog在執(zhí)行mWindowManager.addView(mDecor, l);方法時(shí),由于Context使用的是Activity,所以WindowManager用的是ActivityWindowManager,而ActivityWindowManger保存了Activitytoken,所以就能正常添加了,如果使用的不是Activity,而是Application,那么就會(huì)由于沒(méi)有token對(duì)象,而拋異常。

Toast

首先關(guān)于Toast的原理可以參考我原來(lái)的一篇文章【W(wǎng)indow系列】——Toast源碼解析
,這里面同樣提到了關(guān)于token的疑問(wèn),所以我們帶著結(jié)論來(lái)看下

TN(String packageName, @Nullable Looper looper) {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            ...
            //設(shè)置為系統(tǒng)類(lèi)型的
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            ...
}
public void handleShow(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            // If a cancel/hide is pending - no need to show - at this point
            // the window token is already invalid and no need to do any work.
            if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
                return;
            }
            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                //context
                Context context = mView.getContext().getApplicationContext();
                String packageName = mView.getContext().getOpPackageName();
                if (context == null) {
                    context = mView.getContext();
                }
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                ...
                    //設(shè)置token
                mParams.token = windowToken;
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                // Since the notification manager service cancels the token right
                // after it notifies us to cancel the toast there is an inherent
                // race and we may attempt to add a window after the token has been
                // invalidated. Let us hedge against that.
                try {
                //利用WindowManager將View加入
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                } catch (WindowManager.BadTokenException e) {
                    /* ignore */
                }
            }
        }

這里可以看到首先在TN對(duì)象的創(chuàng)建方法里面,將WindowManager.LayoutParams設(shè)置為了WindowManager.LayoutParams.TYPE_TOAST系統(tǒng)類(lèi)型,而對(duì)于剛才的結(jié)論可以得到,系統(tǒng)類(lèi)型的token為null也是可以正常顯示的,所以在WindowManager的獲取地方,可以看到這里獲取的是Application類(lèi)型的,也就是WindowManager為新創(chuàng)建的,并且token為null。

Toast的BadTokenException

這里要說(shuō)下,由于Toast的版本差異,導(dǎo)致我們?cè)谝恍┌姹镜腡oast,會(huì)有關(guān)閉權(quán)限通知導(dǎo)致無(wú)法顯示Toast的問(wèn)題,分析原因是在某個(gè)版本,系統(tǒng)源碼對(duì)于WindowManager.LayoutParams.TYPE_TOAST做了權(quán)限控制,所以沒(méi)有系統(tǒng)通知權(quán)限的時(shí)候,Toast也無(wú)法正常顯示,這里有些地方的解決方式是手動(dòng)傳入ToastWindowManager.LayoutParams,不再使用WindowManager.LayoutParams.TYPE_TOAST這樣確實(shí)能避免關(guān)閉通知權(quán)限無(wú)法顯示的問(wèn)題,但是就會(huì)出現(xiàn)幾個(gè)問(wèn)題:
1.Toast由于不是系統(tǒng)類(lèi)型的,所以依賴(lài)于父布局,所以不再支持Activity跳轉(zhuǎn)時(shí)仍然顯示
2.由于Toast變成了不是系統(tǒng)類(lèi)型,依賴(lài)父布局,所以就可能在某些Activity銷(xiāo)毀的生命周期內(nèi)顯示Toast就會(huì)有BadTokenException

PopupWindow

首先關(guān)于PopupWindow的原理可以參考我原來(lái)的一篇文章【W(wǎng)indow系列】——PopupWindow的前世今生
有了前面的經(jīng)驗(yàn),我們知道了分析一個(gè)彈窗類(lèi)型的分析三步:

1.先分析WindowManager是不是Activity公用
2.再分析WindowManager.LayoutParams的type類(lèi)型
3.最后分析WindowManager.LayoutParams的token賦值
所以我們來(lái)看下PopupWindow

public PopupWindow(View contentView, int width, int height, boolean focusable) {
        if (contentView != null) {
            mContext = contentView.getContext();
            //傳入的View的Context類(lèi)型,
            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        }

        setContentView(contentView);
        setWidth(width);
        setHeight(height);
        setFocusable(focusable);
    }

首先可以看到PopupWindow使用的是傳入View的Context,一般我們傳入的都是我們布局里面的某一個(gè)View,所以這里Context對(duì)象就是Activity,所以可以得到是Activity共用的WindowManager

//子布局類(lèi)型
private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
        ...
        //創(chuàng)建布局參數(shù)
        final WindowManager.LayoutParams p =
                createPopupLayoutParams(anchor.getApplicationWindowToken());
        ...
    }
protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
        ...
        p.type = mWindowLayoutType;
        p.token = token;
        ...
        return p;
    }

然后可以看到創(chuàng)建WindowManager.LayoutParams,傳入了依賴(lài)的Viewtoken,也就是parentViewtoken,并且typeWindowManager.LayoutParams.TYPE_APPLICATION_PANEL;子布局類(lèi)型,所以符合預(yù)期,可以正常顯示的。

總結(jié)

WindowManager.LayoutParams中分為三種類(lèi)型

應(yīng)用程序窗口 : type在 FIRST_APPLICATION_WINDOW ~ LAST_APPLICATION_WINDOW 之間
要求:token設(shè)置成Activity的token。
例如:Dialog

子窗口: type在 FIRST_SUB_WINDOW ~ LAST_SUB_WINDOW SubWindows之間
要求:需將token設(shè)置成它所附著宿主窗口的token。
例如:PopupWindow(想要依附在Activity上需要將token設(shè)置成Activity的token)

系統(tǒng)窗口: type值在 FIRST_SYSTEM_WINDOW ~ LAST_SYSTEM_WINDOW之間。
要求:token可以為null,但需要權(quán)限運(yùn)行才能使用
例如: Toast,輸入法等。

分析一個(gè)彈窗類(lèi)型的分析三步:

1.先分析WindowManager是不是Activity公用
2.再分析WindowManager.LayoutParams的type類(lèi)型
3.最后分析WindowManager.LayoutParams的token賦值

相關(guān)文章推薦

1.Android應(yīng)用Activity、Dialog、PopWindow、Toast窗口添加機(jī)制及源碼分析
2.Android窗口機(jī)制(五)最終章:WindowManager.LayoutParams和Token以及其他窗口Dialog,Toast
3.Toast通知欄權(quán)限填坑指南
4.同學(xué),你的系統(tǒng)Toast可能需要修復(fù)一下
5.創(chuàng)建Dialog所需的上下文為什么必須是Activity?
6.WindowManager調(diào)用流程源碼分析
7.Android之Window和彈窗問(wèn)題
8.Toast與Snackbar的那點(diǎn)事

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