More than a year ago, I thought I had some understanding of View add display logic, but later found that I only understand some superficial. After more than a year of working with the Android and Java foundations, it’s time to look at the added display in DecorView.

View drawing series:

  • DecorView and ViewRootImpl for Android View drawing process

  • Android View drawing process Measure process details (1)

  • Android View drawing process Layout and Draw process details (2)

  • Android View event distribution principle analysis

An introduction to

In Android, an Activity exists as the carrier of an application. It represents a complete user interface and provides a window to draw various views. When an Activity starts, we set up a content view using the setContentView method. This content view is the interface the user sees. So how are views and activities related?

The diagram above shows the relationship between View and Activity. First, explain the roles of some classes in the diagram and their relationships:

  • Activity: There is a PhoneWindow for each Activity.
  • PhoneWindow: This class inherits from the Window class and is a concrete implementation of the Window class. Furthermore, the class contains a DecorView object that is the root View of all application Windows.
  • DecorView is the root container of an application window, which is essentially a FrameLayout. DecorView has a single child, which is a vertical LinearLayout containing two child elements, the TitleView (container for ActionBar) and the ContentView (container for window content).
  • ContentView: is a FrameLayout (Android.r.D.C. Tent), and our usual setContentView is its child View.
  • WindowManager: is an interface, inside the common methods are: add View, update View and delete View. It is mainly used to manage Windows. The WindowManager concrete implementation class is WindowManagerImpl. Eventually, Windows Manager ImpL hands over the business to Windows ManagerGlobal.
  • WindowManagerService (WMS) : Manages the creation, update, deletion, and display sequence of app Windows. Run in system_server process.

ViewRootImpl: Has an instance of a DecorView through which to control DecorView rendering. An internal class W of ViewRootImpl implements the IWindow interface. The IWindow interface is used by WMS. WSM finally executes the corresponding methods in W by calling some methods of IWindow and communicating with Binder. Similarly, ViewRootImpl calls some of the Session methods of WMS via IWindowSession. The Session class inherits from iWindowSession. Stub, where each application process has a unique Session object to communicate with WMS.

The creation of a DecorView

Starting with the code in the Mainactivity, setContentView is called;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}
Copy the code

This method is a method of the parent AppCompatActivity and will eventually call the setContentView method of AppCompatDelegateImpl:

// AppCompatDelegateImpl  
public void setContentView(int resId) {
        this.ensureSubDecor();
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
        contentParent.removeAllViews();
        LayoutInflater.from(this.mContext).inflate(resId, contentParent);
        this.mOriginalWindowCallback.onContentChanged();
    }
Copy the code

EnsureSubDecor, which literally means creating subDecorView, is themed and covered below. Once created, get the contentParent from it and add the ID XML layout passed in from the activity. Note, however, that the removeAllViews() method is called to ensure that there is no interference from other child views.

private void ensureSubDecor() { if (! this.mSubDecorInstalled) { this.mSubDecor = this.createSubDecor(); . }... }Copy the code

Finally, createSubDecor() is called to look at the specific code logic:

