Source Code Analysis for the Creation Process of Android Window--Activity, Dialog and Toast

From the above analysis, we can see that View is the way of view presentation in Android, but View can not exist independently. It needs to be attached to the abstract concept of Window, that is, where there is an interface, there is Window. Line surface is a deep understanding of the creation process of Window through Activity, Dialog and Toast.How on earth.

1. Creation of Window in Activity

Before introducing the process of creating Window in Activity, we first need to understand the process of starting Activity. After that, we will write a special article to introduce the process of starting Activity. Here is a brief introduction or the source code first:

......
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }
......
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
......

The starting process of an Activity is complex, and it is ultimately accomplished by the performLaunchActivity method in ActivityThread. See the source code above, it can be seen that the performLaunchActivity obtains an instance of the Activity through the class loader.The attach method of the Activity is then invoked to be a series of context environment variables on which its association runs.

In Activity's attach method,

  • The system creates the Window object to which the Activity belongs and sets the callback interface, where the Window object is actually PhoneWindow.
  • Initialize various parameters for the Activity, such as mUiThread, etc.
  • Setting up WindowManager for PhoneWindow actually sets up WindowManagerImpl:
    The following figure gives a part of the source code. Interested students still look at the source code directly.
......
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if (voiceInteractor != null) {
            if (lastNonConfigurationInstances != null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                        Looper.myLooper());
            }
        }
        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();
        mCurrentConfig = config;\
        mWindow.setColorMode(info.colorMode);
......

Since Activity implements the allback interface of Window, it calls back the method of Activity when Window receives a change in external state.There are many methods in the Callback interface, but several are familiar to us, such as onAttachedToWindow, onDetachedFromWindow, dispatchTouchEvent, and so on.

    public interface Callback {
        public boolean dispatchKeyEvent(KeyEvent event);
        public boolean dispatchKeyShortcutEvent(KeyEvent event);
        public boolean dispatchTouchEvent(MotionEvent event);
        public boolean dispatchTrackballEvent(MotionEvent event);
        public boolean dispatchGenericMotionEvent(MotionEvent event);
......

Window has been created here, but as mentioned in the previous article, only Window is actually an empty shelf and you need View to really see it.How do Activity's views get added to Windows?Here's a very familiar method, setContentView.

......
    /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     * @param layoutResID Resource ID to be inflated.
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
......

It is clear from the Activity's setContentView method that getWindow() actually returns the PhoneWindow created above, that is, it calls PhoneWindow's setContentView, in which DecorView is created and the layout view is populated.Let's look at the source code for PhoneWindow's setContentView.

 @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

From the source code above we can clearly see that there are probably several steps:

  1. If there is no DecorView, it needs to be created, otherwise all Views in the mContentParent will be removed.
  2. Add View to DecorView's mContentParent.
  3. The onContentChanged method that calls back an Activity notifies the Activity view that has changed.

After these steps, DecorView is created and initialized successfully.Activity's layout file has also been successfully added to DecorView's mContentParent, but at this time DecorView has not been officially added to Windows by WindowManager.There is a need to understand the concept of Window correctly. Window represents more of an abstract set of functions. Although Window was created as early as the attach method in Activity, at this time because DecorView was not recognized by WindowManager, Window cannot provide specific functions because itIt is not possible to receive input from outside.In the handleResumeActivity method of ActivityThread, the onResume method of Activity is called first, and then the makeVisible() of Activity is called. It is in the makeVisible method that DecorView actually completes the process of adding and displaying the two processes, so that the view of the activity can be seen by the user.

 void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

2. Creation of Window in Dialog

Dialog's Window s creation process is very similar to Activity's, and there are generally a few steps below.
-1. Create a Window
Dialog's Window was also created by PhoneWindow, and the rest is very similar to Activity.Look at the source code below.

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == ResourceId.ID_NULL) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mListenersHandler = new ListenersHandler(this);
    }

-2. Initialize DecorView and add Dialog's interface to DecorView
This process is similar to Activity in that it adds the specified layout through Window.

    /**
     * Set the screen content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the screen.
     * @param layoutResID Resource ID to be inflated.
     */
    public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
    }

-3. Add DecorView to Window s and display
In Dialog's show method, DecorView is added to Windows through WindowManager with the following source code

......
 mDecor = mWindow.getDecorView();
 ......
 mWindowManager.addView(mDecor, l);
 mShowing = true;
......

