Solution to android.view.WindowManager$BadTokenException

About Token

android.view.WindowManager$BadTokenException: Unable to add window

         Do you often encounter this bug? The usual solution is to judge and protect the activity

if(!isDestroyed() && !isFinishing())

         However, it will be found in a few days that the crash will still be reported on the log platform, and the protection has not worked. To solve this problem completely, let's first understand what this token is?

/frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java

ActivityRecord(ActivityManagerService _service, ProcessRecord _caller, int _launchedFromPid,
            int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType,
            ActivityInfo aInfo, Configuration _configuration,
            ActivityRecord _resultTo, String _resultWho, int _reqCode,
            boolean _componentSpecified, boolean _rootVoiceInteraction,
            ActivityStackSupervisor supervisor, ActivityOptions options,
            ActivityRecord sourceRecord) {
        service = _service;
        appToken = new Token(this, _intent);
        info = aInfo;
        launchedFromPid = _launchedFromPid;
        launchedFromUid = _launchedFromUid;
        launchedFromPackage = _launchedFromPackage;
        userId = UserHandle.getUserId(aInfo.applicationInfo.uid);
        intent = _intent;
        shortComponentName = _intent.getComponent().flattenToShortString();
        resolvedType = _resolvedType;
        componentSpecified = _componentSpecified;
        rootVoiceInteraction = _rootVoiceInteraction;
        mLastReportedConfiguration = new MergedConfiguration(_configuration);
        resultTo = _resultTo;
        resultWho = _resultWho;

         It literally means identification, which is similar to the token of the communication interface with the server. It is used for identification, authentication, etc. here, the appToken is initialized in the ActivityRecord constructor to ensure the unique identification of activity in AMS. The following windows token in WMS is also created through this token.

/frameworks/base/services/core/java/com/android/server/am/ActivityStack.java

TaskRecord task = null;
        if (!newTask) {
            // If starting in an existing task, find where that is...
            boolean startIt = true;
            for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
                task = mTaskHistory.get(taskNdx);
                if (task.getTopActivity() == null) {
                    // All activities in task are finishing.
                    continue;
                }
                if (task == r.task) {
                    // Here it is!  Now, if this is not yet visible to the
                    // user, then just add it without starting; it will
                    // get started when the user navigates back to it.
                    if (!startIt) {
                        if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to task "
                                + task, new RuntimeException("here").fillInStackTrace());
                        task.addActivityToTop(r);
                        r.putInHistory();
                      //The token of ActivityRecord is passed into WindowManager
                        mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
                                r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
                                (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0,
                                r.userId, r.info.configChanges, task.voiceSession != null,
                                r.mLaunchTaskBehind);
                        if (VALIDATE_TOKENS) {
                            validateAppTokensLocked();
                        }
                        ActivityOptions.abort(options);
                        return;
                    }

In WMS:

synchronized(mWindowMap) {
            AppWindowToken atoken = findAppWindowToken(token.asBinder());
            if (atoken != null) {
                Slog.w(TAG, "Attempted to add existing app token: " + token);
                return;
            }
  //Instantiate AppWindowToken
            atoken = new AppWindowToken(this, token, voiceInteraction);
            atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
            atoken.appFullscreen = fullscreen;
            atoken.showForAllUsers = showForAllUsers;
            atoken.requestedOrientation = requestedOrientation;
            atoken.layoutConfigChanges = (configChanges &
                    (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0;
            atoken.mLaunchTaskBehind = launchTaskBehind;
            if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + atoken
                    + " to stack=" + stackId + " task=" + taskId + " at " + addPos);

            Task task = mTaskIdToTask.get(taskId);
            if (task == null) {
                task = createTaskLocked(taskId, stackId, userId, atoken);
            }
            task.addAppToken(addPos, atoken);
            mTokenMap.put(token.asBinder(), atoken);

            // Application tokens start out hidden.
            atoken.hidden = true;
            atoken.hiddenRequested = true;

            //dump();
        }

         In WMS, it will find out whether a WindowToken has been created according to the incoming appToken. If not, it will instantiate a WindowToken and use the token as the ID.

         AMS manages activities through ActivityRecord, TaskRecord and ActivityStack. ActivityRecord is created with the start of the activity and destroyed with the termination of the activity.

Destroy process of Activity:

boolean skipDestroy = false;

						//Call destroy lifecycle
            try {
                if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                        DestroyActivityItem.obtain(finishing, configChangeFlags));
            } catch (Exception e) {
                // We can just ignore exceptions here...  if the process has crashed, our death
                // notification will clean things up.
                if (finishing) {
                    removeFromHistory(reason + " exceptionInScheduleDestroy");
                    removedFromHistory = true;
                    skipDestroy = true;
                }
            }

            nowVisible = false;

            // If the activity is finishing, we need to wait on removing it from the list to give it
            // a chance to do its cleanup.  During that time it may make calls back with its token
            // so we need to be able to find it on the list and so we don't want to remove it from
            // the list yet.  Otherwise, we can just immediately put it in the destroyed state since
            // we are not removing it from the list.
            if (finishing && !skipDestroy) {
                if (DEBUG_STATES) {
                    Slog.v(TAG_STATES, "Moving to DESTROYING: " + this + " (destroy requested)");
                }
                setState(DESTROYING,
                        "destroyActivityLocked. finishing and not skipping destroy");
              // Start timeout thread
                mAtmService.mH.postDelayed(mDestroyTimeoutRunnable, DESTROY_TIMEOUT);
            } else {
                if (DEBUG_STATES) {
                    Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (destroy skipped)");
                }
                setState(DESTROYED,
                        "destroyActivityLocked. not finishing or skipping destroy");
                if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during destroy for activity " + this);
                app = null;
            }

         In the destroimimmediately method of ActivityRecord, we can see that the activity life cycle is called through scheduleTransaction, and a timeout thread mDestroyTimeoutRunnable is started with a timeout of 10s. If the call of ondestroy life cycle is not completed within 10s, AWS will mark the activity as deleted and the token will be removed.

         How to solve our problems?

Solution 1: check the message queue

         Since there are deleted messages in the message queue_ The activity is not executed until it times out, which means that the message is still in the queue. If you can get the message, does it mean that it is badtoken at this time and pop-up logic cannot be used

         This method requires hook to process MessageQueue and judge the message type inside. Due to the limitation of system version, reflection failure is easy to occur.

Solution 2: handle messages with the help of Handler

         The basic process is shown in the figure above. Destroy messenger is in the messagequeue. If the Token is removed after timeout, then do the pop-up logic, and badToken will be generated. Can you add a pop-up message in the message queue by sending a message? The pop-up message must be after destroy messenger, After the Destroy Messsage is processed, the mmactivity. Isdestroyed() will be set to true. The specific solution code is as follows:

if (activity != null&&!activity.isFinishing()&& !activity.isDestroyed()) {
   // post a message through the main thread
   activity.runOnUiThread(new Runnable() {
      @Override
      public void run() {
        // Judge again
        if (activity != null&&!activity.isFinishing()&& !activity.isDestroyed()) {
           //Pop up logic, or addview
        }
      }
  });
}

summary

         In fact, it is difficult to reproduce a Bug. Through analysis, it can be known that the application usually retreats to the background for a long time, which may lead to too many stop activities or changes in the horizontal and vertical screens. In short, the server AMS will eventually take the initiative to send a Destory request to the current Activity and set the time-space timeout. If the Activity completes its normal processing within 10s, The Activity is destroyed. If it cannot be completed, the Token will be forcibly removed through the timeout mechanism. At this time, if the UI thread is triggered to addView, the BadToken will be caused because the server AMS forcibly removes the WindowToken of WMS.

        

Keywords: Android

Added by bamse on Fri, 08 Oct 2021 10:06:53 +0300