This is the 8th day of my participation in the August More Text Challenge

(1) The relationship between Activity,Window and View

View the rendering process from the Activity’s setContent method (2)

View the rendering process from the setContent method of the Activity (3) and then view the Window

See the rendering process from the setContent method of an AppCompatActivity

In the previous analysis, we knew that the following sections of the DecorView were well understood, but it was not clear how the DecorView was displayed on our screen. So let’s look at how the DecorView and PhoneWindow and Activity are related. Let’s get two things straight:

  1. When the DecorView is drawn to the screen
  2. DecorView how to draw to the screen (addView removeView, upDateViewLayout)
  3. How does an Activity get a Touch event

When the DecorView is drawn to the screen

In the Activity launch process, the performLaunchActivity method that executes the Activity is first created by reflection, and then the Attach method of the Activity is called. In the previous article we saw that the Attach method initializes the PhoneWindow and then executes the lifecycle onCreate, and the setContent method executes after onCreate, which seems to make a lot of sense.

First create an Activity, then create a PhoneWindow for the Activity, execute onCreate, and finally set the XML we wrote to it.

We’re all set, but we can’t see the interface so far because we know that the Activity is really visible when onResume is executed. So the answer to the question of when the DecorView is drawn to the screen is already there, and it needs to be verified in the code.

Those of you who have read my previous articles know that you need to look at the performLifecycleSequence method in the TransactionExecutor to find specific code to execute an Activity in its different lifecycle.

The Activity’s onRemune is in the Activity’s handleResumeActivity method, and we’ll take a look at how it saves PhoneWindow.

android.app.ActivityThread @Override public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { ...... // TODO pushes resumeArgs into the activity for consideration. PerformResumeActivity triggers the activity's onResume lifecycle final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); . r.window = r.activity.getWindow(); // getDecorView View decor = r.window.getdecorview (); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); . AddView (decor, L); . }Copy the code

The key code is just a simple sentence wm. AddView (decor, L);

Find out WindowManagerImpl

The wm is of type ViewManager, and the method to get it is the WindowManager of the Activity. First, let’s figure out what the relationship between Windows Manager and ViewManager is.

Pretend to parse a wave and use ViewManager to receive WindowManager, indicating that WindowManager is either a subclass of ViewManager or implements the ViewManager interface. And you can use wm.addView. So this addView method is defined in ViewManager, not in Windows Manager, otherwise you wouldn’t write it this way.

In fact, there is no need to think so much, each point into the know.

android.view.ViewManager

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

Copy the code
android.view.WindowManager @SystemService(Context.WINDOW_SERVICE) public interface WindowManager extends ViewManager { . }Copy the code

ViewManager is an interface, and there are only three methods in it, but we’re very familiar with them, and we don’t need to talk about the importance of these three methods.

To my surprise, WindowManager is also an interface…… So what’s the implementation of addView? You have to go to getWindowManager to find out.

android.app.Activity ...... private WindowManager mWindowManager; . public WindowManager getWindowManager() { return mWindowManager; }Copy the code

And the assignment operation is in attach

final void attach(......) { mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(mWindowControllerCallback); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); . Set a WindowManager 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()); } fetch WindowManager and assign to mWindowManager mWindowManager = mwindow.getwinDowManager (); }Copy the code

So if you want to know what mWindows Manager is, you have to look at mWindow.getwindowManager. And mwindow.getwinDowManager returns the same line mwindow.setwinDowManager, so go to setWindowManager

android.view.Window ...... private WindowManager mWindowManager; . public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated; if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); Key code mWindowManager} = (WindowManagerImpl wm). CreateLocalWindowManager (this); }... public WindowManager getWindowManager() { return mWindowManager; }Copy the code

The truth comes out

The real object of mWindowManager is WindowManagerImpl

addView

In this section we take a look at the process of adding Windows

It took us so long to figure out wm. AddView (decor, L); What wm is. Let’s see what addView does to our DecorView.

android.view.WindowManagerImpl public final class WindowManagerImpl implements WindowManager { @UnsupportedAppUsage private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); . @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, mContext.getUserId()); }... }Copy the code

WindowManagerImpl is again an empty shell, with most operations being handled by the singleton WindowManagerGlobal class.

