【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)過了10個月了~中途因為換了工作胸蛛,所以一直在忙工作的事氛悬,后面會慢慢恢復(fù)博客的進度雷恃,博客還是會堅持的!:⒏铩!
本次博客還是接著上次博客的介紹势誊,這篇博客應(yīng)該是對Window系列的收尾撩笆,前三篇博客都提到了關(guān)于Token變量,這篇博客就來分析一下Token在Window的使用中的重要性抒线。

源碼分析

addView的主體流程

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

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

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

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

可以看到Google也會有ifelse~~~户秤,這里其實是做了特殊判斷,如果是WINDOW_SERVICE逮矛,那么就會返回Activity中的mWindowManager鸡号。那么我們可以看一下Activity中這個對象是怎么創(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ù)來看一下Window中的方法鲸伴,這里其實也可以看到Window的實現(xiàn)類其實是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);
    }

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

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

好吧汞窗,又通過其他類代理了姓赤,那么繼續(xù)看一下mGlobal對象是什么。

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

這里就簡單多了仲吏,可以看到mGlobal是一個WindowManagerGlobal對象不铆,并且這個對象是一個final對象。在WindowManagerGlobal對象中我們終于看到了addView的實現(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;
            }
        }
    }

這里其實有三個重要的步驟

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

其中第一個步驟parentWindow.adjustLayoutParamsForSubWindow(wparams);通過方法名我們可以簡單的看出來這個方法的作用是通過parent給子Window設(shè)置param裹唆,這里先不詳細展開方法的內(nèi)部(后面會分析到)誓斥。
而第二個和第三個步驟,熟悉Activity的View的繪制流程的其實應(yīng)該對于這兩個方法很熟悉许帐,這個其實就是我們View繪制起始的地方劳坑。由于本篇博客不是詳細講解View的繪制流程的,所以這里不對于這個方法詳細展開舞吭,后面如果有必要會專門寫博客分析這個地方(因為網(wǎng)上這類的博客實在太多了)泡垃,這里先說一下結(jié)論吧,我們調(diào)用setView是怎么能顯示到頁面中的呢羡鸥,在setView中會調(diào)用會有這樣一個代碼蔑穴。

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

這里會通過IPC調(diào)用mWindowSession.addToDisplay,簡單說一下調(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];
        //校驗權(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)注到兩個地方衷旅,第一個是這里會先判斷權(quán)限捐腿,第二就是這里會判斷token,如果不滿足則直接返回柿顶,這里的返回則還會通過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);
                }

                ...
            }
        }
    }

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

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

Token的創(chuàng)建流程

看了上面的流程我們大體上有了一個概念野芒,就是我們經(jīng)常遇到的BadTokenException其實就是和我們的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方法我們會看到這里Activity保存了傳入的token對象涝登,并且還設(shè)置給了PhoneWindow
然后在調(diào)用WindowManagerGlobaladdView方法的時候、

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

這里有一個parentWindow的概念

    1. 如果是應(yīng)用程序窗口的話效诅,這個parentWindow就是activity的window
    1. 如果是子窗口的話,這個parentWindow就是activity的window
    1. 如果是系統(tǒng)窗口的話趟济,那個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)類型的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;
            }
            ...
    }

可以看到這里分別針對不同類型的type來進行判斷乱投,給WindowManager.LayoutParams設(shè)置token,而設(shè)置完tokenparam后顷编,這個token就會一直帶到我們剛才的流程中戚炫,進行判斷。那么這里我們就來分別看一下幾種類型的Windowtoken媳纬。