As you can see from the three steps above, Dialog's Windows creation process is very similar to Activity, with little difference.When Dialog is closed, DecorView is removed through WindowManager.
The common Dialog differs in that you must use the Activity's Context, which will cause errors if you use the Application's Context.This is because a normal Dialog requires token, and a token is typically an activity. If you must use the ApplicationContext at this time, you need Dialog to be the Windows of the system. You need to start setting the Window type. TYPE_SYSTEM_OVERLAY is generally chosen to specify that the Windows type is the System Window.

3 Toast's Window s Creation Process

Toast is slightly more complex than Dialog.First Toast is also based on Window s, but because Toast cancels this function regularly, the system uses Handler.There are two types of IPC processes inside Toast, the first is that Toast accesses the Notification Manager Service, and the second is that Notification-Manager Service calls back the TN interface in Toast.Some knowledge about IPC can be moved IPC Modes in Android .For ease of description, the Notification Manager Service is referred to as NMS for short.
Toast is a system Window whose internal views are specified in two ways: the default style of the system and the setView method to specify a custom View, which in any case corresponds to the mNextView, an internal member of Toast's View type.Toast provides show and cancel to show and hide Toast, respectively. Inside them is an IPC process. Let's look at the show method and cancel method.

     /**
     * Show the view for the specified duration.
     */
    public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }
        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;
        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }
/**
     * Close the view if it's showing, or don't show it if it isn't showing yet.
     * You do not normally have to call this.  Normally view will disappear on its own
     * after the appropriate duration.
     */
    public void cancel() {
        mTN.cancel();
    }

As you can see from the code above, both displaying and hiding Toast need to be done through NMS. Since NMS runs in the process of the system, it can only be displayed and hidden through remote calls.It is important to note that TN is a Binder class that calls back methods in TN across processes when the NMS processes the display or hide requests for Toast during IPC between Toast and NMS, at which point since TN runs in the Binder thread pool, it needs to be switched to the current thread through Handler.The current thread here refers to the thread where the Toast request is sent.Note that because a Handler is used here, this means that Toast cannot pop up in a thread without a Looper because a Handler needs a Looper to switch threads.
From the source show method above, we can see that the display of Toast calls the enqueueToast method of NMS.The enqueueToast method has three parameters: the current application package name of pkg, the tn remote callback, and the mDuration display time.
enqueueToast first encapsulates the Toast request as a ToastRecord object and adds it to a queue named mToastQueue.MToastQueue is actually an ArrayList.For non-system applications, a maximum of 50 ToastRecords can exist simultaneously in mToastQueue to prevent DOS (DenialofService).If you don't do this, imagine that if we pop Toast out continuously through a large number of loops, this will result in other applications not having the opportunity to pop Toast out, then for other applications'Toast requests, the system will behave as a denial of service, which is Denial of Service Attacks Meaning, this means is often used in network attacks.

                        // Limit the number of toasts that any given package except the android
                        // package can enqueue.  Prevents DOS attacks and deals with leaks.
                        if (!isSystemToast) {
                            int count = 0;
                            final int N = mToastQueue.size();
                            for (int i=0; i<N; i++) {
                                 final ToastRecord r = mToastQueue.get(i);
                                 if (r.pkg.equals(pkg)) {
                                     count++;
                                     if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                         Slog.e(TAG, "Package has already posted " + count
                                                + " toasts. Not showing more. Package=" + pkg);
                                         return;
                                     }
                                 }
                            }
                        }
                    // If it's at index 0, it's the current toast.  It doesn't matter if it's
                    // new or just been updated.  Call back and tell it to show itself.
                    // If the callback fails, this will remove it from the list, so don't
                    // assume that it's valid after this.
                    if (index == 0) {
                        showNextToastLocked();
                    }

Normally, an application cannot reach the upper limit. When ToastRecord is added to mToastQueue, the NMS displays the current Toast using the showNextToastLocked method.The code below is well understood and it is important to note that the display of Toast is done by the callback of ToastRecord, which is actually a remote Binder of the TN object in Toast. Accessing the TN through the callback is done across processes, and the method in the TN that is ultimately called runs in the originating ToastIn the Binder thread pool of the requested application.

 @GuardedBy("mToastQueue")
    void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
            try {
                record.callback.show(record.token);
                scheduleTimeoutLocked(record);
                return;
            } catch (RemoteException e) {
                Slog.w(TAG, "Object died trying to show notification " + record.callback
                        + " in package " + record.pkg);
                // remove it from the list and let the process die
                int index = mToastQueue.indexOf(record);
                if (index >= 0) {
                    mToastQueue.remove(index);
                }
                keepProcessAliveIfNeededLocked(record.pid);
                if (mToastQueue.size() > 0) {
                    record = mToastQueue.get(0);
                } else {
                    record = null;
                }
            }
        }
    }