android.view.WindowManagerGlobal private final ArrayList<View> mViews = new ArrayList<View>(); private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>(); private final ArraySet<View> mDyingViews = new ArraySet<View>(); public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) { ...... ViewRootImpl root; View panelParentView = null; Synchronized (mLock) {if (view == null) {throw new IllegalArgumentException("view must not be null"); } if (display == null) { throw new IllegalArgumentException("display must not be null"); } if (! (params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; If it is a sub-WindowH also need to adjust the layout parameters if (parentWindow! = null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); }... Create 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 {// pass DecorView to ViewRootImpl root.setView(view, wparams, panelParentView, userId); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; }}}Copy the code

First we saw that Windows ManagerGlobal maintains four collections

  • MViews: Stores all views corresponding to Windows

  • MRoots: Stores the view wrootimpl corresponding to all Windows

  • MParams: Stores layout parameters for all Windows

  • MDyingViews: Stores views that are being deleted

The key code later in addView creates the ViewRootImpl and gives it our DecorView.

android.view.ViewRootImpl public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) { synchronized (this) { if (mView == null) { mView = view; }... Trigger a screen refresh of requestLayout(); Try {final operating to mWindowSession res. = mWindowSession addToDisplayAsUser (mWindow mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mDisplayCutout, inputChannel, mTempInsets, mTempControls); setFrame(mTmpFrame); }... CharSequence counterSuffix = attrs.getTitle(); mSyntheticInputStage = new SyntheticInputStage(); InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage); InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, "aq:native-post-ime:" + counterSuffix); InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage); InputStage imeStage = new ImeInputStage(earlyPostImeStage, "aq:ime:" + counterSuffix); InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage); InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage, "aq:native-pre-ime:" + counterSuffix); . } @Override public void requestLayout() { if (! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }}Copy the code

The setView method does a layout refresh, and the internal scheduleTraversals() is the entry to the View. When this method is called, the View associated with ViewRootImpl also performs the measure – layout-draw operation to ensure that it has been measured and drawn before the View is added to the Window and displayed on the screen. At this point, ViewRootImpl completes adding the View to the Window. The addToDisplayAsUser method of mWindowSession is then called to complete the process of adding the Window. The actual place of execution inside is in WMS (WindowManagerService).

How do View events feed back into the Activity

I don’t quite understand the input event handling code, but I’ve seen it somewhere before, so I have a bit of an impression of the mechanism that uses pipes and the system’s underlying communication. Here is a set of input channels. Because the occurrence of a touch screen event is definitely initiated by the screen, and then through a series of calculation processing of the driver layer and finally through Socket cross-process notification of the Android Framework layer. In fact, I didn’t plan to go further when I saw this section at first, because I knew that my ability was almost there. However, I clicked on it with curiosity to see what ViewPostImeInputStage was. It turned out to be something unexpected.

android.view.ViewRootImpl final class ViewPostImeInputStage extends InputStage { public ViewPostImeInputStage(InputStage  next) { super(next); } @Override protected int onProcess(QueuedInputEvent q) { if (q.mEvent instanceof KeyEvent) { return processKeyEvent(q);  } else { final int source = q.mEvent.getSource(); if ((source & InputDevice.SOURCE_CLASS_POINTER) ! ProcessPointerEvent (q) {return processPointerEvent(q); } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) ! = 0) { return processTrackballEvent(q); } else { return processGenericMotionEvent(q); }}}}Copy the code
private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; mAttachInfo.mUnbufferedDispatchRequested = false; mAttachInfo.mHandlingPointerEvent = true; boolean handled = mView.dispatchPointerEvent(event); . return handled ? FINISH_HANDLED : FORWARD; }Copy the code

Notice the passing of the arguments and you see that this mView is actually our DecorView from the very beginning. But dispatchPointerEvent is defined in the View.

android.view.View public final boolean dispatchPointerEvent(MotionEvent event) { if (event.isTouchEvent()) { return dispatchTouchEvent(event); } else { return dispatchGenericMotionEvent(event); }}Copy the code

Returns the dispatchTouchEvent and dispatchGenericMotionEvent execution. So go to DecorView

com.android.internal.policy.DecorView @Override public boolean dispatchTouchEvent(MotionEvent ev) { final Window.Callback cb = mWindow.getCallback(); return cb ! = null && ! mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); } @Override public boolean dispatchTrackballEvent(MotionEvent ev) { final Window.Callback cb = mWindow.getCallback(); return cb ! = null && ! mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev); } @Override public boolean dispatchGenericMotionEvent(MotionEvent ev) { final Window.Callback cb = mWindow.getCallback(); return cb ! = null && ! mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev); }Copy the code