public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable {
        //窗口的絕對XY位置双肤,需要考慮gravity屬性
        public int x;
        public int y;
        //在橫縱方向上為相關(guān)的View預(yù)留多少擴展像素,如果是0則此view不能被拉伸钮惠,其他情況下擴展像素被widget均分
        public float horizontalWeight;
        public float verticalWeight;
        //窗口類型
        //有3種主要類型如下:
        //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)用程序,使用時需要有特殊權(quán)限撰糠,它是特定的系統(tǒng)功能才能使用酥馍;
        public int type;

        //WindowType:開始應(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來指定窗口屬于誰
        public static final int TYPE_APPLICATION        = 2;
        //WindowType:應(yīng)用程序啟動時所顯示的窗口旨袒,應(yīng)用自己不要使用這種類型,它被系統(tǒng)用來顯示一些信息遮斥,直到應(yīng)用程序可以開啟自己的窗口為止
        public static final int TYPE_APPLICATION_STARTING = 3;
        //WindowType:結(jié)束應(yīng)用程序窗口
        public static final int LAST_APPLICATION_WINDOW = 99;

        //WindowType:SubWindows子窗口峦失,子窗口的Z序和坐標空間都依賴于他們的宿主窗口
        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:對話框,類似于面板窗口较屿,繪制類似于頂層窗口隧魄,而不是宿主的子窗口
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
        //WindowType:媒體信息卓练,顯示在媒體層和程序窗口之間,需要實現(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)欄襟企,只能有一個狀態(tài)欄,位于屏幕頂端狮含,其他窗口都位于它下方
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
        //WindowType:搜索欄顽悼,只能有一個搜索欄,位于屏幕上方
        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)容之上解孙,此窗口不能獲得輸入焦點坑填,否則影響鎖屏
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
        //WindowType:電話優(yōu)先,當(dāng)鎖屏?xí)r顯示弛姜,此窗口不能獲得輸入焦點脐瑰,否則影響鎖屏
        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
        //WindowType:系統(tǒng)對話框
        public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;
        //WindowType:鎖屏?xí)r顯示的對話框
        public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;
        //WindowType:系統(tǒng)內(nèi)部錯誤提示,顯示于所有內(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)部輸入法對話框,顯示于當(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)欄的滑動面板
        public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
        //WindowType:安全系統(tǒng)覆蓋窗口中剩,這些窗戶必須不帶輸入焦點忌穿,否則會干擾鍵盤
        public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
        //WindowType:拖放偽窗口,只有一個阻力層(最多)结啼,它被放置在所有其他窗口上面
        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:鼠標指針
        public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
        //WindowType:導(dǎo)航欄(有別于狀態(tài)欄時)
        public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;
        //WindowType:音量級別的覆蓋對話框掠剑,顯示當(dāng)用戶更改系統(tǒng)音量大小
        public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;
        //WindowType:起機進度框,在一切之上
        public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
        //WindowType:假窗郊愧,消費導(dǎo)航欄隱藏時觸摸事件
        public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22;
        //WindowType:夢想(屏保)窗口朴译,略高于鍵盤
        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背后真正的窗戶
        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:放大窗口覆蓋属铁,用于突出顯示的放大部分可訪問性放大時啟用
        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訪問眠寿,或者硬件加速的內(nèi)存區(qū)域
        public static final int MEMORY_TYPE_HARDWARE = 1;
        //MemoryType:窗口緩沖位于可被圖形加速器訪問的區(qū)域
        public static final int MEMORY_TYPE_GPU = 2;
        //MemoryType:窗口緩沖不擁有自己的緩沖區(qū),不能被鎖定焦蘑,緩沖區(qū)由本地方法提供
        public static final int MEMORY_TYPE_PUSH_BUFFERS = 3;

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

        //Flag:當(dāng)該window對用戶可見的時候,允許鎖屏
        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不能獲得焦點,這樣用戶快就不能向該window發(fā)送按鍵事
        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
        //Flag:讓該window不接受觸摸屏事件
        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
        //Flag:即使在該window在可獲得焦點情況下狡逢,依舊把該window之外的任何event發(fā)送到該window之后的其他window
        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
        //Flag:當(dāng)手機處于睡眠狀態(tài)時宁舰,如果屏幕被按下,那么該window將第一個收到
        public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
        //Flag:當(dāng)該window對用戶可見時奢浑,讓設(shè)備屏幕處于高亮(bright)狀態(tài)
        public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
        //Flag:讓window占滿整個手機屏幕蛮艰,不留任何邊界
        public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
        //Flag:window大小不再不受手機屏幕大小限制,即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:開啟抖動(dithering)
        public static final int FLAG_DITHER             = 0x00001000;
        //Flag:當(dāng)該window在進行顯示的時候雀彼,不允許截屏
        public static final int FLAG_SECURE             = 0x00002000;
        //Flag:一個特殊模式的布局參數(shù)用于執(zhí)行擴展表面合成時到屏幕上
        public static final int FLAG_SCALED             = 0x00004000;
        //Flag:用于windows時,經(jīng)常會使用屏幕用戶持有反對他們的臉,它將積極過濾事件流,以防止意外按在這種情況下,可能不需要為特定的窗口,在檢測到這樣一個事件流時,應(yīng)用程序?qū)⒔邮杖∠\動事件表明,這樣應(yīng)用程序可以處理這相應(yīng)地采取任何行動的事件,直到手指釋放
        public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;
        //Flag:一個特殊的選項只用于結(jié)合FLAG_LAYOUT_IN_SC
        public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
        //Flag:轉(zhuǎn)化的狀態(tài)FLAG_NOT_FOCUSABLE對這個窗口當(dāng)前如何進行交互的方法
        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)鎖屏的時候徊哑,顯示該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被顯示的時候仍律,系統(tǒng)將把它當(dāng)做一個用戶活動事件,以點亮手機屏幕
        public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
        //Flag:消失鍵盤
        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:對該window進行硬件加速善涨,該flag必須在Activity或Dialog的Content View之前進行設(shè)置
        public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
        //Flag:讓window占滿整個手機屏幕窒盐,不留任何邊界
        public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
        //Flag:請求一個半透明的狀態(tài)欄背景以最小的系統(tǒng)提供保護
        public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
        //Flag:請求一個半透明的導(dǎo)航欄背景以最小的系統(tǒng)提供保護
        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;

        //行為選項標記
        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;

        //私有的行為選項標記
        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:用于描述軟鍵盤顯示規(guī)則的bite的mask
        public static final int SOFT_INPUT_MASK_STATE = 0x0f;
        //SOFT_INPUT:沒有軟鍵盤顯示的約定規(guī)則
        public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
        //SOFT_INPUT:可見性狀態(tài)softInputMode,請不要改變軟輸入?yún)^(qū)域的狀態(tài)
        public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
        //SOFT_INPUT:用戶導(dǎo)航(navigate)到你的窗口時隱藏軟鍵盤
        public static final int SOFT_INPUT_STATE_HIDDEN = 2;
        //SOFT_INPUT:總是隱藏軟鍵盤
        public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
        //SOFT_INPUT:用戶導(dǎo)航(navigate)到你的窗口時顯示軟鍵盤
        public static final int SOFT_INPUT_STATE_VISIBLE = 4;
        //SOFT_INPUT:總是顯示軟鍵盤
        public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
        //SOFT_INPUT:顯示軟鍵盤時用于表示window調(diào)整方式的bite的mask
        public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
        //SOFT_INPUT:不指定顯示軟件盤時钢拧,window的調(diào)整方式
        public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
        //SOFT_INPUT:當(dāng)顯示軟鍵盤時蟹漓,調(diào)整window內(nèi)的控件大小以便顯示軟鍵盤
        public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
        //SOFT_INPUT:當(dāng)顯示軟鍵盤時,調(diào)整window的空白區(qū)域來顯示軟鍵盤源内,即使調(diào)整空白區(qū)域葡粒,軟鍵盤還是有可能遮擋一些有內(nèi)容區(qū)域,這時用戶就只有退出軟鍵盤才能看到這些被遮擋區(qū)域并進行
        public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
        //SOFT_INPUT:當(dāng)顯示軟鍵盤時膜钓,不調(diào)整window的布局
        public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
        //SOFT_INPUT:用戶導(dǎo)航(navigate)到了你的window
        public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;

        //軟輸入法模式選項
        public int softInputMode;

        //窗口如何退越唬靠
        public int gravity;
        //水平邊距,容器與widget之間的距離颂斜,占容器寬度的百分率
        public float horizontalMargin;
        //縱向邊距
        public float verticalMargin;
        //積極的insets繪圖表面和窗口之間的內(nèi)容
        public final Rect surfaceInsets = new Rect();
        //期望的位圖格式夫壁,默認為不透明,參考android.graphics.PixelFormat
        public int format;
        //窗口所使用的動畫設(shè)置沃疮,它必須是一個系統(tǒng)資源而不是應(yīng)用程序資源盒让,因為窗口管理器不能訪問應(yīng)用程序
        public int windowAnimations;
        //整個窗口的半透明值,1.0表示不透明司蔬,0.0表示全透明
        public float alpha = 1.0f;
        //當(dāng)FLAG_DIM_BEHIND設(shè)置后生效邑茄,該變量指示后面的窗口變暗的程度,1.0表示完全不透明俊啼,0.0表示沒有變暗
        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;
        //用來覆蓋用戶設(shè)置的屏幕亮度肺缕,表示應(yīng)用用戶設(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;
        //定義出入境動畫在這個窗口旋轉(zhuǎn)設(shè)備時使用
        public int rotationAnimation = ROTATION_ANIMATION_ROTATE;

        //窗口的標示符
        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é)構(gòu)
        public int subtreeSystemUiVisibility;
        //得到關(guān)于系統(tǒng)ui能見度變化的回調(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的原理可以參考我原來的一篇文章【W(wǎng)indow系列】——Dialog源碼解析,這里面的最后有提到,為什么我們創(chuàng)建Dialog傳入的Context必須是Activity類型的搓谆,這里我們看下Dialog的創(chuàng)建過程炒辉。

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,注意后面兩個參數(shù)為null
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }

