Don’t know how to do UI optimization?
This article is divided into three sections — Principles of UI, Principles of LayoutInflater, and UI optimization — and is a bit long, so choose your favorite chapter to read. Each section has a summary at the end.
You can also give a rough idea of the process of starting an Activity, such as:
AMS is responsible for managing all activities in the system, so applying startActivity will eventually call the startActivity method of AMS through Binder. AMS will check before starting an Activity, such as permissions, whether it is registered in the manifest file, etc. Then you can start. AMS is a system service in a separate process, so the lifecycle is told to applications, and it involves cross-process calls, which also use Binder, through ActivityThread’s inner ApplicationThread class. AMS passes the lifecycle across the process to the ApplicationThread, which is then distributed to the Handler inside the ActivityThread. At this point, the lifecycle is called back to the main ApplicationThread, calling back to the Activity’s lifecycle methods.
You can also subdivide the relationship between Activity, Window, and DecorView. This should be easy, but why should setContentView be in onCreate? Can I put it in another method? Can I put it in the onAttachBaseContext method? In fact, these questions can be found in the source code answers.
This article refers to Android source code 9.0, API 28.
The body of the
When we write an Activity, we usually set our layout by calling the setContentView method in the onCreate method. Have you ever thought about this question? Once you set the layout, the interface will start drawing?
First, from the life cycle source analysis UI principle
The process for starting an Activity is as follows:
- Context -> startActivity
- AMS -> startActivity
- If the process does not exist, Zygote is notified to start the process. After the process is started, the main method of ActivityThread is executed, the loop is entered, and messages are distributed through the Handler.
- ApplicationThread -> scheduleLaunchActivity
- ActivityThread -> handleLaunchActivity
- Other lifecycle callbacks
Start with point 4
1.1 ActivityThread
1.1.1 Inner class ApplicationThread
For the moment, AMS will start an Activity by notifying it to the ActivityThread via the ApplicationThread. Take a look at the scheduleLaunchActivity method
1.1.2 ApplicationThread# scheduleLaunchActivity
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, PersistableBundle persistentState, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) { ActivityClientRecord r = new ActivityClientRecord(); r.token = token; r.ident = ident; r.intent = intent; . sendMessage(H.LAUNCH_ACTIVITY, r); }Copy the code
SendMessage finally wraps an ActivityClientRecord object into MSG, calling mh.sendMessage (MSG); MH is a Handler, so let’s just look at the Handler,
1.2 ActivityThread’s inner class H
private class H extends Handler { public void handleMessage(Message msg) { switch (msg.what) { case LAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); final ActivityClientRecord r = (ActivityClientRecord) msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); //1, get the ActivityClientRecord object from MSG. Obj and call handleLaunchActivity to handle the message handleLaunchActivity(r, null, "LAUNCH_ACTIVITY"); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; . }}Copy the code
So that’s the basics of this Handler, so you should be able to read it, so comment 1, start the Activity and go directly to the handleLaunchActivity method
1.3 ActivityThread# handleLaunchActivity
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) { ... // Make sure we are running with the most recent config. handleConfigurationChanged(null, null); / / Initialize before creating the activity / / initialization WindowManagerGlobal WindowManagerGlobal. The Initialize (); //1. Start an Activity, involve creating an Activity object, and finally return the Activity object Activity a = Activity a = performLaunchActivity(r, customIntent); if (a ! = null) { r.createdConfig = new Configuration(mConfiguration); reportSizeConfigurations(r); Bundle oldState = r.state; //2. HandleResumeActivity (R.Token, false, R.isforward,! r.activity.mFinished && ! r.startsNotResumed, r.lastProcessedSeq, reason); if (! r.activity.mFinished && r.startsNotResumed) { //3. If not in the foreground display, I entered a performPauseActivityIfNeeded onPuse method (r, a tiny); }} else {// If there was an error, for any reason, tell the activity manager to stop us. Notifies the AMS finish off this Activity the try {ActivityManagerNative. GetDefault () finishActivity (r.t oken, Activity. The RESULT_CANCELED, null, Activity.DONT_FINISH_TASK_WITH_ACTIVITY); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); }}}Copy the code
2. The Activity is in the Resume state, handleResumeActivity 3. If can’t at the front desk display, then enter the pause state, performPauseActivityIfNeeded comments 4, if failed, start notice to finishActivity AMS.
PerformLaunchActivity (1.3.1) and handleResumeActivity (1.3.2)
1.3.1 ActivityThread# performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {/ / 1 create the Activity object Activity = mInstrumentation newActivity (cl, component getClassName (), r.intent); Attach (appContext, this, getInstrumentation(), R.token, R.indent, app, R.intent, appContext, appContext, appContext, getInstrumentation(), r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window); / / 3. Callback onCreate mInstrumentation. CallActivityOnCreate (activity, r.s Tate, r.p ersistentState); //4. Callback onStart Activity.performStart (); if (r.state ! = null || r.persistentState ! = null) {//5. If there is saved state, call the onRestoreInstanceState method, for example, the Activity was killed by an exception, Override onSaveInstanceState save some state mInstrumentation. CallActivityOnRestoreInstanceState (activity, r.s Tate, r.p ersistentState); } / / 6. Callback onPostCreate, this method used basic mInstrumentation. CallActivityOnPostCreate (activity, r.s Tate); }Copy the code
Here we can see the order in which the Activity methods are called:
- Activity#attach
- Activity#onCreate
- Activity#onStart
- Activity#nnRestoreInstanceState
- Activity#onPostCreate
We’ll focus on the ATTACH method and the most familiar onCreate method
1.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) {// attachBaseContext(context); //2, create PhoneWindow mWindow = new PhoneWindow(this, window); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); . 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;Copy the code
The attach method focuses on the following points: 1. The attachBaseContext method is invoked. 2. Create a PhoneWindow and assign it to mWindow, which you will often encounter later.
2. Activity#onCreate
Have you ever wondered why the setContentView is in the onCreate method? You can’t put it in onStart, onResume and we probably know that, because pressing the Home button and cutting it back will call back the lifecycle onStart, onResume, setContentView multiple times and it’s not necessary. How about if I put it inside attachBase Text? Look at the logic inside the setContentView
3. Activity#setContentView
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
Copy the code
GetWindow returns mWindow, which is initialized in the attach method of the Activity, which we just analyzed. MWindow is a PhoneWindow object. So setContentView has to go after attachBaseContext.
4. PhoneWindow#setContentView
Public void setContentView(int layoutResID) {if (mContentParent == null) {//1. } else if (! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else {//2, mContentParent is FrameLayout in the DecorView, Add our layout to this FrameLayout with mlayoutInflater.inflate (layoutResID, mContentParent); }... }Copy the code
The installDecor method is to create a DecorView and take a look at the main code
Note 1: installDecor, create root layout DecorView. Note 2: mlayoutinflater.inflate (layoutResID, mContentParent). To render an XML layout into an mContentParent, section 2 focuses on the LayoutInflater principle.
Let’s start with note 1
5. PhoneWindow#installDecor
private void installDecor() { mForceDecorInstall = false; If (mDecor == null) {//1. Create DecorView mDecor = generateDecor(-1); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! = 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); If the decor is not empty, setWindow decor. SetWindow (this); } if (mContentParent == null) { //3. FindViewById (ID_ANDROID_CONTENT) mContentParent = generateLayout(mDecor); // Set up decor part of UI to ignore fitsSystemWindows if appropriate. mDecor.makeOptionalFitsSystemWindows(); //4. Find the root View of the DecorView, give the title bar part, DecorContentParent = (DecorContentParent) mDecor. FindViewById (DecorContentParent) R.id.decor_content_parent); //5. If (decorContentParent! = null) { mDecorContentParent = decorContentParent; mDecorContentParent.setWindowCallback(getCallback()); if (mDecorContentParent.getTitle() == null) { mDecorContentParent.setWindowTitle(mTitle); }... mDecorContentParent.setUiOptions(mUiOptions); if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) ! = 0 || (mIconRes ! = 0 &&! mDecorContentParent.hasIcon())) { mDecorContentParent.setIcon(mIconRes); } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 && mIconRes == 0 && ! mDecorContentParent.hasIcon()) { mDecorContentParent.setIcon( getContext().getPackageManager().getDefaultActivityIcon()); mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK; } if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) ! = 0 || (mLogoRes ! = 0 &&! mDecorContentParent.hasLogo())) { mDecorContentParent.setLogo(mLogoRes); }... }... }}Copy the code
InstallDecor does two things: one is to create a DecorView, and the other is to fill in some of the DecorView’s properties based on the theme, such as the title bar + content section by default
First look at note 1: generateDecor creates DecorView
6. PhoneWindow#generateDecor
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
Copy the code
7. DecorView constructor
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
super(context);
...
updateAvailableWidth();
//设置window
setWindow(window);
}
Copy the code
Create a DecorView and pass in a PhoneWindow.
Call mDecor. SetWindow (this) if the DecorView was already created.
Look again at Note 3 of the installDecor method
if (mContentParent == null) { mContentParent = generateLayout(mDecor); .Copy the code
To assign a value to mContentParent, look at the generateLayout method
8. PhoneWindow#generateLayout
protected ViewGroup generateLayout(DecorView decor) { // 1. For example, requestFeature(FEATURE_NO_TITLE); . if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { requestFeature(FEATURE_NO_TITLE); }... if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) { setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags())); }... //2. Obtain the windowBackground property, If (mBackgroundDrawable == null) {if (mBackgroundResource == 0) {mBackgroundResource = a.getResourceID (mBackgroundResource == 0) R.styleable.Window_windowBackground, 0); }... }... ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); . if (getContainer() == null) { //4. Drawable (drawable); drawable (Drawable); drawable (Drawable); if (mBackgroundResource ! = 0) { background = getContext().getDrawable(mBackgroundResource); } else { background = mBackgroundDrawable; } mDecor.setWindowBackground(background); } return contentParent; }Copy the code
Note 1: first is the window property Settings, such as our subject attribute set have no title, then go: requestFeature (FEATURE_NO_TITLE); SetFlags (FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
There are a lot of other properties, so I’m just going to list two representative, why do I say representative, because we usually want the Activity to be full screen, so we get rid of the title bar, so we can set that property in the theme, or we can set it in code, which is in the onCreate method before the setContentView, right
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN );
Copy the code
Note 2: Get the window background attribute ID, which is an attribute under the theme we defined in style.xml
<style name="AppThemeWelcome" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/colorPrimary</item> ... <item name="android:windowBackground">@mipmap/logo</item> </style>Copy the code
Note 3, this ID_ANDROID_CONTENT defined in the Window, the public static final ints ID_ANDROID_CONTENT = com. Android. Internal. R.i, dc ontent; Find the ViewGroup that corresponds to the ID and it’s called contentParent, and eventually it’s going to go back.
Note 4: We got the window background id in note 2, which is converted to Drawable and set to DecorView. DecorView is a FrameLayout, so we set the default background image in the theme, which is finally set to DecorView
The generateLayout method reads a bunch of properties configured in the theme, then sets some properties in the DecorView, such as the background, and gets the content layout in the DecorView as a return value for the method.
Back in the installDecor method comment 4, populate the DecorContentParent content
9. Fill DecorContentParent
// This is the DecorView layout, Final DecorContentParent DecorContentParent = (DecorContentParent) mDecor. FindViewById (DecorContentParent) R.id.decor_content_parent); if (decorContentParent ! = null) { mDecorContentParent = decorContentParent; mDecorContentParent.setWindowCallback(getCallback()); / / have this property, it related Settings, such as title, icon mDecorContentParent. SetWindowTitle (mTitle); . mDecorContentParent.setIcon(mIconRes); . mDecorContentParent.setIcon( getContext().getPackageManager().getDefaultActivityIcon()); . mDecorContentParent.setLogo(mLogoRes); .Copy the code
This is the final step in installDecor, setting the title, ICONS, and other properties.
If the DecorView does not exist, create the DecorView. Now that the DecorView is created, add the layout we wrote to the DecorView. How do we do that? See comment 2 of the phonwwPhonwWindoWindows #setContentView method, mlayoutInflater.inflate (layoutResID, mContentParent); The mContentParent is returned by the generateLayout method, which is the DecorView with the ID content.
So the next step with LayoutInflater.inflate is to add the layout we wrote to the contents of the DecorView. How do you do that? So this involves parsing the XML and converting it to a View object. How?
The LayoutInflater principle is given in the second section to make it clearer. mLayoutInflater.inflate(layoutResID, mContentParent); The XML layout is parsed into a View object and added to the DecorView. Draw a picture
The DecorView does not appear on the screen at this point because PhoneWindow is not in use, it is just ready. 1.3 Continue to analyze the second point
1.3.2 ActivityThread# handleResumeActivity
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); . R = performResumeActivity(token, clearHide, reason); if (r ! = null) { final Activity a = r.activity; . if (r.window == null && ! a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); //2, wm is WindowManager ViewManager for the current Activity wm = a.getwindowManager (); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; . if (a.mVisibleFromClient && ! a.mWindowAdded) { a.mWindowAdded = true; // 3. Add decor to WindowManager Wm. addView(decor, L); }Copy the code
Note 1: performResumeActivity, which calls back the Activity’s onResume method. Note 2: Trace this WM from the Activity, which is actually WindowManagerImpl. Note 3: The DecorView is added to the WindowManagerBal by calling the addView method of the WindowManagerImpl, which eventually calls the addView method of the WindowManagerGlobal
In this order, the decorView is added to the window only after the Activity’s onResume method calls back
1 WindowManagerImpl#addView
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); . public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); }Copy the code
See WindowManagerGlobal# addView
2 WindowManagerGlobal#addView
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ViewRootImpl root; View panelParentView = null; synchronized (mLock) { ... //1, create ViewRootImpl root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); //2, add to ArrayList for management mviews.add (view); //3. MRoots stores the ViewRootImpl object mroots.add (root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { // 4. Call ViewRootImpl's setView root.setView(view, wParams, panelParentView); }Copy the code
2. Add the DecorView to the ViewRootImpl. The relationship between ViewRootImpl and the DecorView is established. Note 3: Save ViewRootImpl to ArrayList. Note 4: Call the setView method of ViewRootImpl. This is key.
The first argument is a DecorView, the second argument is a property of PhoneWindow, and the third argument is the parent window. If the current window is a child window, pass the parent window. Otherwise, pass null.
If you’re confused, let’s rearrange the whole process of resume:
- ActivityThread calls handleResumeActivity (this method originates from the AMS callback)
- Callback the Activity’s onResume
- Add the DecorView to the WindowManagerGlobal
- Create a ViewRootImpl, call the setView method, bind it to the DecorView
Next, focus on the setView method of ViewRootImpl
1.4 ViewRootImpl analysis
Let’s look at the constructor
1.4.1 ViewRootImpl structure
public ViewRootImpl(Context context, Display display) { mContext = context; / / 1, WindowSession IWindowSession, binder object, can communication across processes with WMS mWindowSession = WindowManagerGlobal getWindowSession (); mDisplay = display; . MWindow = new W(this); // 2. Create window, which is a binder object. . //3. MAttachInfo View.post is related to this mAttachInfo = new view.attachInfo (mWindowSession, mWindow, display, this, mHandler, this); . / / 4, Choreographer initialization mChoreographer = Choreographer. Here the getInstance (); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); loadSystemProperties(); }Copy the code
Note 1: Create a mWindowSession, which is a Binder object that communicates with WMS. Note 2: mWindow instantiation, which receives notification of window state changes. Note 3: mAttachInfo creation, view.post(runable) can get width and height. The problem is related to the creation of mAttachInfo. Note 4: mChoreographer initializes. We have covered Choreographer before and will not repeat this story.
Let’s just take a look at how mWindowSession is created, because it’s going to be used in a lot of places to communicate with WMS.
1.4.2 What mWindowSession does
mWindowSession = WindowManagerGlobal.getWindowSession();
Copy the code
How does Windows ManagerGlobal create a Session
1. WindowManagerGlobal.getWindowSession()
public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { InputMethodManager imm = InputMethodManager.getInstance(); IWindowManager windowManager = getWindowManagerService(); / / call the WMS openSession method through agent returns a Session (also a Binder agent) sWindowSession = windowManager. OpenSession (new IWindowSessionCallback.Stub() { @Override public void onAnimatorScaleChanged(float scale) { ValueAnimator.setDurationScale(scale); } }, imm.getClient(), imm.getInputContext()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } return sWindowSession; }}Copy the code
Get WMS’s Binder proxy and call WMS’s openSession method through the binder proxy. Take a look at WMS’s openSession method
2. WindowManagerService#openSession
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client, IInputContext inputContext) { if (client == null) throw new IllegalArgumentException("null client"); if (inputContext == null) throw new IllegalArgumentException("null inputContext"); Session = new Session(this, callback, client, inputContext); return session; }Copy the code
Create a Session return, which is also a Binder object with cross-process transport capability
3. Session
final class Session extends IWindowSession.Stub
implements IBinder.DeathRecipient {
Copy the code
MWindowSession is a Session object with cross-process capability. It has a reference to WMS, and ViewRootImpl interacts with WMS through this mWindowSession.
Look at the setView method of ViewRootImpl
1.4.2 ViewRootImpl# setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {// . //2. RequestLayout requestLayout(); . try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); //3.WindowSession, add window to screen, Returns a value res = mWindowSession. AddToDisplay (mWindow mSeq, mWindowAttributes, getHostVisibility (), mDisplay. GetDisplayId (), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); }... If (res < WindowManagerglobal.add_okay) {... switch (res) { case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?" ); case WindowManagerGlobal.ADD_NOT_APP_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not for an application"); case WindowManagerGlobal.ADD_APP_EXITING: throw new WindowManager.BadTokenException( "Unable to add window -- app for token " + attrs.token + " is exiting"); case WindowManagerGlobal.ADD_DUPLICATE_ADD: throw new WindowManager.BadTokenException( "Unable to add window -- window " + mWindow + " has already been added"); case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED: // Silently ignore -- we would have just removed it // right away, anyway. return; case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON: throw new WindowManager.BadTokenException("Unable to add window " + mWindow + " -- another window of type " + mWindowAttributes.type + " already exists"); case WindowManagerGlobal.ADD_PERMISSION_DENIED: throw new WindowManager.BadTokenException("Unable to add window " + mWindow + " -- permission denied for window type " + mWindowAttributes.type); case WindowManagerGlobal.ADD_INVALID_DISPLAY: throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow + " -- the specified display can not be found"); case WindowManagerGlobal.ADD_INVALID_TYPE: throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow + " -- the specified window type " + mWindowAttributes.type + " is not valid"); } throw new RuntimeException( "Unable to add window -- unknown error code " + res); }... //5. Set parent to ViewRootImpl view.assignParent(this); . }}}Copy the code
1. Assign mView as DecorView annotation 2. Call requestLayout, initiate rendering request comment 3. WindowSession is a Binder object, mWindowSession. AddToDisplay (…). 4. Add window failure comment 5: View.assignParent (this); Set parent to ViewRootimpl for DecorView
Call requestLayout, request vSYN signal, and then the next time vsync signal comes, call performTraversals, which was analyzed in the last article. PerformTraversals mainly executes the measure, layout and draw of the View.
Note 3, mWindowSession addToDisplay this sentence to focus on analysis,
1.4.2.1 mWindowSession.addToDisplay
The creation of mWindowSession has been analyzed above.
1.4.2.2 Session# addToDisplay
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) {// call WMS addWindow return mService. AddWindow (this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outOutsets, outInputChannel); }Copy the code
We have WMS in our Session, and you can see here that the addToDisplay method is calling the addWindow method of WMS.
1.4.2.3 WindowManagerService# addWindow
WMS addWindow method has a lot of code, which is not intended to be analyzed in detail. Internally, it involves window permission verification, token verification, creating window objects, adding Windows to Windows list, determining window location, and so on. Readers can view the source code by themselves.
The medium through which ViewRootImpl communicates with WMS is Session:
ViewRootImpl -> Session -> WMS
Return to the setView method of ViewRootImpl, comment 2 calls the requestLayout method, which will eventually request the vsync signal, and then wait for the next vsync signal to arrive and execute the performTraversals method through the Handler. So in order of execution, the performTraversals method is called after the Window is added.
1.5 ViewRootImpl# performTraversals
private void performTraversals() { ... RelayoutResult = relayoutWindow(params, viewVisibility, insetsPending); // 1, relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); . performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); . performLayout(lp, mWidth, mHeight); . performDraw(); }Copy the code
PerformDraw traversals, traversals, traversals, traversals, traversals, traversals, traversals, traversals, traversals
Look at the performDraw
private void performDraw() { draw(fullRedrawNeeded); . } private Boolean draw(Boolean fullRedrawNeeded) {// new Surface = mSurface; // Only analysis software drawif(! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {return false;
}
return useAsyncReport;
}
Copy the code
- MSurface was defined as new
public final Surface mSurface = new Surface();
There is one thing that performTraversals does in note 1, calling relayoutWindow
relayoutWindow
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException { ... Int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params, (int) (mview.getMeasuredWidth () * appScale + 0.5f), (int) (mview.getMeasuredHeight () * appScale + 0.5f), viewVisibility, insetsPending? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
mPendingMergedConfiguration, mSurface);
return relayoutResult;
}
Copy the code
It calls the mWindowSession.relayout method, passes the mSurface, and looks directly at the Session relayout method
public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, Int res = mService. RelayoutWindow (this, window, seq, attrs) {// invoke the WMS relayoutWindow method int res = mService. RelayoutWindow (this, window, seq, attrs, requestedWidth, requestedHeight, viewFlags, flags, frameNumber, outFrame, outOverscanInsets, outContentInsets, outVisibleInsets, outStableInsets, outsets, outBackdropFrame, cutout, mergedConfiguration, outSurface); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
+ Binder.getCallingPid());
return res;
}
Copy the code
Finally, WMS will call the relayoutWindow method and WMS will do something with the mSurface to make it relevant to the current PhoneWindow.
performDraw -> draw -> drawSoftware
Moving on to the ViewRootImpl’s drawSoftware method
ViewRootImpl#drawSoftware
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) { // Draw with software renderer. final Canvas canvas; Canvas = mSurface. LockCanvas (dirty); //2, mview.draw (canvas); / / 3, finish drawing submitted to Surfece, and release the brush surface. UnlockCanvasAndPost (canvas); }Copy the code
The core of the drawSoftware method is three lines of code:
- LockCanvas gets a canvas from the Surface.
- Draw, use this canvas to draw the View.
- UnlockCanvasAndPost to submit the drawn content to the Surface.
mView.draw(canvas); Everyone’s familiar with, mView is DecorView, is a FrameLayout, here is the traversal view, calls the draw method, finally is called Canvas. The draw. When the View tree draws, it submits the drawn content to the Surface, which is then managed by The SurfaceFlinger.
- Surface: Each View is managed by a window, and each window is associated with a Surface
- Canvas: Obtain a Canvas through the lockCanvas method of Surface. Canvas can be understood as the encapsulation of Skia’s underlying interface. Calling Canvas’s draw method is equivalent to calling the drawing method of Skia library.
- Graphic Buffer: SurfaceFlinger will host a Buffer Queue for us. We get a Graphic Buffer from the Buffer Queue and rasterize the drawn data onto it via Canvas and Skia.
- SurfaceFlinger: Submit the foreground Graphic Buffer to SurfaceFlinger through Swap Buffer. Finally, synthesize the Graphic Buffer through Hardware Composer and output it to the display.
Summary of UI Principles
- Create a PhoneWindow in the Attach method of the Activity.
- The setContentView in the onCreate method calls the setContentView method in the PhoneWindow, creating the DecorView and parsing the XML layout and adding it to the DecorView.
- After the onResume method is executed, the ViewRootImpl is created, which is the top-level View and the parent of the DecorView. After it is created, the setView method is called.
- The setView method of ViewRootImpl adds PhoneWindow to THE WMS, using Session as a medium. So the setView method will call requestLayout, and it will make a drawing request.
- RequestLayout will be passed through the View’s 3 measure, layout and draw methods. The requestLayout will be passed through the View’s 3 measure, layout and draw methods.
- Finally, the principle of software drawing is analyzed. The Surface is bound to the current Window by the relayoutWindow method, and the Surface Canvas is obtained by the lockCanvas method of the Surface, and then the drawing of the View is done through this Canvas. Finally, the unlockCanvasAndPost method of Surface is used to submit the drawn data. Finally, the drawn data is submitted to The SurfaceFlinger for screen display.
LayoutInflater Principle
While analyzing the setContentView earlier, you end up calling mlayoutInflater.inflate (layoutResID, mContentParent); Add the View of the layout file to the Content section of the DecorView. In this section, we’ll examine how LayoutInflaters work
Layoutinflaters are created in the PhoneWindow constructor
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
Copy the code
LayoutInflater is a system service
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return LayoutInflater;
}
Copy the code
The inflate method has many overloads. See one with the resource ID parameter
Layoutinflaterinflate (@layoutres int Resource, @Nullable ViewGroup root)
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root ! = null); } // If root is not empty, attachToRoot is true, indicating that the final parsed View is to be added to root. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); //1. Convert the resource ID to XmlResourceParser final XmlResourceParser Parser = Res.getLayout (Resource); Try {//2. Call another overloaded method return inflate(Parser, root, attachToRoot); } finally { parser.close(); }}Copy the code
Note 1 converts the resource ID to XmlResourceParser. What is XmlResourceParser?
public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable {
/**
* Close this interface to the resource. Calls on the interface are no
* longer value after this call.
*/
public void close();
}
Copy the code
Inherits the XmlPullParser interface, which is the standard XmlPullParser interface, and inherits the AttributeSet interface and the close interface called when the user has finished reading the resource, which is simply an XML resource parser for parsing XML resources.
See the link at the end of this article for details on how XmlPullParser works.
2.2 [Extension] What are the methods of XML parsing
SAX (Simple API XML)
Event-driven streaming parsing is an event-based parser that parses sequentially from the beginning of the file to the end of the document without pausing or regress
DOM
The object document model, which loads the entire XML document into memory (so it’s inefficient and not recommended), treating each node as an object
Pull
The way Android parses layout files. Pull is similar to SAX in that both provide similar events, such as start and end elements. SAX, by contrast, is event-driven by calling back to the corresponding method, providing the method for the callback, and then automatically calling the corresponding method within SAX. The Pull parser, on the other hand, does not mandate that the triggering method be provided. Because the event he triggers is not a method, but a number. It is easy to use and efficient.
Never use it, but know the advantages of Pull parsing: Pull parser compact and light, parsing fast, easy to use, very suitable for use in Android mobile devices, Android system in the internal parsing of various XML is also using Pull parser, Android official recommended developers to use Pull parsing technology.
Refer to https://www.cnblogs.com/guolingyun/p/6148462.html
Inflate method comment 1 above parses the XML, resulting in an XmlResourceParser object,
To parse an XML, we can use getContext().getResources().getLayout(resource); Returns an XmlResourceParser object.
Note 2, calls the other inflate overload method, with XmlResourceParser as the input parameter
Layoutinflaterinflate (XmlPullParser Parser, ViewGroup root, Boolean attachToRoot)
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final Context inflaterContext = mContext; Final AttributeSet attrs = xml.asattributeset (parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root; try { // Look for the root node. int type; While ((type = parser.next())); = XmlPullParser.START_TAG && type ! = XmlPullParser.END_DOCUMENT) { // Empty } //2. If (type! = XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!" ); } String name = Parser.getName (); String name = parser.getName(); String name = parser.getName(); / / 4, if the root is the merge tag if layout (TAG_MERGE. Equals (name)) {/ / merge tag must be attached to a parent layout, rootView is empty, then throw exceptions if (root = = null | |! attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } // With the merge tag call the rInflate method (analyzed below). RInflate (Parser, root, inflaterContext, attrs, false); } else {//5, not merge tag, call createViewFromTag, Temp is the root View that was found in the XML final View Temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root ! = null) {// This method is parsing the layout_width and layout_height properties, Create a LayoutParams / / Create layout params that match the root, the if supplied params = root. GenerateLayoutParams (attrs); if (! AttachToRoot) {// If it is simply parsed and does not attach to a parent View, then the root View sets LayoutParams; otherwise, use the external View's width and height property temp. }} //6. Minflate all children under temp against its context. rInflateChildren(Parser, Temp, attrs, true); // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root ! = null && attachToRoot) { root.addView(temp, params); } // Return temp if not attached to root, // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || ! attachToRoot) { result = temp; } } } return result; }}Copy the code
This is a way of looking at it sequentially, not too complicated, step by step,
Comments 1 to 3: Parse the start tag from XmlPullParser, for example, and then fetch the tag name LinearLayout
Note 4: If the root label is a merge label, the merge label must be attached to the RootView, otherwise an exception will be thrown. The use of the merge label, needless to say, must be placed in the root layout.
The root layout merge tag is parsed by calling rInflate(Parser, root, inflaterContext, attrs, false); Method. Let’s see
2.4 Merge tag synchronization: rInflate
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; while (((type = parser.next()) ! = XmlPullParser.END_TAG || parser.getDepth() > depth) && type ! = XmlPullParser.END_DOCUMENT) { if (type ! = XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); If (TAG_REQUEST_FOCUS. Equals (name)) {parseRequestFocus(parser, parent); } else if (tag_tag.equals (name)) {// 2, tag tag parseViewTag(parser, parent, attrs); } else if (tag_include.equals (name)) {// 3, include tag if (parser.getDepth() == 0) {throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (tag_merge.equals (name)) {throw new InflateException("<merge /> must be the root element"); } else {//5, final View View = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); RInflateChildren (parser, View, attrs, true); viewGroup.addView(view, params); } } if (finishInflate) { parent.onFinishInflate(); }}Copy the code
Resolve merge tags in the following cases:
<? The XML version = "1.0" encoding = "utf-8"? > <merge xmlns:android="http://schemas.android.com/apk/res/android"> <requestFocus></requestFocus> //requestFocus <tag Android :id="@+id/tag"></tag> //tag <include layout="@layout/activity_main"/> //include <merge> // error, <TextView android:layout_width="match_parent" android:layout_height="match_parent" Android :textColor="@color/colorAccent" Android :text="123"/> </merge> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:textColor="@color/colorAccent" android:text="123"/> </merge>Copy the code
2.4.1 Parsing the requestFocus tag
Call the parent View’s requestFocus method
private void parseRequestFocus(XmlPullParser parser, View view) throws XmlPullParserException, IOException { view.requestFocus(); // This view is the parent layout that requests focus consumeChildElements(parser); } final static void consumeChildElements(XmlPullParser Parser) throws XmlPullParserException, IOException { int type; final int currentDepth = parser.getDepth(); While (((type = parser.next()))! = XmlPullParser.END_TAG || parser.getDepth() > currentDepth) && type ! = XmlPullParser.END_DOCUMENT) { // Empty } }Copy the code
2.4.2 Parsing tags
Set the tag to the parent layout
private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs) throws XmlPullParserException, IOException { final Context context = view.getContext(); final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag); final int key = ta.getResourceId(R.styleable.ViewTag_id, 0); final CharSequence value = ta.getText(R.styleable.ViewTag_value); view.setTag(key, value); // This view is the parent, set tag ta.recycle() to the parent layout; consumeChildElements(parser); // Skip the tag}Copy the code
You can see that the requestFocus tag and the tag tag just set properties for the parent View.
2.4.3 Parsing include Labels
private void parseInclude(XmlPullParser parser, Context context, View parent, AttributeSet attrs) throws XmlPullParserException, IOException { int type; If (parent instanceof ViewGroup) {... / / 1, parse layout id attribute values, parsing is less than the default 0 int layout = attrs. GetAttributeResourceValue (null, ATTR_LAYOUT, 0); . If (layout == 0) {final String value = attrs.getAttributeValue(null, ATTR_LAYOUT); throw new InflateException("You must specify a valid layout " + "reference. The layout ID " + value + " is not valid."); } else {//3. Final XmlResourceParser childParser = Context.getResources ().getLayout(Layout); // You can inflate the merge tag just like you did before. CreateViewFromTag try {final AttributeSet childAttrs = xml.asAttributeset (childParser); while ((type = childParser.next()) ! = XmlPullParser.START_TAG && type ! = XmlPullParser.END_DOCUMENT) { // Empty. } if (type ! = XmlPullParser.START_TAG) { throw new InflateException(childParser.getPositionDescription() + ": No start tag found!" ); } final String childName = childParser.getName(); if (TAG_MERGE.equals(childName)) { // The <merge> tag doesn't support android:theme, so // nothing special to do here. rInflate(childParser, parent, context, childAttrs, false); } else { final View view = createViewFromTag(parent, childName, context, childAttrs, hasThemeOverride); // Parent View, and eventually parse child View will be added to the parent View. final ViewGroup group = (ViewGroup) parent; final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.Include); final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID); Final int visibility = a.getint (r.starleable.include_visibility, -1); a.recycle(); ViewGroup.LayoutParams params = null; try { params = group.generateLayoutParams(attrs); } catch (RuntimeException e) { // Ignore, just fail over to child attrs. } if (params == null) { params = group.generateLayoutParams(childAttrs); } view.setLayoutParams(params); // rInflateChildren(childParser, View, childAttrs, true); if (id ! = View.NO_ID) { view.setId(id); } // Set the visibility property switch (visibility) {case 0: view.setvisibility (view.visible); break; case 1: view.setVisibility(View.INVISIBLE); break; case 2: view.setVisibility(View.GONE); break; } group.addView(view); } } finally { childParser.close(); } } } else { throw new InflateException("<include /> can only be used inside of a ViewGroup"); } LayoutInflater.consumeChildElements(parser); }Copy the code
The parent View of the include tag must be a ViewGroup. Note 1 and note 2 check whether the include tag has a layout property. The value of the property must not be 0.
Note 3: XML parses, parses layout/activity_main layout, context.getResources().getLayout(Layout) is familiar, and then it’s pretty much the same as the original parsed layout. If you encounter the merge tag, You may call the rInflateChildren method to resolve the merge tag. Otherwise, as with the minflate method logic, call createViewFromTag to create a View object, and then call rInflateChildren to resolve the child View. Set the visibility property for the View. This logic is the same as in the inflate method, so you’ll look at it later.
2.4.4 Merge Child Labels
The merge label cannot exist under the merge label. The merge label must reside on the ROOT XML node
2.4.5 Recursively parse subviews, rInflateChildren
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, Boolean finishInflate) throws XmlPullParserException, IOException {// Recursively call the parse merge tag method, Parent may inflate (Parser, parent, parent-.getContext (), attrs, finishInflate); }Copy the code
With the merge tag resolved, go back to 2.3 LayoutInflater#inflate and see comment 5
2.5 LayoutInflater#inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
CreateViewFromTag = merge; Temp is the root View that was found in the XML final View Temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root ! = null) {/ / this method is analytical layout_width and layout_height attributes, return a LayoutParams params = root. GenerateLayoutParams (attrs); if (! AttachToRoot) {// If it is simply parsed and does not attach to a parent View, then the root View sets LayoutParams; otherwise, use the external View's width and height property temp. } //6, call this method, Minflate all children under temp against its context. rInflateChildren(Parser, Temp, attrs, true); // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root ! = null && attachToRoot) { root.addView(temp, params); } // Return temp if not attached to root, // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || ! attachToRoot) { result = temp; }Copy the code
Note 5: Create a View object by passing the name of the View in the createViewFromTag method
2.6 LayoutInflater# createViewFromTag
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, Boolean ignoreThemeAttr) {//1. If (name.equals("view")) {name = attrs.getAttributeValue(null, "class"); }... If (name.equals(TAG_1995)) {// Let's party like it's 1995! return new BlinkLayout(context, attrs); } try { View view; if (mFactory2 ! Factory2. OnCreateView (parent, name, context, attrs); factory2. } else if (mFactory ! OnCreateView (name, context, attrs); } else { view = null; } if (view == null && mPrivateFactory ! = null) {/ / 5, and create the View through mPrivateFactory View. = mPrivateFactory onCreateView (the parent, the name, the context, attrs); } if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; If (-1 == name.indexof ('.')) {View = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; }}Copy the code
Note 1: If it is a View tag, for example < View class=” Android.Widget.textView “>, then take out the class property, which is Android.Widget.textView,
Note 2: If the label is blink, create a BlinkLayout and return.
2.6.1 BlinkLayout
private static class BlinkLayout extends FrameLayout { private static final int MESSAGE_BLINK = 0x42; private static final int BLINK_DELAY = 500; private boolean mBlink; private boolean mBlinkState; private final Handler mHandler; public BlinkLayout(Context context, AttributeSet attrs) { super(context, attrs); mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (msg.what == MESSAGE_BLINK) { if (mBlink) { mBlinkState = ! mBlinkState; makeBlink(); } invalidate();} invalidate(); return true; } return false; }}); } private void makeBlink() { Message message = mHandler.obtainMessage(MESSAGE_BLINK); mHandler.sendMessageDelayed(message, BLINK_DELAY); } @override protected void onAttachedToWindow() {super.onattachedToWindow (); mBlink = true; MBlinkState = true; makeBlink(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mBlink = false; mBlinkState = true; mHandler.removeMessages(MESSAGE_BLINK); }Copy the code
Internally through the Handler, onAttachedToWindow mBlink is true, after calling the makeBlink method, 500 milliseconds later Handler processing, because mBlink is true, so call makeBlink method again. So this is a FrameLayout that changes its state and refreshes every 500 milliseconds, which means it’s never been used.
3. Create a View using mFactory2
2.6.2 Factory2
public interface Factory2 extends Factory {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
public interface Factory {
public View onCreateView(String name, Context context, AttributeSet attrs);
}
Copy the code
Factory2 is an interface, mFactory2 is not empty, so the View will eventually be created through the implementation class of this interface.
AppCompatDelegateImpl implements this Factory2 interface. When is AppCompatDelegateImpl used? And when and where is mFactory2 assigned?
Take a look at the AppCompatActivity source
2.6.3 AppCompatActivity
protected void onCreate(@Nullable Bundle savedInstanceState) { AppCompatDelegate delegate = this.getDelegate(); / / 1, obtain agent delegate. InstallViewFactory (); //2, initialize Factory delegate. OnCreate (savedInstanceState); . }Copy the code
Note 1: Get the agent
public AppCompatDelegate getDelegate() {
if (this.mDelegate == null) {
this.mDelegate = AppCompatDelegate.create(this, this);
}
return this.mDelegate;
}
Copy the code
Appcompatdelegate. create actually returns AppCompatDelegateImpl
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}
Copy the code
OnCreate note 2: a delegate. InstallViewFactory (), see AppCompatDelegateImpl inside
public void installViewFactory() { LayoutInflater layoutInflater = LayoutInflater.from(this.mContext); // If getFactory is null, Is set Factory2 if (layoutInflater. GetFactory () = = null) {LayoutInflaterCompat. SetFactory2 (layoutInflater, this); }}Copy the code
LayoutInflaterCompat#setFactory2
public static void setFactory2(@NonNull LayoutInflater inflater, @NonNull Factory2 factory) { inflater.setFactory2(factory); Factory2: Factory2: Factory2: Factory2: Factory2: Factory2: Factory2: Factory2 if (VERSION.SDK_INT < 21) { Factory f = inflater.getFactory(); If (f instanceof Factory2) {// Force Factory2 forceSetFactory2(inflater, (Factory2)f); } else { forceSetFactory2(inflater, factory); }}}Copy the code
LayoutInflaterCompat#forceSetFactory2
Just look at the key code:
private static void forceSetFactory2(LayoutInflater inflater, Factory2 factory) {/ / reflection to LayoutInflater mFactory2 field sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2"); sLayoutInflaterFactory2Field.setAccessible(true); / / mandatory Settings sLayoutInflaterFactory2Field. Set (inflater, factory); }Copy the code
So Google is providing compatibility packages, v4, v7, and now recommended to replace androidx, in order to make some of the features of the high API available in the low version. Lower-version devices can also have features of higher-version apis as long as they inherit from AppCompactActivity.
View = mFactory2. OnCreateView (parent, name, context, attrs); AppCompatDelegateImpl’s onCreateView method can be compatdelegateImpl’s onCreateView method on compatDelegateImpl. If you don’t think you’re writing an interface that inherits an Activity, I’d frown on you.
2.6.4 AppCompatDelegateImpl# onCreateView
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) { return this.createView(parent, name, context, attrs); } public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (this.mAppCompatViewInflater == null) { TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme); // we can configure our own Inflater String viewInflaterClassName = a.getString(styleable.appcompattheme_VieWinFlaterClass); if (viewInflaterClassName ! = null && ! AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) { try { Class viewInflaterClass = Class.forName(viewInflaterClassName); this.mAppCompatViewInflater = (AppCompatViewInflater)viewInflaterClass.getDeclaredConstructor().newInstance(); } catch (Throwable var8) { Log.i("AppCompatDelegate", "Failed to instantiate custom view inflater " + viewInflaterClassName + ". Falling back to default.", var8); this.mAppCompatViewInflater = new AppCompatViewInflater(); }} else {/ / 2, if not specified, the default create AppCompatViewInflater enclosing mAppCompatViewInflater = new AppCompatViewInflater (); }}... / / 3, at last, by createView AppCompatViewInflater method to create the View return enclosing mAppCompatViewInflater. CreateView (the parent, the name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed()); }Copy the code
Comments 1 and 2 show that we can Inflater in a theme, and that we can customize the rules for creating views
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item Name = "viewInflaterClass" > android. Support. V7. App. AppCompatViewInflater < item > / / specified viewInflaterClass < / style >Copy the code
Note 3 AppCompatViewInflater’s createView method actually creates the View object.
2.6.5 AppCompatViewInflater# createView,
final View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) { ... switch(name.hashCode()) { ... case -938935918: if (name.equals("TextView")) { var12 = 0; } break; case 2001146706: if (name.equals("Button")) { var12 = 2; }}... Case 0: // 0 is a TextView tag view = this.createTextView(context, attrs); this.verifyNotNull((View)view, name); break; case 1: view = this.createImageView(context, attrs); this.verifyNotNull((View)view, name); break; case 2: view = this.createButton(context, attrs); this.verifyNotNull((View)view, name); break; . Case default: view = this.createView(context, name, attrs); } if (view == null && originalContext ! = context) {// if none of the above is created, createViewFromTag will save some views = this.createViewFromTag(context, name, attrs); } if (view ! = null) { this.checkOnClickListener((View)view, attrs); } return (View)view; }Copy the code
If you encounter a TextView, call createTextView.
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
Copy the code
So you see here, you just use AppCompactActivity, you put TextView in your layout, and you end up creating an AppCompatTextView.
Other ImageView, Button, no longer analysis, the reader can view the source code.
If you go through all the cases and you don’t create it, that’s okay, you can save it by calling createViewFromTag, adding a prefix, and then creating it by reflection, so let’s see
2.6.6 AppCompatViewInflater# createViewFromTag
Private View createViewFromTag(Context Context, String name, AttributeSet attrs) { If (name.equals("view")) {name = attrs.getAttributeValue((String)null, "class"); } View view; try { this.mConstructorArgs[0] = context; this.mConstructorArgs[1] = attrs; View var4; If (-1 == name.indexof (46)) {//sClassPrefixList defines: private static final String[] sClassPrefixList = new String[]{"android.widget.", "android.view.", "android.webkit."}; for(int i = 0; i < sClassPrefixList.length; ++ I) {//createViewByPrefix = this.createViewByPrefix(context, name, sClassPrefixList[I]); if (view ! = null) { View var6 = view; return var6; } } var4 = null; return var4; } // Null var4 = this.createViewByPrefix(context, name, (String)null); return var4; } catch (Exception var10) { view = null; } finally { this.mConstructorArgs[0] = null; this.mConstructorArgs[1] = null; } return view; }Copy the code
TextView in the layout XML, the class full path is Android.widget. TextView, so add the prefix Android.widget. To create a TextView object using full-path reflection, sClassPrefixList defines three prefixes: private static final String[] sClassPrefixList = new String[]{“android.widget.”, “android.view.”, “android.webkit.”}; Native controls are defined in these directories.
To create a View prefixed by the View name (null if it is our custom View), call the createViewByPrefix method
2.6.7 AppCompatViewInflater# createViewByPrefix
private View createViewByPrefix(Context context, String name, String prefix) throws ClassNotFoundException, InflateException { Constructor constructor = (Constructor)sConstructorMap.get(name); Try {// constructor == null) {// constructor == null); extends View> clazz = context.getClassLoader().loadClass(prefix ! = null ? prefix + name : name).asSubclass(View.class); constructor = clazz.getConstructor(sConstructorSignature); // Cache class constructor sconstructorMap. put(name, constructor); } constructor.setAccessible(true); return (View)constructor.newInstance(this.mConstructorArgs); } catch (Exception var6) { return null; }}Copy the code
Finally, a class is loaded through the ClassLoader, asSubclass(View.class); Is a strong conversion to a View object,
In this case, we use reflection to create a View object. For the performance of reflection, we cache the Class constructor. Key is the name of the Class.
At this point, you may have a question, why only analyze AppCompat, if the topic does not use AppCompat, then what is the process?
If AppCompat fails to create an AppCompatTextView, then android.Widget. TextView is created in the reflection area. The default process is similar.
Back to LayoutInflater# createViewFromTag
2.7 LayoutInflater# createViewFromTag
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, Boolean ignoreThemeAttr) {//1. If (name.equals("view")) {name = attrs.getAttributeValue(null, "class"); }... If (name.equals(TAG_1995)) {// Let's party like it's 1995! return new BlinkLayout(context, attrs); } try { View view; if (mFactory2 ! Factory2. OnCreateView (parent, name, context, attrs); factory2. } else if (mFactory ! OnCreateView (name, context, attrs); } else { view = null; } if (view == null && mPrivateFactory ! = null) {/ / 5, and create the View through mPrivateFactory View. = mPrivateFactory onCreateView (the parent, the name, the context, attrs); } if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; If (-1 == name.indexof ('.')) {// Native control does not have. Call this onCreateView method to create view = onCreateView(parent, name, attrs); } else {// The full name of the custom control is. Null view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; }}Copy the code
If there is no proxy class or a proxy class that cannot be created, then use the default creation method, which is comment 6. LayoutInflater#onCreateView(String name, AttributeSet attrs
2.7.1 LayoutInflater#onCreateView(String name, AttributeSet attrs)
Protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {// The native controls provided by Android are Android.view. Return createView(name, "android.view.", attrs); } public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException {//2, Constructor<? extends View> constructor = sConstructorMap.get(name); Class<? extends View> clazz = null; Try {// there is no cache, {// Class not found in the cache, see if it's real, and try to add it clazz = mContext.getClassLoader().loadClass( prefix ! = null ? (prefix + name) : name).asSubclass(View.class); if (mFilter ! = null && clazz ! = null) { boolean allowed = mFilter.onLoadClass(clazz); if (! allowed) { failNotAllowed(name, prefix, attrs); } } constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else {// cached logic... } Object[] args = mConstructorArgs; args[1] = attrs; final View view = constructor.newInstance(args); If (view instanceof ViewStub) {// Use the same context when inflating ViewStub later. Final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } return view; }}Copy the code
1, pass the prefix android.view., native control under this package. 2, create View by reflection, reflection cost performance, so use caching mechanism. If you encounter a viewStub, give it a LayoutInflater
How to create the second layer View?
RInflateChildren (Parser, temp, attrs, true)
RInflateChildren (Parser, temp, attrs, true);
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
Copy the code
The rInflateChildren method internally calls the rinflatarinflate method, which is already analyzed above, in the same way that the merge tag process is resolved. Here basically the entire XML parsing into View object flow analysis is finished, need a simple summary, otherwise forget what we said before.
LayoutInflater summary
XML layout parsing, text summary as follows:
1. Use Pull parsing to parse the layout ID into an XmlResourceParser object. Briefly introduce several XML parsing methods: SAX, DOM and Pull parsing. Because you parsed the XML in step 1, you can inflate the tag’s View name. Call the rInflate method if it is a merge tag, or create a View object with the createViewFromTag method if it is a normal View. 3. Explain how the rInflate method resolves individual tags
- RequestFocus label
- The tag label
- The include tag
- The view tag
- Blink label
- The merge tag
Mainly on the above labels (in no particular order) processing, processing
The creation of the second View in XML also ends with the rInflate method. This is a recursive creation, and the View is created in depth-first order.
The createViewFromTag method is used to create a View. If it is a blink tag, then new new BlinkLayout() is returned.
The createViewFromTag method normally creates several logic for the View
- If mFactory2 is not empty, it is created by calling the mFactory2.onCreateView method
- If the mFactory is not empty, create it by calling the mFactory.onCreateView method
- If this fails, it is created using the default mPrivateFactory
- Add android.view to the View by calling LayoutInflater’s CreateView method. Prefix, and then create a View object using reflection. Given the performance cost of reflection, the Constructor of the class will be cached for the first successful creation, and then the same View will be created using the Constructor’s newInstance method.
Factory2 implements AppCompatDelegateImpl, and AppCompatDelegateImpl gives almost all of the lifecycle methods to AppCompatDelegateImpl. With A LayoutInflater set to Factory2, the View is created on the Factory2 interface, which is the AppCompatDelegateImpl implementation class.
This topic describes how AppCompatDelegateImpl creates a View. If the layout file is a TextView, it creates a compatible AppCompactTextView class. The same goes for other controls. If the creation fails, go back to the normal creation process, add a prefix such as Android.Widget.textView, and create it by reflection. 7. If you do not use a compatibility package, such as inheriting an Activity, then create a View object using reflection, and customize the View without prefixes.
3. UI optimization
The so-called UI optimization is to take down the time spent in the rendering process, find the bottleneck and optimize it.
You’ve analyzed the UI principles, the relationship between activities, Windows, DecorView, and ViewRootImpl, and how XML layout files are parsed into View objects.
Time consuming:
- View creation is in the main thread, including measure, layout, draw, when the interface is complex, this part may be time-consuming.
- Parsing the XML and reflecting the time it takes to create the VIew object.
The following are some common UI optimization methods
3.1 Conventional Mode
- Reduce UI hierarchy, use merge, Viewstub label optimization
- The optimized layout overhead, RelativeLayout, and Linearlayout with weight are measured multiple times. Try using ConstraintLayout instead.
- When you create a DecorView, it sets a default background for the DecorView. You can use a common background for the DecorView. You don’t need to set a background for the other parent controls.
3.2 XML to code
Writing a layout with XML is convenient, but you eventually have to parse the XML out via the LayoutInflater’s inflate method and recursively + reflection to create the View object, which can be time-consuming if the layout is complex.
Using code creation reduces this part of the View creation time with XML recursive parsing and reflection. Of course, if you write all the XML in code, you’re not going to be productive, and the code readability is a problem.
Palm read open source a library, compile time automatically XML into Java code, X2C
Its principle is to use APT (Annotation Processor Tool) + JavaPoet technology to complete the operation of the whole process during compilation [Annotation] – [Annotation] -> [translate XML] -> [generate Java]
During the compilation and generation of APK, the layout that needs to be translated is translated into the corresponding Java file. In this way, the developers write the layout in the original XML, but for the program, the runtime load is the corresponding Java file.
Extremely low intrusion, and removing annotations falls back to native runtime parsing. Of course, there are cases where conversions are not supported, such as the merge tag, whose parent cannot be determined at compile time.
3.3 Creating a View asynchronously
Create View through child thread, reduce main thread time.
private void threadNewView() {
new Thread(){
@Override
public void run() {
mSplashView = LayoutInflater.from(MainActivity.this).inflate(R.layout.activity_splash,null);
}
}.start();
}
Copy the code
Of course, this approach deals with synchronization issues, and it doesn’t address the time required to create the View in the first place, it just puts that time into the thread. UI updates must be switched to the main thread, otherwise the ViewRootImpl checkThread check will be triggered.
void checkThread() {
if(mThread ! = Thread.currentThread()) { throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views."); }}Copy the code
3.4 the reuse View
Reuse View this should be more common, like RecyclerView four cache, the purpose is to reduce the time to create a View by reuse View.
We can use the onDestroy method to clear the state of the View and put it in the cache. Hit the cache while onCreate and set the state.
3.5 Asynchronous layout: Litho
Under normal circumstances, measure, layout and draw are all executed in the main thread, and the final drawing operation is in the draw method, while measure and Layout just do some data preparation, which can be completely put into the child thread to do.
The principle of Litho is to put measure, layout in the child thread: github.com/facebook/li…
Advantages:
- Measure, layout, in the child thread to do, reduce the main thread time.
- Use your own layout engine to reduce the View hierarchy and make the interface flat.
- Optimize RecyclerView and improve cache hit ratio.
Disadvantages:
- Cannot preview in AS.
- Using your own layout engine has a bit of cost to use.
3.6 Flutter: Self-painted engine
Flutter is a cross-platform UI framework that integrates the Skia image library internally and takes over the image drawing process itself. The performance is as close to native as possible. It’s time to plan and learn
Summary of UI optimization
After the previous analysis of UI principles, we know that UI rendering mainly involves XML parsing, View measure, layout, draw and other time-consuming stages. UI optimization methods are summarized as follows:
- The conventional way.
- XML parsing and reflection create View time, use new instead of XML.
- Reuse the View
- Create the View asynchronously, putting the time spent creating the View in the child thread.
- Put the measure and layout in the child thread, representing:
Litho
.
The full text summary
This paper is divided into three sections:
- The first section analyzes the UI principle through the source code, mainly introduces the relationship between Activity, Window, DecorView, ViewRootImpl, and the creation process.
- Section 2 examines how LayoutInflater parses AN XML layout. Reflection is time-consuming to create a View, and caching is used internally.
- The third section introduces several UI optimization methods based on the principles of the first two sections.
UI optimization reference: Android development master class 21 | UI optimization (bottom) : how to optimize the UI rendering?
Interviewer: Here we go again: Have your app cards been blocked? Interviewer: Toutiao started very quickly. How do you think it might have been optimized?
Have questions in the comments section.
Stay tuned for more articles.