Private ViewGroup createSubDecor() {// 1. Actionbar TypedArray, such as a = this. MContext. ObtainStyledAttributes (styleable. AppCompatTheme); if (! a.hasValue(styleable.AppCompatTheme_windowActionBar)) { a.recycle(); throw new IllegalStateException("You need to use a Theme.AppCompat theme (or descendant) with this activity."); } else { if (a.getBoolean(styleable.AppCompatTheme_windowNoTitle, false)) { this.requestWindowFeature(1); } else if (a.getBoolean(styleable.AppCompatTheme_windowActionBar, false)) { this.requestWindowFeature(108); } if (a.getBoolean(styleable.AppCompatTheme_windowActionBarOverlay, false)) { this.requestWindowFeature(109); } if (a.getBoolean(styleable.AppCompatTheme_windowActionModeOverlay, false)) { this.requestWindowFeature(10); } this.mIsFloating = a.getBoolean(styleable.AppCompatTheme_android_windowIsFloating, false); a.recycle(); / / 2, to ensure priority initialization DecorView enclosing mWindow. GetDecorView (); LayoutInflater inflater = LayoutInflater.from(this.mContext); ViewGroup subDecor = null; // 3. Initializes subDecor according to different Settings if (! this.mWindowNoTitle) { if (this.mIsFloating) { subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null); this.mHasActionBar = this.mOverlayActionBar = false; } else if (this.mHasActionBar) { TypedValue outValue = new TypedValue(); this.mContext.getTheme().resolveAttribute(attr.actionBarTheme, outValue, true); Object themedContext; if (outValue.resourceId ! = 0) { themedContext = new ContextThemeWrapper(this.mContext, outValue.resourceId); } else { themedContext = this.mContext; } subDecor = (ViewGroup)LayoutInflater.from((Context)themedContext).inflate(layout.abc_screen_toolbar, (ViewGroup)null); this.mDecorContentParent = (DecorContentParent)subDecor.findViewById(id.decor_content_parent); this.mDecorContentParent.setWindowCallback(this.getWindowCallback()); if (this.mOverlayActionBar) { this.mDecorContentParent.initFeature(109); } if (this.mFeatureProgress) { this.mDecorContentParent.initFeature(2); } if (this.mFeatureIndeterminateProgress) { this.mDecorContentParent.initFeature(5); } } } else { if (this.mOverlayActionMode) { subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null); } else { subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null); } if (VERSION.SDK_INT >= 21) { ViewCompat.setOnApplyWindowInsetsListener(subDecor, new OnApplyWindowInsetsListener() { public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) { int top = insets.getSystemWindowInsetTop(); int newTop = AppCompatDelegateImpl.this.updateStatusGuard(top); if (top ! = newTop) { insets = insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), newTop, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); } return ViewCompat.onApplyWindowInsets(v, insets); }}); } else { ((FitWindowsViewGroup)subDecor).setOnFitSystemWindowsListener(new OnFitSystemWindowsListener() { public void onFitSystemWindows(Rect insets) { insets.top = AppCompatDelegateImpl.this.updateStatusGuard(insets.top); }}); } } if (subDecor == null) { throw new IllegalArgumentException("AppCompat does not support the current theme features: { windowActionBar: " + this.mHasActionBar + ", windowActionBarOverlay: " + this.mOverlayActionBar + ", android:windowIsFloating: " + this.mIsFloating + ", windowActionModeOverlay: " + this.mOverlayActionMode + ", windowNoTitle: " + this.mWindowNoTitle + " }"); } else { if (this.mDecorContentParent == null) { this.mTitleView = (TextView)subDecor.findViewById(id.title); } ViewUtils.makeOptionalFitsSystemWindows(subDecor); ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content); ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290); if (windowContentView ! = null) { while(windowContentView.getChildCount() > 0) { View child = windowContentView.getChildAt(0); windowContentView.removeViewAt(0); contentView.addView(child); } windowContentView.setId(-1); contentView.setId(16908290); if (windowContentView instanceof FrameLayout) { ((FrameLayout)windowContentView).setForeground((Drawable)null); }} / / will be added to the DecorView subDecor enclosing mWindow. The setContentView (subDecor); contentView.setAttachListener(new OnAttachListener() { public void onAttachedFromWindow() { } public void onDetachedFromWindow() { AppCompatDelegateImpl.this.dismissPopups(); }}); return subDecor; }}}Copy the code

The above code summed up doing one thing, creating subDecor. To spread it out, the details are as follows:

1, according to the theme selected by the user to set some display features, including title, actionbar, etc.

2. Initialize subDecor according to different features; Initialize sub-views inside subDecor. \

3. Finally add it to the DecorView.

The specific code added is as follows: here by calling

// AppCompatDelegateImpl this.mWindow.getDecorView(); // phoneWindow public final View getDecorView() { if (mDecor == null || mForceDecorInstall) { installDecor(); } return mDecor; } private void installDecor() { mForceDecorInstall = false; // Generate DecorView mDecor = generateDecor(-1); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! = 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); }} else {// DecorView has window mDecor. SetWindow (this); }... } protected DecorView generateDecor(int featureId) { // System process doesn't have application context and in that case  we need to directly use // the context we have. Otherwise we want the application context, so we don't cling to the // activity. Context context; if (mUseDecorContext) { Context applicationContext = getContext().getApplicationContext(); if (applicationContext == null) { context = getContext(); } else { context = new DecorContext(applicationContext, getContext()); if (mTheme ! = -1) { context.setTheme(mTheme); } } } else { context = getContext(); } return new DecorView(context, featureId, this, getAttributes()); }Copy the code

This completes the creation of the DecorView. However, we don’t seem to see when the DecorView was added and made visible to the user.

 WindowManager

Once the View is created, how does the Decorview get added to the screen? Windows Manager, of course, so how do you upload a View to Windows Manager?

Look at the handleResumeActivity method in ActivityThread:

// ActivityThread public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { ...... final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. boolean willBeVisible = ! a.mStartedActivity; if (! willBeVisible) { try { willBeVisible = ActivityManager.getService().willActivityBeVisible( a.getActivityToken()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } if (r.window == null && ! a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; . if (a.mVisibleFromClient) { if (! a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); } else { // The activity will get a callback for this {@link LayoutParams} change // earlier. However, at that time the decor will not be set (this is set // in this method), so no action will be taken. This call ensures the // callback occurs with the decor set. a.onWindowAttributesChanged(l);  } } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. } else if (! willBeVisible) { if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set"); r.hideForNow = true; } // Get rid of anything left hanging around. cleanUpPendingRemoveWindows(r, false /* force */); // The window is now visible if it has been added, we are not // simply finishing, and we are not starting another activity. if (! r.activity.mFinished && willBeVisible && r.activity.mDecor ! = null && ! r.hideForNow) { if (r.newConfig ! = null) { performConfigurationChangedForActivity(r, r.newConfig); if (DEBUG_CONFIGURATION) { Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig); } r.newConfig = null; } if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward); WindowManager.LayoutParams l = r.window.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) ! = forwardBit) { l.softInputMode = (l.softInputMode & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) | forwardBit; if (r.activity.mVisibleFromClient) { ViewManager wm = a.getWindowManager(); View decor = r.window.getDecorView(); wm.updateViewLayout(decor, l); } } r.activity.mVisibleFromServer = true; mNumVisibleActivities++; If (state Richard armitage ctivity. MVisibleFromClient) {/ / here will call addview state Richard armitage ctivity. MakeVisible (); } } r.nextIdle = mNewActivities; mNewActivities = r; if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r); Looper.myQueue().addIdleHandler(new Idler()); }Copy the code

The above code does a few things:

1. Get DecorView, Settings invisible, addView to WindowManager via wm.addView(decor, l);

2. In some cases, such as when you click on the input field to bring up the keyboard, wm.updateViewLayout(decor, L) is called to update the View layout.

3. Once this is done, the Activity’s makeVisible is called to make the view visible. If the DecorView is not added to WindowManager at this point, it will be.

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

Now, let’s look at the logic of addView. The WindowManager implementation class is WindowManagerImpl, which implements addView through the WindowManagerGlobal agent.

// WindowManagerGlobal public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { // ...... 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 { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; }}Copy the code

Here, the view wrootimpl is instantiated. The setView method of ViewRootImpl is also called to hold the DecorView. There are also instances of DecorView, Params, and ViewRootImpl.

Now we know why the View is visible in OnResume.

 ViewRootImpl

In fact, View drawing is done by View Rotimpl. Each application window’s DecorView has a ViewRootImpl object associated with it, and this association is maintained by WindowManager.

Let’s start with the setView method in the ViewRootImpl. It’s a long method and we’ll comment out some unimportant points:

/** * We have one child */ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; . mAdded = true; int res; /* = WindowManagerImpl.ADD_OKAY; */ // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); . }}}Copy the code

Here you save the mView instance of the DecorView, and then call the requestLayout() method to complete the initial layout of the application’s user interface.

 public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
Copy the code

Make sure that the main thread is used as a checkThread. ScheduleTraversals are then called to plan the rendering.

void scheduleTraversals() { if (! mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (! mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}Copy the code

Here are two main concerns:

MTraversalBarrier: Synchronization barrier for handlers. It intercepts Looper’s attempts to get and distribute synchronous messages. After joining the synchronization barrier, Looper will only get and process asynchronous messages. If there are no asynchronous messages, Looper will block. That is, processing operations that render a View can take precedence (set to asynchronous messages).

MChoreographer: Choreographer. Unify animation, input, and drawing timing. This chapter also needs to focus on the analysis of content.

MTraversalRunnable: An instance of the TraversalRunnable, which is a Runnable, must eventually call its run method:

final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); }}Copy the code

