AlertDialog cancel() Destroy Window Process Resolution

Let's analyze the AlertDialog cancel() destroy window process today.As mentioned in the previous article, both activity and dialog manage views through windows objects.So we can learn about the activity interface destruction process from the AlertDialog destruction process.

The display of windows in Android system is controlled by Window object and done by ViewRootImpl object. What is the process of cancelling the drawing of windows?This article takes Dialog as an example to illustrate how the Window window cancels drawing.

The Window window's cancel drawing process is placed in the Dialog. In fact, their cancel drawing process is similar. It is very simple to look at the cancel drawing process of Activity after the Dialog's cancel drawing process.

Remember the example of Dialog in our last article?We created an AlertDialog by AlertDialog.Builder and displayed it by clicking on events with a button in the Activity. AlertDialog defines a "know" button that triggers the alertDialog.cancel method. By doing this, our alertDialog is no longer displayed. It is obvious that the cancel method is executedThe logic to cancel drawing is performed, so let's first look at our example core code:

title.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this.getApplication());
                builder.setIcon(R.mipmap.ic_launcher);
                builder.setMessage("this is the content view!!!");
                builder.setTitle("this is the title view!!!");
                builder.setView(R.layout.activity_second);
                builder.setPositiveButton("Got it", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        alertDialog.cannel();
                    }
                });
                alertDialog = builder.create();
                alertDialog.show();
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

The title here is a TextView in our own Activity, displaying an AlertDialog by registering a click event for this TextView, and executing the cancel method of alertDialog by registering a button click event in AlertDialog.

Okay, take a look at the implementation of Dialog's cannel method:

public void cancel() {
        if (!mCanceled && mCancelMessage != null) {
            mCanceled = true;
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mCancelMessage).sendToTarget();
        }
        dismiss();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

You can see that in the body of the method, if the current Dialog is not cancelled and a cancel message is set, Message.obtain(mCancel).sendToTarget() is called, and the sendToTarget method, which has been analyzed here, calls back our registered asynchronous message processing logic:

