preface

Window, Activity and View are often used, but the relationship among the three is still not systematically clarified. Today, let’s start sorting out the relationship among the three:

  • Window: An abstract base class for top-level Window appearance and behavior policies. The only implementation is the PhoneWindow class.
  • Activity: One of four components that provides an interface for users to click and swipe.
  • View: Represents the basic building block of user interface components, UI components.

Source: Android SDK 30

Activity.setContentView

Not sure where to start, let’s start with setContentView. It’s essential to compare the newly created Android project Activity and setContentView. Remember to look at setContentView for an Activity rather than AppCompatActivity

SetContentView:

    public void setContentView(@LayoutRes int layoutResID) {
        // Here's the thing
        getWindow().setContentView(layoutResID);
        // Create and set the ActionBar,
        initWindowDecorActionBar();
    }
Copy the code

GetWindow? So soon? That’s easy. Let’s keep watching.

Activity.getWindow

public Window getWindow(a) {
        return mWindow;
}
Copy the code

We thought we had found a big fish, but obviously the Activity does almost nothing and passes the operation directly to a Window. GetWindow returns the Activity’s global variable mWindow, which is of Window type. So when is it assigned?

Activity.attach()

After searching the entire Activity source code, I finally found it in the Attach method. Let’s look at the code first.

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, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        // The key is to instantiate PhoneWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this); ./ / call setWindowManagermWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) ! =0); . }Copy the code

Window: In fact, there is only one implementation class for Window in the entire Android system, which is PhoneWindow. PhoneWindow: Android-specific Window.

Next, call the setWindowManager method to pass system WindowManager to PhoneWindow.

WindowManager manages Windows. When it comes to managing Windows, it is necessary to add, update, and delete them. Here we refer to them collectively as Window operations. Windows operations are ultimately handled by WMS.

Window.setWindowManager

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            // Obtain WMS through Binder mechanism
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        / / comment
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }
Copy the code

Note: This method simply creates the WindowManagerImpl object again, and the WindowManager is actually associated with the Window.

PhoneWindow.setContentView

The Activity passes the setContentView operation to the PhoneWindow. Here’s how it works:

@Override
    public void setContentView(int layoutResID) {
        // Note: When the theme properties, etc., crystallize, you can set the function content conversion during the window decoration installation.
        // Do not check functionality until this happens.
        if (mContentParent == null) {
            / / comment 1
            installDecor();
        } else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            / / comment 2
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if(cb ! =null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

Copy the code

PhoneWindow.installDecor

  private void installDecor(a) {
        mForceDecorInstall = false;
        // Initialize mDecor
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if(! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! =0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); }}else {
            mDecor.setWindow(this);
        }
        // Initialize mContentParent
        if (mContentParent == null) { mContentParent = generateLayout(mDecor); . }}Copy the code

Note 1: Call installDecor to initialize DecorView and mContentParent.

Note 2: MLayoutinflater.inflate (layoutResID, mContentParent); Expands the new view hierarchy from the specified XML resource. The layout passed in by calling setContentView is added to the mContentParent.

As you can see, PhoneWindow has a DecorView (actually a FrameLayout) by default and a mContentParent (ViewGroup) by default in the DecorView. Our own implementation of the layout is added to the mContentParent, so after passing through setContentView, the View relationship inside PhoneWindow looks like this:

So far the PhoneWindow has just created a DecorView and filled it with the layoutId layout that we passed into the Activity, but the DecorView has not yet established any connection with the Activity. It’s not drawn to the interface. So when is the DecorView drawn on the screen?

The Activity is not visible until onCreate, and the content of the Activity is visible on the screen only after onResume is executed. The reason for this is that the onCreate phase only initializes what the Activity needs to display, whereas the onResume phase (when the interface interacts with the user, Call the ActivityThread’s handleResumeActivity method) to actually draw the PhoneWindow DecorView onto the screen.

ActivityThread.handleResumeActivity