The doTraversal, as the name implies, is now drawn, and the method itself will eventually be called formTraversals to draw.

void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; }}}Copy the code

This completes the binding between the DecorView and the activity, and the next chapter will show you what performTraversals do, the View drawing process.

Attached is a flow chart:

 

At this point, the relationship between DecorView and ViewRootImpl is clear.

The problem

1. When was the drawing process of the first View triggered?

Now that we’re talking about the View drawing process, when does the whole process get triggered? The answer is in ActivityThread. HandleResumeActivity triggered.

ActivityThread. HandleResumeActivity invoked in wm. AddView to add DecorView, wm is WindowManagerImpl;

Finally through WindowManagerImpl. AddView – > WindowManagerGlobal. AddView – > ViewRootImpl. SetView – > ViewRootImpl. RequestLayout That triggers the first View drawing.

2. When was ViewRootImpl created? 

Can see from the above process, ViewRootImpl in ActivityThread. Create handleResumeActivity.

3. What is the relationship between ViewRootImpl and DecorView? 

Set in PhoneWindow. InstallDecor -> generateLayout. In ViewRootImpl. SetView, set ViewRootImpl to be the parent of the DecorView via decorView. assignParent. So the relationship between ViewRootImpl and DecorView is that ViewRootImpl is the parent of the DecorView. Since the DecorView is the top layer of our layout, we can now see how layer upon layer calls to methods such as requestLayout are called into the ViewRootImpl.

