[Android] understand why dialog cannot be displayed in the application from the perspective of source code

Time:2022-5-6

First, write a demo:

[Android] understand why dialog cannot be displayed in the application from the perspective of source code

image.png

After running, the following errors will be reported:

[Android] understand why dialog cannot be displayed in the application from the perspective of source code

image.png

How did this mistake come about, the so-calledtoken null is not validWhat is the token in? In this article, we will analyze the source code.

1. Error tracking

First, follow the steps of the source code to track why this error is reported, starting with dialog #show

public void show() {
    ...
    mWindowManager.addView(mDecor, l);
    ...
   
}

The addview method of mwindowmanager called here. Its formal parameter mdecor is the decorview of the dialog we resolved, and l is WindowManager Layoutparams property; The concrete of mwindowmanager is the real windowmanagerimpl.

Windowmanagerimpl calls windowmanagerglobal#addview to continue:

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

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

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

Here, the viewrootimpl is created and the viewrootimpl#setview method is executed:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            ....
            try {
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } catch (RemoteException e) {
            } finally {
                
            }
            ...
            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?");
                        ...
                }
                throw new RuntimeException(
                        "Unable to add window -- unknown error code " + res);
            }
            ...
        }
    }
}

In this method, we find the place where the exception is thrown, when the value of res is windowmanagerglobal ADD_ BAD_ APP_ Token or windowmanagerglobal ADD_ BAD_ SUBWINDOW_ When token, the error at the beginning of the article will be thrown.
This res isIWindowSession#addToDisplayMethod. Iwindowsession is a binder interface, which is responsible for the communication between viewrootimpl and windowmanagerservice and is obtained when the viewrootimpl object is created. hereIWindowSession#addToDisplayFinally, the windowmanagerservice#addwindow method will be called:

// com.android.server.wm.WindowManagerService
public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
    ...
    synchronized(mWindowMap) {
        ...
        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;
            }
        }
        WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
        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;
            }
            ...
        }
        ...
    return res;
}

Through this method, you can determine that the method returns windowmanagerglobal ADD_ BAD_ APP_ Token or windowmanagerglobal ADD_ BAD_ SUBWINDOW_ The condition of token, that is, when the type of window is a child window, if parentwindow is empty, windowmanagerglobal will be returned ADD_ BAD_ SUBWINDOW_ Token, when the window type is application_ When windows, when the corresponding windowtoken is empty, it will return windowmanagerglobal ADD_ BAD_ APP_ TOKEN

2. What is windowtoken

In the last part of the first chapter, we introduce a new concept: windowtoken, which is closely related to the judgment of this exception. Where was it created? What is the role?

Take a look at its acquisition method:

    WindowToken getWindowToken(IBinder binder) {
        return mTokenMap.get(binder);
    }

Let’s first look at the inheritance system of appwindowtoken:

[Android] understand why dialog cannot be displayed in the application from the perspective of source code

image.png

Review the startup process of an activity. During the startup process of an activity, the activitystack#startactivitylocked method will be called:

final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
        boolean newTask, boolean keepCurTransition, ActivityOptions options) {
   ....
    TaskRecord task = null;
    if (!newTask) {
        boolean startIt = true;
        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
            task = mTaskHistory.get(taskNdx);
            if (task.getTopActivity() == null) {
                continue;
            }
            if (task == rTask) {
                if (!startIt) {
                    if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to task "
                            + task, new RuntimeException("here").fillInStackTrace());
                    //Zhangyulong generate windowcontainer object
                    r.createWindowContainer();
                    ActivityOptions.abort(options);
                    return;
                }
                break;
            } else if (task.numFullscreen > 0) {
                startIt = false;
            }
        }
    }
    ....
}