In the ActivityThread’s handleResumeActivity, the addView method of WindowManager is called to add the DecorView to the WMS(WindowManagerService). As follows:

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {... ViewManager wm = a.getWindowManager(); .if (a.mVisibleFromClient) {
                if(! a.mWindowAdded) { a.mWindowAdded =true;
                    / / comment
                    wm.addView(decor, l);
                } else{ a.onWindowAttributesChanged(l); }}... }Copy the code

WindowManger addView results have two:

The DecorView is rendered and drawn to display on the screen; A DecorView can receive screen touch events.

WindowManager.addView

ViewManager is an interface, WindowManager is an interface that implements ViewManager, and WindowManager ImpL implements WindowManager, just to sort out the hierarchy of this method, Move on to the AddView method in Windows ManagerImpl

WindowManagerImpl.addView

    @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 addView is also an empty shell, it calls the WindowManagerGlobal addView method.

WindowManagerGlobal.addView

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {... ViewRootImpl root; View panelParentView =null;

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

            / / comment 1
            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // Do this last because it sends a message to start the operation
            try {
                / / comment 2
                root.setView(view, wparams, panelParentView, userId);
            } catch(RuntimeException e) { ... }}}Copy the code

Note 1: WindowMangerGlobal is a singleton in the addView method that creates one of the most critical ViewRootImpl objects.

Note 2: Then add the view to the WMS via the root.setView method.