4. What is the layout of the DecorView?

DecorView -> [title_bar, content] The title_bar and Content views are included in the DecorView, but this is the default layout. In fact, depending on the theme style, the DecorView should have a different layout. The title_bar and content included in the figure correspond to the r.layout.screen_simple layout. So when was all this layout set up?

5. When was the DecorView created?

The PhoneWindow. InstallDecor -> generateDecor is actually creating a DecorView. So when did installDecor get called? Invocation chain is the Activity. The setContentView – > PhoneWindow. The setContentView – > installDecor here that continue to think of, the Activity. What is the process of the setContentView?

6. Flow of setContentView

The setContentView process is simpler, can call PhoneWindow. The setContentView. It does two things:

  1. Create a DecorView
  2. Create the View according to layoutResId and add it to the DecorView

7. The relationship between Activity, PhoneWindow, DecorView, ViewRootImpl?

A PhoneWindow is the only subclass of Window that is the middle layer of the Activity and View interaction system, and a DecorView is the top layer of the View hierarchy. The ViewRootImpl is the parent of a DecorView, but it’s not really a View. It just inherits the ViewParent interface, which handles the View’s events. Include requestLayout, Invalidate, dispatchInputEvent, etc. \

8. When was PhoneWindow created?

Now that PhoneWindow is mentioned, when was PhoneWindow created? Is in the Activity. The created in the attach, and Activity. The attach is the ActivityThread. Create performLaunchActivity. Here can also be extended to the Activity start process, here will not talk about. \

9. How do I trigger a redraw?

How do we trigger the View to be redrawn? Call requestLayout and Invalidate.

 

 

Check out the next Android View measurement process in detail