In this method, the activityrecord#createwindowcontainer method will be called. In this method, an instance of appwindowcontainer will be created, and then an instance of appwindowcontainercontroller will be created. In appwindowcontainercontroller, an instance of appwindowtoken will be created. When creating an instance, the construction method of appwindowtoken will be called. From the inheritance diagram of appwindowtoken in the above figure, we can know that the parent class of appwindowtoken is windowtoken, and its construction method is as follows:

    // com.android.server.wm.WindowToken
    WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
            DisplayContent dc, boolean ownerCanManageAppTokens) {
        mService = service;
        token = _token;
        windowType = type;
        mPersistOnEmpty = persistOnEmpty;
        mOwnerCanManageAppTokens = ownerCanManageAppTokens;
        onDisplayChanged(dc);
    }

Take a look at the ondisplaychanged method:

     // com.android.server.wm.onDisplayChanged
    void onDisplayChanged(DisplayContent dc) {
        dc.reParentWindowToken(this);
        ...
    }

Displaycontent#reparentwindowtoken will add the windowtoken to a map called mtokenmap, which is defined asHashMap<IBinder, WindowToken> mTokenMap. the ibinder object as the key is the apptoken object we created in the activity startup process. In other words, each apptoken will correspond to an appwindowtoken. A simple example is shown below:

[Android] understand why dialog cannot be displayed in the application from the perspective of source code

image.png

3. Root causes of problems

To explore the root cause of this error, we only need to find and confirm the values of window type and apptoken when the exception occurs.

The type of window is easy to confirm. When creating the window of dialog, its corresponding type is generatedWindowManager.LayoutParams, the type of window corresponding to dialog has been determined in its construction method:

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

The situation of apptoken is a little complicated. We must first determine where the apptoken is obtained.

Let’s first look at the construction method of dialog:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
    ...
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    ...
}

This windowmanger is the windowmanger we use to execute addview in the show method.

We can start the dialog and windowdialog in different ways.

Let’s first look at activity:

It is also the construction method of dialog. The actual type of this context is the object of activity, which overrides the getsystemservice method:

    @Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }

        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }

Therefore, in the dialog construction method, we actually get the windowmnager defined in the activity. Let’s look at the assignment of this windowmnager. This part of logic is in the activity #attach method:

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) {
    ...

    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());
    }
    mWindowManager = mWindow.getWindowManager();
    ...
}

This windowmnager is the WindowManager of its corresponding window. When the WindowManager of its corresponding window is created, a parentwindow is passed in:

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

In the show process of dialog, because the WindowManager used contains parentwindow, the following logic will be triggered in the windownagerglobal#addview process:

 if (parentWindow != null) {
      parentWindow.adjustLayoutParamsForSubWindow(wparams);
}

Because the type of wParam is type_ Application, so window #adjustlayoutparamsforsubwindow will call the following logic:

        if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
            if ((curTitle == null || curTitle.length() == 0)
                    && mAppName != null) {
                wp.setTitle(mAppName);
            }

This mapptoken is the token in the activity.

As for the token in activity, interested friends can see the article I wrote before
AMS source code analysis (I) activity life cycle management

After analyzing the activity, let’s continue to see what dialog shows in the application:

Or the construction method of dialog:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
    ...
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    ...
}

The application does not overload the context #getsystemservice, so the WindowManager of the context is obtained here, and the windowmnager of the context does not contain parentwindow. Therefore, when the token is finally used to obtain appwindowtoken, a null value will be obtained, and then combined with the window type of dialog, that is, type_ Application, the following judgment will be triggered:

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

Finally, an exception is thrown.

Recommended Today

Golang influxdb basic operation

Infixdb basic operation Basic operation connect [[email protected] ~]# influx -precision rfc3339 Connected to http://localhost:8086 version 1.8.0 InfluxDB shell version: 1.8.0 > precisionThe parameter indicates the format and precision of any returned timestamp. In the above example,rfc3339Yes, let influxdb returnRFC339Timestamp of format (yyyy-mm-ddthh: mm: SS. Nnnnnnnz). validate logon > auth username: icms password: [email protected] Operation database […]