這里假定我們傳入的是Activity類型的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);
    }

這里就會返回Activity內(nèi)部的WindowManager了黔寇,然后Dialog自己創(chuàng)建了Window對象,也就是說Dialog是和Activity共用一個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對象和type類型的WindowManager.LayoutParams憋飞。

Window.java

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

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

因為我們剛才分析知道,Dialog是自己本身創(chuàng)建了一個新的PhoneWindow,所以可以看到WindowManager.LayoutParams用的是默認值姆吭。

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

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

Toast

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

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)類型的
            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對象的創(chuàng)建方法里面,將WindowManager.LayoutParams設(shè)置為了WindowManager.LayoutParams.TYPE_TOAST系統(tǒng)類型撑螺,而對于剛才的結(jié)論可以得到佩谣,系統(tǒng)類型的token為null也是可以正常顯示的,所以在WindowManager的獲取地方实蓬,可以看到這里獲取的是Application類型的茸俭,也就是WindowManager為新創(chuàng)建的,并且token為null安皱。

Toast的BadTokenException

這里要說下调鬓,由于Toast的版本差異,導(dǎo)致我們在一些版本的Toast酌伊,會有關(guān)閉權(quán)限通知導(dǎo)致無法顯示Toast的問題腾窝,分析原因是在某個版本缀踪,系統(tǒng)源碼對于WindowManager.LayoutParams.TYPE_TOAST做了權(quán)限控制,所以沒有系統(tǒng)通知權(quán)限的時候虹脯,Toast也無法正常顯示驴娃,這里有些地方的解決方式是手動傳入ToastWindowManager.LayoutParams,不再使用WindowManager.LayoutParams.TYPE_TOAST這樣確實能避免關(guān)閉通知權(quán)限無法顯示的問題循集,但是就會出現(xiàn)幾個問題:
1.Toast由于不是系統(tǒng)類型的唇敞,所以依賴于父布局,所以不再支持Activity跳轉(zhuǎn)時仍然顯示
2.由于Toast變成了不是系統(tǒng)類型咒彤,依賴父布局疆柔,所以就可能在某些Activity銷毀的生命周期內(nèi)顯示Toast就會有BadTokenException