public void setOnCancelListener(final OnCancelListener listener) {
        if (mCancelAndDismissTaken != null) {
            throw new IllegalStateException(
                    "OnCancelListener is already taken by "
                    + mCancelAndDismissTaken + " and can not be replaced.");
        }
        if (listener != null) {
            mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
        } else {
            mCancelMessage = null;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

You can see that if we set setOnCancelListener when we initialize AlertDialog.Builder, then we will execute asynchronous message processing for mListenersHandler. Okay, let's take a look at the definition of mListenersHandler:

private static final class ListenersHandler extends Handler {
        private WeakReference<DialogInterface> mDialog;

        public ListenersHandler(Dialog dialog) {
            mDialog = new WeakReference<DialogInterface>(dialog);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DISMISS:
                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
                    break;
                case CANCEL:
                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());
                    break;
                case SHOW:
                    ((OnShowListener) msg.obj).onShow(mDialog.get());
                    break;
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Okay, what we call here is the onCancel method of OnCancelListener that is set. That is, when we call the dialog.cancel method, we first determine if dialog calls setOnCancelListener. If set, we first call OnCancelListener's onCancel method, then execute the dismiss method again. If we do not set OnCancelListener for Dialog.Builder, then cancel method and dismThe ISS method is equivalent.

So let's look at the implementation logic of the dismiss method:

public void dismiss() {
        if (Looper.myLooper() == mHandler.getLooper()) {
            dismissDialog();
        } else {
            mHandler.post(mDismissAction);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

You can see that this first determines if the current thread's ooper is the main thread's ooper (since the mHandler was created in the main thread, the mHandler.get Looper returns the ooper object created in the main thread), if so, execute the dismissDialog() method directly, otherwise, send an asynchronous message to the main thread through the mHandler, which is simply a judgmentWhether the front thread is the main thread, if the main thread executes the dismissDialog method or sends asynchronous messages, let's look at the mHandler's handling of asynchronous messages. Since mDismissAction is a Runnable object here, look directly at the definition of mDismissAction:

private final Runnable mDismissAction = new Runnable() {
        public void run() {
            dismissDialog();
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5

Okay, the asynchronous message here is also ultimately the dismissDialog method that is called...

So whether we execute cancel or dismiss, whether we execute in the main thread or in the child thread, the final call is the dismissDialog method, so let's see how dismissDialog executes logic.

void dismissDialog() {
        if (mDecor == null || !mShowing) {
            return;
        }

        if (mWindow.isDestroyed()) {
            Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
            return;
        }

        try {
            mWindowManager.removeViewImmediate(mDecor);
        } finally {
            if (mActionMode != null) {
                mActionMode.finish();
            }
            mDecor = null;
            mWindow.closeAllPanels();
            onStop();
            mShowing = false;

            sendDismissMessage();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

Well, it doesn't seem like there's a lot of code yet. In the body of the method, first determine if the current mDector is empty, or if the current Dialog is displayed, or if it's empty or not, return it directly. That is, our dialog is no longer displayed, so we don't need to go down.

Then we call the mWindow.isDestroyed() method to determine if the Window object has been destroyed or, if it has been destroyed, return it directly and print the error log.

Then we called mWindowManager.removeViewImmediate(mDector), where mDector is the root layout of our Dialog window. Look at the name of this method which should be Dialog's operation to remove the root layout, and see how this method is implemented.We've analyzed in previous articles that mWindowManager here is actually an instance of WindowManagerImpl, so the removeViewImmediate method here should be one in WindowManagerImpl. Let's take a look at its implementation:

@Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }
  • 1
  • 2
  • 3
  • 4

You can see that here it calls the mGlobal.removeView method, where mGlobalis an instance of WindowManagerGlobals, so let's look at the implementation logic of removeView in WIndowManagerGlobals:

public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

You can see that after obtaining the saved mDector component, the removeViewLocked method is called here. Let's look at the implementation logic of this method:

private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

See, we take the ViewRootImpl of the mDector component and call its die method, which implements the destruction process of the Window component.

boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

You can see that there is a call to the doDie method in the body of the method. Looking at the name should be the method that actually does the window s destruction, let's take a look at the implementation of the doDie method:

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

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

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

                    mSurface.release();
                }
            }

            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

You can see that in the body of the method, the checkThread method is invoked first, as described in the drawing process of the Activity, to determine the current thread executing the code and throw an exception if it is not the main thread:

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Following the doDie method, we call the dispatchDetachedFromWindow() method, which destroys member variables, temporary variables, and so on, in Windows.

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

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

        destroyHardwareRenderer();

        setAccessibilityFocus(null, null);

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

        mSurface.release();

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

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

     mDisplayManager.unregisterDisplayListener(mDisplayListener);

        unscheduleTraversals();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

You can see that we invoked the mView.dispatchDetachedFromWindow method in our method, which is designed to detach mView from Windows. We can see the implementation of this method:

void dispatchDetachedFromWindow() {
        AttachInfo info = mAttachInfo;
        if (info != null) {
            int vis = info.mWindowVisibility;
            if (vis != GONE) {
                onWindowVisibilityChanged(GONE);
            }
        }

        onDetachedFromWindow();
        onDetachedFromWindowInternal();

        InputMethodManager imm = InputMethodManager.peekInstance();
        if (imm != null) {
            imm.onViewDetachedFromWindow(this);
        }

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewDetachedFromWindow(this);
            }
        }

        if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
            mAttachInfo.mScrollContainers.remove(this);
            mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
        }

        mAttachInfo = null;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchDetachedFromWindow();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

The onDetachedFromWindow method is an empty callback method. Here we focus on the onDetachedFromWindowInternal method:

protected void onDetachedFromWindowInternal() {
        mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;

        removeUnsetPressCallback();
        removeLongPressCallback();
        removePerformClickCallback();
        removeSendViewScrolledAccessibilityEventCallback();
        stopNestedScroll();

        // Anything that started animating right before detach should already
        // be in its final state when re-attached.
        jumpDrawablesToCurrentState();

        destroyDrawingCache();

        cleanupDraw();
        mCurrentAnimation = null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

The method body of the onDetachedFromWindowInternal method is not very long either, it is some calling functions. Let's take a look at the destropDrawingCache method, which destroys the View's cache Drawing. Let's take a look at the implementation:

public void destroyDrawingCache() {
        if (mDrawingCache != null) {
            mDrawingCache.recycle();
            mDrawingCache = null;
        }
        if (mUnscaledDrawingCache != null) {
            mUnscaledDrawingCache.recycle();
            mUnscaledDrawingCache = null;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Here mDrawingCache is actually a member variable of type Bitmap, and the recycler and null operations invoked here are essentially emptying the bitmap cached after the draw method is executed in the View.

It should be noted here that the final display implementation of our View component is achieved by drawing, and the parameter of our draw method is a Canvas, which is an object of the canvas. Drawing method is to manipulate and display this object, and the Canvas object is able to achieve the effect of display because it has a Bitmap object stored in it and by operationCanvas objects essentially operate on Bitmap objects inside Canvas objects, and the display of View components is achieved through Bitmap here.

The empty bitmap object above is equivalent to the empty display of the View component, which is equivalent to canceling the execution of the draw method of the View, going back to our dispatchDetachedFromWindow method, calling mView = null after executing the mView.dispatchDetachedFromWindow() method, and setting mView here to be empty, so we haveThe execution of View's meature and layouot is cancelled.

When we call the finish method of the activity, we call back the handleDestroyActivity method of the activity Thread. Let's take a look at the implementation of this method:

private void handleDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance) {
        ...
        wm.removeViewImmediate(v);
        ...            
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

You can see that this calls the wm.removeViewImmediate method, which is not the way we just started analyzing the Dialog destroy drawing process?The following logic is detailed so that we can implement the cancel-drawing process of Activity.

Summary:

  • The undrawing process for windows is similar, including Activity, Dialog, and so on.

  • By calling the WindowManager.removeViewImmediate method, start the process of cancelling the drawing of the Window window;

  • Cancel drawing process of Window window, cancel drawing execution by emptying bitmap, and cancel drawing execution by emptying View;

Keywords: Windows Android

Added by BillyMako on Sat, 25 May 2019 19:25:58 +0300