We find that either method ends up getting a callback cb from mWindow and calling the thing back out. Mwindow.setcallback (this); mwindow.setcallback (this); The type of argument the method receives is an interface Callback inside the Window that our Activity implements. So mWindow.setcallback sets the Activity in, so this cb is our Activity, and so the DecorView event is passed to the Activity

removeView

android.view.WindowManagerGlobal @UnsupportedAppUsage public void removeView(View view, boolean immediate) { if (view == null) { throw new IllegalArgumentException("view must not be null"); 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); }}Copy the code

The delete process page for Windows is in Windows ManagerGlobal. Find the index to remove the View and pass it to removeViewLocked.

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

        if (root != null) {
            root.getImeFocusController().onWindowDismissed();
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }
Copy the code

RemoveViewLocked also uses ViewRootImpl to delete from inside. Get (index).getView(), View View = root.getView(); The View returned by ViewRootImpl’s getView is actually our top-level View, passed in from the setView method above.

Take a look at view rotimpl’s die method

Params:immediate -- True, do now if not in traversal. False, put on queue and do later. Returns:True, request has been queued. False, request has been completed. 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(mTag, "Attempting to destroy the window while drawing! \n" + " window=" + this + ", title=" + mWindowAttributes.getTitle()); } mHandler.sendEmptyMessage(MSG_DIE); return true; }Copy the code

The immediates parameter indicates whether to delete immediately. Returning false indicates that the deletion was completed, and returning true indicates that it was added to the queue to be deleted. Look at the end of removeViewLocked and add the return true to the list of mDyingViews to delete. In case of immediate deletion, doDie is executed; in case of asynchronous deletion, a message is sent; the internal Handle of viewrotimPL receives the message and doDie is executed.

Now it seems that doDie is where the deletion actually takes place.

void doDie() { checkThread(); if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface); synchronized (this) { if (mRemoved) { return; } mRemoved = true; If (mAdded) {key code dispatchDetachedFromWindow (); }... Global singleton WindowManagerGlobal also perform WindowManagerGlobal. The corresponding method getInstance () doRemoveView (this); }Copy the code
Inside the void dispatchDetachedFromWindow () {complete view to remove mView. DispatchDetachedFromWindow (); Mwindowsession.remove (mWindow); } catch (RemoteException e) {} }Copy the code

Inside the dispatchDetachedFromWindow method by using the approach of View dispatchDetachedFromWindow complete View to delete, at the same time done through mWindowSession Window to delete

updateViewLayout

android.view.WindowManagerGlobal public void updateViewLayout(View view, ViewGroup.LayoutParams params) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } if (! (params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; Update View parameter view.setLayoutParams(wparams); synchronized (mLock) { int index = findViewLocked(view, true); ViewRootImpl root = mRoots.get(index); mParams.remove(index); mParams.add(index, wparams); Update the ViewRootImpl parameter root.setLayoutParams(wparams, false); }}Copy the code

The internal thing to do with updateViewLayout is relatively simple: first update the View’s parameters, then update the View View’s parameters.

  • ScheduleTraversals are triggered internally by setLayoutParams to rearrange the View, and once this happens, the relayoutWindow method is executed to trigger the WMS to update the Window View. I won’t post the code, too much, attach the call path.
  • ScheduleTraversals ->mTraversalRunnable (TraversalRunnable) -> doduleTraversal ->performTraversals->relayoutWindow-> Internally execute mWindowSe Ssion. Relayout notifies WMS to update the Window

conclusion

  1. DecorView executes in the Activity’s handleResumeActivity method via wm.addView. The execution time is a little bit after onResume
  2. A DecorView exists in a PhoneWindow. The ViewRootImpl is responsible for adding and removing DecorView updates, while the WMS is responsible for adding and removing updates for a PhoneWindow
  3. PhoneWindow events are called back to the Activity through the Callback interface

It would be inaccurate to say that a DecorView adds or removes updates from a DecorView because the DecorView is also a View, and the ViewRootImpl adds or removes updates from a View and then communicates internally with the WMS via mWindowSession. The Window is added, updated, and removed