From the source above, you can see that after Toast displays, a delay message is sent through scheduleTimeoutLocked, depending, of course, on the time set at the beginning.Look at the code specifically:

 @GuardedBy("mToastQueue")
    private void scheduleTimeoutLocked(ToastRecord r)
    {
        mHandler.removeCallbacksAndMessages(r);
        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
        mHandler.sendMessageDelayed(m, delay);
    }

The above LONG_DELAY is 3.5s and SHORT_DELAY is 2s.After the delay, NMS hides Toast by cancelToastLocked and removes it from mToastQueue. We can see this process clearly by looking at the source code. Here is the cancelToastLocked method. You can see if mToastQueue has Toast and then calls showNextToastLocked after removing Toast.

 @GuardedBy("mToastQueue")
    void cancelToastLocked(int index) {
        ToastRecord record = mToastQueue.get(index);
        try {
            record.callback.hide();
        } catch (RemoteException e) {
            Slog.w(TAG, "Object died trying to hide notification " + record.callback
                    + " in package " + record.pkg);
            // don't worry about this, we're about to remove it from
            // the list anyway
        }

        ToastRecord lastToast = mToastQueue.remove(index);
        mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY);

        keepProcessAliveIfNeededLocked(record.pid);
        if (mToastQueue.size() > 0) {
            // Show the next one. If the callback fails, this will remove
            // it from the list, so don't assume that the list hasn't changed
            // after this point.
            showNextToastLocked();
        }
    }

From the analysis above, we understand that the display and hiding of Toast is actually achieved by the TN class in Toast, which has two methods, show and hide, respectively, for the display and hide of Toast.Since these two methods are invoked by NMS in a cross-process manner, they run in the Binder thread pool.In order to switch the execution environment to the thread where the Toast request is located, Handler is used inside of them, looking specifically at the source code:

......
           mHandler = new Handler(looper, null) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case SHOW: {
                            IBinder token = (IBinder) msg.obj;
                            handleShow(token);
                            break;
                        }
                        case HIDE: {
                            handleHide();
                            // Don't do this in handleHide() because it is also invoked by
                            // handleShow()
                            mNextView = null;
                            break;
                        }
                        case CANCEL: {
                            handleHide();
                            // Don't do this in handleHide() because it is also invoked by
                            // handleShow()
                            mNextView = null;
                            try {
                                getService().cancelToast(mPackageName, TN.this);
                            } catch (RemoteException e) {
                            }
                            break;
                        }
                    }
                }
            };
......
         /**
         * schedule handleShow into the right thread
         */
        @Override
        public void show(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
        }
        /**
         * schedule handleHide into the right thread
         */
        @Override
        public void hide() {
            if (localLOGV) Log.v(TAG, "HIDE: " + this);
            mHandler.obtainMessage(HIDE).sendToTarget();
        }

In the above code, mShow and mHide are two Runnable that call handleShow and handleHide methods internally.Thus, handleShow and handleHide are the places where Toast is really displayed and hidden.TN's handleShow adds Toast's view to Window s.The code is as follows.

......
                // 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 {
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                } catch (WindowManager.BadTokenException e) {
                    /* ignore */
                }
......

The handleShow code snippet above clearly shows that mWM added Toast.The source code for handleHide is as follows:

       public void handleHide() {
            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
            if (mView != null) {
                // note: checking parent() just to make sure the view has
                // been added...  i have seen cases where we get here when
                // the view isn't yet added, so let's try not to crash.
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeViewImmediate(mView);
                }

                mView = null;
            }
        }

The creation of Window for Toast is finished here. I believe you should have a new understanding after reading it.
Of course, there are many other components implemented through Windows, such as PopWindow, Menu Bar and Status Bar, which are no longer covered here, or that sentence. Looking at the source code, the comments are also more detailed.

Reference: Ren Yugang. Exploration of Android Development Art. Electronic Industry Press

Tags: Android Windows Java REST

Posted on Mon, 02 Sep 2019 20:46:38 -0700 by iwarlord