PopupWindow

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

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

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

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

首先可以看到PopupWindow使用的是傳入View的Context旷档,一般我們傳入的都是我們布局里面的某一個View,所以這里Context對象就是Activity歇拆,所以可以得到是Activity共用的WindowManager

//子布局類型
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,傳入了依賴的Viewtoken鞋屈,也就是parentViewtoken,并且typeWindowManager.LayoutParams.TYPE_APPLICATION_PANEL;子布局類型故觅,所以符合預(yù)期谐区,可以正常顯示的。

總結(jié)

WindowManager.LayoutParams中分為三種類型

應(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)限運行才能使用
例如: Toast,輸入法等灭返。

分析一個彈窗類型的分析三步:

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

相關(guān)文章推薦

1.Android應(yīng)用Activity盗迟、Dialog、PopWindow熙含、Toast窗口添加機制及源碼分析
2.Android窗口機制(五)最終章: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和彈窗問題
8.Toast與Snackbar的那點事

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邮弹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蚓聘,更是在濱河造成了極大的恐慌腌乡,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,294評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夜牡,死亡現(xiàn)場離奇詭異与纽,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,493評論 3 385
  • 文/潘曉璐 我一進店門急迂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來影所,“玉大人勾给,你說我怎么就攤上這事钧汹。” “怎么了弃鸦?”我有些...
    開封第一講書人閱讀 157,790評論 0 348
  • 文/不壞的土叔 我叫張陵听盖,是天一觀的道長胀溺。 經(jīng)常有香客問我,道長皆看,這世上最難降的妖魔是什么仓坞? 我笑而不...
    開封第一講書人閱讀 56,595評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮腰吟,結(jié)果婚禮上无埃,老公的妹妹穿的比我還像新娘。我一直安慰自己毛雇,他們只是感情好嫉称,可當(dāng)我...
    茶點故事閱讀 65,718評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著灵疮,像睡著了一般织阅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上震捣,一...
    開封第一講書人閱讀 49,906評論 1 290
  • 那天荔棉,我揣著相機與錄音,去河邊找鬼蒿赢。 笑死润樱,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的羡棵。 我是一名探鬼主播壹若,決...
    沈念sama閱讀 39,053評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼皂冰!你這毒婦竟也來了店展?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,797評論 0 268
  • 序言:老撾萬榮一對情侶失蹤秃流,失蹤者是張志新(化名)和其女友劉穎壁查,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剔应,經(jīng)...
    沈念sama閱讀 44,250評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡睡腿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,570評論 2 327
  • 正文 我和宋清朗相戀三年语御,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片席怪。...
    茶點故事閱讀 38,711評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡应闯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挂捻,到底是詐尸還是另有隱情碉纺,我是刑警寧澤,帶...
    沈念sama閱讀 34,388評論 4 332
  • 正文 年R本政府宣布刻撒,位于F島的核電站骨田,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏声怔。R本人自食惡果不足惜态贤,卻給世界環(huán)境...
    茶點故事閱讀 40,018評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望醋火。 院中可真熱鬧悠汽,春花似錦、人聲如沸芥驳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,796評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兆旬。三九已至假抄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丽猬,已是汗流浹背宿饱。 一陣腳步聲響...
    開封第一講書人閱讀 32,023評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宝鼓,地道東北人。 一個月前我還...
    沈念sama閱讀 46,461評論 2 360
  • 正文 我出身青樓巴刻,卻偏偏與公主長得像愚铡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子胡陪,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,595評論 2 350