ViewRootImpl.setView

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) { mView = view; .int res; /* = WindowManagerImpl.ADD_OKAY; * /
                / / comment 1
                requestLayout();
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    inputChannel = newInputChannel(); } mForceDecorViewVisibility = (mWindowAttributes.privateFlags & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) ! =0;
                try{.../ / comment 2
                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);
                    setFrame(mTmpFrame);
                } catch (RemoteException e) {
                ...
                } finally {
                    if(restore) { attrs.restore(); }}}}Copy the code

Note 1: RequestLayout is the operation that refreshes the layout. The View associated with ViewRootImpl also executes measure -> Layout -> draw to make sure that before the View is added to the Window and displayed on the screen, The measurement and drawing operations have been completed.

Note 2: Call the addToDisplay method of mWindowSession to add the View to the WMS.

Where does mWindowSession come from? He is a new RootViewlmpl object is introduced to come in, call WindowManagerGlobal. GetWindowSession (generation).

new ViewRootImpl(context,display);

    public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),false);
    }
Copy the code

WindowManagerGlobal.getWindowSession()

WindowSession is a singleton object in Windows ManagerGlobal, initialized as follows:

    public static IWindowSession getWindowSession(a) {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    / / comment
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) { ValueAnimator.setDurationScale(scale); }}); }catch (RemoteException e) {
                    throwe.rethrowFromSystemServer(); }}returnsWindowSession; }}Copy the code

Note 1: sWindowSession is actually of type IWindowSession, which is a Binder type. The real implementation class is the Session in the System process. Use AIDL to get the Session object in the System process.

Session.addToDisplay

@Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
                outInsetsState, outActiveControls, UserHandle.getUserId(mUid));
    }
Copy the code

return mService.addWindow(…) ; The mService is WMS. At this point, the Window has been successfully passed to the WMS. The rest of the work is transferred to the WMS in the system process for the final addition.

Back in the Activity

A sign of addView’s success is that it can receive touch screen events. Through the analysis of setContentView process, it can be seen that the operation of adding a View is essentially the overall operation of PhoneWindow, and WMS is in charge behind it. Activities, on the other hand, never feel engaged. But we also know that when a Touch event occurs, the Touch event is first passed into the Activity and then delivered to the ViewGroup or View in the layout (see Touch event distribution). So how do Touch events get passed to an Activity?

In addition to calling IWindowSession to add a View across processes, another important action in the ViewRootImpl setView method is to set the handling of input events:

ViewRootImpl.setView

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {.. res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mDisplayCutout, inputChannel, mTempInsets, mTempControls);// Note 1: Set the input pipe.
          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); . }Copy the code

Note 1: SET up a series of input channels. A touch event is initiated by the screen, and after a series of optimization calculations by the driver layer, the Android Framework layer is notified across the Socket process (actually WMS). Finally, the screen touch event is sent to the input pipe in the code.

These input pipes are actually a linked list structure. When a screen touch event reaches its ViewPostImeInputState, it is processed by onProcess, as shown below:

ViewRootImpl.ViewPostImeInputStage

 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) ! =0) {
                    return processPointerEvent(q);
                } else if((source & InputDevice.SOURCE_CLASS_TRACKBALL) ! =0) {
                    return processTrackballEvent(q);
                } else {
                    returnprocessGenericMotionEvent(q); }}}private int processPointerEvent(QueuedInputEvent q) {
            finalMotionEvent event = (MotionEvent)q.mEvent; .boolean handled = mView.dispatchPointerEvent(event);
            maybeUpdatePointerIcon(event);
            maybeUpdateTooltip(event);
            mAttachInfo.mHandlingPointerEvent = false; .returnhandled ? FINISH_HANDLED : FORWARD; }}Copy the code

ProcessPointerEvent in ViewPostImeInputStage don’t get me wrong. You can see that in onProcess we end up calling the dispatchPointerEvent method of an mView, which is actually a DecorView in PhoneWindow, DispatchPointerEvent is implemented by view.java as follows:

View.dispatchPointerEvent

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

DecorView.dispatchTouchEvent

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        returncb ! =null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

Copy the code

The cb.dispatchTouchEvent(EV) method in window. Callback is called.

Activity.attach

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, IBinder assistToken) {... mWindow =new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        //this:Activity
        mWindow.setCallback(this); . }Copy the code

The Activity passes itself to the PhoneWindow and then looks at the Activity’s dispatchTouchEvent.

Activity.dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            // This method is a user interaction called whenever a keystroke, touch, or trackball event is dispatched to the Activity.
            // Only for ACTION_DOWN judgment
            onUserInteraction();
        }
        / / return true
        if (getWindow().superDispatchTouchEvent(ev)) {
            . / / the Activity dispatchTouchEvent () returns true, the method to an end.
            // The click event stops passing down & the event passing process ends
            return true;
        }
        return onTouchEvent(ev);
    }
Copy the code

PhoneWindow.superDispatchTouchEvent

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
Copy the code

DecorView.superDispatchTouchEvent

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
Copy the code

The Touch event just goes around in the Activity and ends up back in the PhoneWindow DecorView for processing. All that remains is to pass the event layers from the DecorView to the inner child views

ViewGroup. dispatchTouchEvent

   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {}Copy the code

I’m not going to do too much repetition here. For those interested, take a look at the event distribution mechanism.

summary

Through the process of setContentView, the relationship between Activity, Window and View is analyzed. On the surface, the Activity has a low degree of participation in the whole process. The Activity holds the object of Window, and the operations of View adding and deleting on Window are managed by WindowManager. WindowManager, in turn, uses the Binder mechanism to get the WMS mapping, which actually displays the View on the screen.

Relationship among the three:

  1. An Activity has a window, that is, a PhoneWindow object, and you call Attach in the Activity to create a PhoneWindow.
  2. There is a DecorView in the PhoneWindow, and the Activity fills this DecorView with a layout when calling setContentView.
  3. There is only one Windows ManagerGlobal object in an application process, because it is static in view global PL.
  4. Each PhoneWindow corresponds to a ViewRootImple object.
  5. WindowMangerGlobal adds the window by calling the setView method of ViewRootImpl.
  6. Call removeAllView() on the ViewGroup to remove all views
  7. The setView method of ViewRootImpl does two main things: render the View (requestLayout) and receive touch events.

Phase to recommend

OkHttp for Android in 10 minutes

Android RecyclerView drawing process and Recycler cache

Android RecyclerView simple use

Android Glide cache mechanism and source code