This is the fourth day of my participation in the August More text Challenge. For details, see: August More Text Challenge
The starting point for this article is the Activity’s setContentView method. I’m going to put in a sequence diagram for loading a layout, which involves relatively few classes.
1. Activity#setContentView()
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
Copy the code
Here getWindow() returns type Window, which is an abstract class with variable name mWindow. As you know from my previous Activity launch process, it is initialized in the Activity attach method. See the following code:
Activity#attach
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
}
Copy the code
Create an implementation class called PhoneWindow. There is only one implementation class called PhoneWindow. Next, go to PhoneWindow’s setContentView method and follow up.
2. PhoneWindow#setContentView
@Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); //------------------->2 } else if (! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); //------------------->2 } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb ! = null && ! isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }Copy the code
Comment 1 initializes the DecorView. That’s not the focus of this document. Let’s look at comment 2, which loads our resource iD with LayoutInflater:
3.LayoutInflater#inflate
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root ! = null); }Copy the code
A two-argument method calls a three-argument method. The third argument must be true because we’re wearing mContentParent. This value is initialized in the installDecor method.
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); if (DEBUG) { Log.d(TAG, "INFLATING from resource: "" + res.getResourceName(resource) + "" (" + Integer.toHexString(resource) + ")"); } final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); }}Copy the code
XMLResourceParser is responsible for loading and parsing our XML, with the inflate method. I’ve explained this part of the code in detail in working with layout loading freaks. In the createViewFromTag method inside, To reflect the View, look at the code:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } // Apply a theme wrapper, if allowed and one is specified. if (! ignoreThemeAttr) { final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId ! = 0) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle(); } if (name.equals(TAG_1995)) { // Let's party like it's 1995! return new BlinkLayout(context, attrs); }... View view; if (mFactory2 ! = null) {//------------->1 view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory ! = null) {//------------>2 view = mFactory.onCreateView(name, context, attrs); } else { view = null; } if (view == null && mPrivateFactory ! = null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); } if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { if (-1 == name.indexOf('.')) {//----------->3 view = onCreateView(parent, name, attrs); } else {//----------------->4 view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; . }Copy the code
Note 1: Determine if mFactory2 is set; Note 2: Determine if mFactory is set; If we set Factory, we call the corresponding onCreateView method, which can be used to do a lot of things, such as counting the drawing speed of layout controls and globally replacing a control, which I’ll cover in a later article. Note 3: Determine whether our control in XML is a custom control, if it is a custom control, generally we are XXX.MyView must contain. If there is a custom control, otherwise it is to enter note 4, system control, for example: TextView like this, go to the createView method:
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { Constructor<? extends View> constructor = sConstructorMap.get(name); if (constructor ! = null && ! verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); } Class<? extends View> clazz = null; try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); if (constructor == null) { // 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 { // If we have a filter, apply it to cached constructor if (mFilter ! = null) { // Have we seen this name before? Boolean allowedState = mFilterMap.get(name); if (allowedState == null) { // New class -- remember whether it is allowed clazz = mContext.getClassLoader().loadClass( prefix ! = null ? (prefix + name) : name).asSubclass(View.class); boolean allowed = clazz ! = null && mFilter.onLoadClass(clazz); mFilterMap.put(name, allowed); if (! allowed) { failNotAllowed(name, prefix, attrs); } } else if (allowedState.equals(Boolean.FALSE)) { failNotAllowed(name, prefix, attrs); } } } Object lastContext = mConstructorArgs[0]; if (mConstructorArgs[0] == null) { // Fill in the context if not already within inflation. mConstructorArgs[0] = mContext; } 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])); } mConstructorArgs[0] = lastContext; return view; . }Copy the code
You can see that we finally created the View using reflection, and we cached the View.
sConstructorMap.put(name, constructor);
Copy the code
That’s all for today. See you in the comments section if you have any questions