The layout we write resides in a DecorView, which is a member variable of the PhoneWindow, which in turn is a member variable of the Activity. We call this the layout hierarchy.
1. Analysis of layout hierarchy
1. SetContentView analysis
After calling our Activity’s onCreate lifecycle method, we then call the setContentView method to display the layout we passed in. How does this work?
The related method calls are shown below. The setContentView method calls the setContentView method of the parent Activity, and the setContentView method of getWindow is called in comment 1. Note 2 is the implementation of getWindow, return mWindow, mWindow is a variable of type Window; Comment 3 assigns the Activity member variable mWindow. PhoneWindow is the only implementation class of window. Where is the attach method called? This is called in the ActivityThread performLaunchActivity method, which is actually called in the AMS and Activty startup process, which we will analyze separately later.
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); //1 initWindowDecorActionBar(); } public Window getWindow() { return mWindow; //2 } final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) { attachBaseContext(context); mWindow = new PhoneWindow(this, window, activityConfigCallback); //3,,,}Copy the code
2, mWindow. The setContentView (layoutResID)
Note 1 is used to generate the DecorView and mContentParent; note 1 is used to generate the DecorView and mContentParent. Comment 2 is used to set layoutResID(the layout ID we implemented) to mContentParent;
public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); //1},,, mLayOutInflater.inflate (layoutResID, mContentParent); //2,,,}Copy the code
3, installDecor ()
The mDecor variable is generated by the generateDecor method at comment 1. Inside the generateDecor method, there is a new DecorView. At comment 2, the mContentParent is obtained by generateLayout. MDecor, a member variable of PhoneWindow, is of type DecorView and inherits from FrameLayout. MContentParent stands for mDecor itself or the subdecor of mDecor. There is a placeholder View at the top of the mDecor, which loads different decorViews according to the theme. If there is no top View loaded, the mContentParent decor is mDecor.
Private void installDecor() {,,, mDecor = generateDecor(-1); //1,, mDecor. SetWindow (this); ,,, mContentParent = generateLayout(mDecor); / / 2}Copy the code
4. Layout level
After the above analysis, we found that the Activity holds a PhoneWindow member variable; The PhoneWindow holds a member variable of a DecorView, and inside the DecorView holds an mContentParent, So the layout hierarchy is Activity — PhoneWindow — DecorView — mContentParent;
The XML layout we wrote is in the mContentParent; So how does the XML layout load into the mContentParent container? This is the mLayOutinflater.inflate (layoutResID, mContentParent) of the annotation 2 in Step 2. We will look at the loading process in the next section.
Second, layout loading process analysis
1. The call chain of mLayOutinflater.inflate (layoutResID, mContentParent) is shown below;
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null); //1
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot); //2
} finally {
parser.close();
}
}
Copy the code
2. The inflate method is eventually called, and root is assigned to result at comment 1. Comment 2 creates the root View with createViewFromTag; Comment 4 is for any children inflate; To explain the last attachToRoot parameter, if set to true, comment 5 is called to add the root view to root and return root; If set to false, comment 3 is called to set root to the root view, but addView is not performed. Comment 6 is then called to assign the root view to result, which returns the root view instead of root.
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { View result = root; //1 final String name = parser.getName(); // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, inflaterContext, attrs); //2 ViewGroup.LayoutParams params = null; if (root ! = null) { // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (! attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); //3 } } // Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true); //4 // 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); //5 } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || ! attachToRoot) { result = temp; //6 } return result; }Copy the code
Create the root View with createViewFromTag. Comment 1 shows the view being created for the first time; Both comments 2 and 3 end up calling the method at comment 3; Note 1 is one case, and note 3 is the other, which we will discuss separately;
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
View view = tryCreateView(parent, name, context, attrs); //1
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs); //2
} else {
view = createView(context, name, null, attrs); //3
}
}
}
return view;
}
Copy the code
3-1 tryCreateView
In the first case, the tryCreateView method is shown below. If mFactory2 is not empty, the view is created using the onCreateView method of mFactory2 in comment 1. If the mFactory is not empty, the view is created from the onCreateView of the mFactory in comment 2. So what are these two factories? As you can see from comments 3 and 4, Factory is a single-method interface. Factory2 inherits from Factory and overloads the onCreateView method. Both of these variables are initially null, so we developers can implement these two interfaces, and then assign to these two variables, and the view will be loaded preferentially through the methods we set;
public final View tryCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { View view; if (mFactory2 ! = null) { view = mFactory2.onCreateView(parent, name, context, attrs); //1 } else if (mFactory ! = null) { view = mFactory.onCreateView(name, context, attrs); //2 } else { view = null; } return view; } private Factory mFactory; //3 private Factory2 mFactory2; //4 public interface Factory { View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs); } public interface Factory2 extends Factory { View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs); }Copy the code
3-2 createView
Because mFactory and mFactory2 are not assigned, it goes to createView; The method code looks like this, with comment 1 first getting the constructor from sConstructorMap, which must be empty the first time; Then go to comment 2 and get the constructor by reflection. The mConstructorSignature parameter represents a two-parameter constructor, so it returns a two-parameter constructor. Then put the constructormap into sConstructorMap in comment 3; Finally, in comment 4, newInstance method is used to obtain the View instance and return it.
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name); //1
if (constructor == null) {
constructor = clazz.getConstructor(mConstructorSignature); //2
constructor.setAccessible(true);
sConstructorMap.put(name, constructor); //3
}
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
final View view = constructor.newInstance(args); //4
return view;
}
Copy the code
AndroidX can change the source code for AppCompatActivity
When an Activity inherits from AppCompatActivity, the setContentView method will call the setContentView of AppCompatActivity. So you end up calling the setContentView of AppCompatDelegateImpl;
AppCompatActivity -- setContentView
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
AppCompatActivity -- getDelegate
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
AppCompatDelegate -- create
public static AppCompatDelegate create(@NonNull Activity activity,
@Nullable AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, callback);
}
Copy the code
AppCompatDelegateImpl setContentView method is shown below, ensureSubDecor() at comment 1 will eventually call installDecor method. Comment 2 is used to get the contentParent; Comment 3 is used to add our layout to contentParent;
public void setContentView(int resId) { ensureSubDecor(); //1 ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); //2 contentParent.removeAllViews(); LayoutInflater.from(mContext).inflate(resId, contentParent); //3 mAppCompatWindowCallback.getWrapped().onContentChanged(); }Copy the code