Series index
Android startup process
- Source code download and compilation
- Overview of the Android system startup process
- Init process source parsing
- Zygote process source code analysis
- SystemServer source code parsing
LayoutInflater source code details
update
【Android/ source code/interview 】LayoutInflater source code details
preface
In this article, we’ll look at LayoutInflater from a source code perspective. LayoutInflater instantiates an XML file as a view object
We’ll see that there are two major sources of time
- XmlResourseParser Traversal of XML
- Reflect the time taken to create the View object
These, in turn, are positively correlated with the complexity of Xml, and the more complex the Xml, the longer the recursive calls take, leading to what we call the caton problem
Overview of the overall process
Eggs: BlinkLayout
BlinkLayout is an inner class in LayoutInflater. It is itself a subclass of FrameLayout. If the current tag is TAG_1995, create a BlinkLayout that flashes every 0.5 seconds to hold the contents of the LayoutInflater
Source code comments are also very interesting, write Let’s Party like it’s 1995! “Is said to celebrate Easter 1995
LayoutInflater
public final View tryCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return newBlinkLayout(context, attrs); }...return view;
}
Copy the code
It is also very simple to use
<blink
android:layout_below="@id/iv_qr_code"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=Android Institute
android:textColor="# 157686"
android:textSize="55sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</blink>
Copy the code
The effect is as follows, which is also suitable for the blinking effect of the cursor in EditText
Scan the above TWO-DIMENSIONAL code to pay attention to the “Android Institute” public number, to obtain more learning materials!
Ps: Those who want to further study are concerned about it, but don’t hurry up and pay attention to a wave?
The creation of a LayoutInflater
An overview of
LayoutInflater is an abstract class that is not created by the App layer. Instead, it is created by calling static functions from() via LAYOUT_INFLATER_SERVICE, We ended up creating a subclass of LayoutInflater, PhoneLayoutInflater
Analysis of important functions
LayoutInflater.from(cxt)
This function is simple. It calls getSystemService() to get the corresponding system service based on the passed Context object and assigns the value to the LayoutInflater
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater = //LayoutInflate is a system service that ultimately returns' PhoneLahyoutInflater '
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
Copy the code
Context itself is an abstract class, and its actual instantiation object is ContextImpl. In this class’s getSystemService() function, the class that actually performs the retrieval of system services is SystemServiceRegistry, A ServiceFetcher is encapsulated to fetch the actual system services. All system services are stored in a map called SYSTEM_SERVICE_FETCHERS. This is actually a get method that fetches the corresponding system services from the map
LayoutInflater
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
Copy the code
SystemServiceRegistry
/** * Gets a system service from a given context. */
public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher<? > fetcher = SYSTEM_SERVICE_FETCHERS.get(name);returnfetcher ! =null ? fetcher.getService(ctx) : null;
}
Copy the code
The addition of the corresponding service, which calls the SYSTEM_SERVICE_FETCHERS put function, is done by registerService()
/** * Statically registers a system service with the context. * This method must be called during static initialization only. */
private static <T> void registerService(String serviceName, Class
serviceClass, ServiceFetcher
serviceFetcher)
{
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
Copy the code
The SystemServiceRegistry class contains a static block of code that registers all services. We only care about how LAYOUT_INFLATER_SERVICE is registered
static{... registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return newPhoneLayoutInflater(ctx.getOuterContext()); }}); . }Copy the code
As we mentioned earlier, this is where the PhoneLayoutInflater is finally created and returned, and the LayoutInflater creation process is finished
thinking
Why leave it to a system service instead of just creating an instance of PhoneLayoutInflater?
Instantiation of LayoutInflater layouts
The whole process
Call layoutInflater’s inflater() function and pass in the resId parameter of XML
Analysis of important functions
inflate
This function is our entry point to instantiate the Xml layout file as a View object
LayoutInflater
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) + ")");
}
View view = tryInflatePrecompiled(resource, res, root, attachToRoot); // This code must return null, because the current version of the precompiled Enable is false
if(view ! =null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource); // Get the xmlblock. Parser object
try {
return inflate(parser, root, attachToRoot);
} finally{ parser.close(); }}Copy the code
The inflate(Parser, root, attachToRoot) function is called here to parse the Xml layout
Here you see some familiar tags, such as include,merge, handled, see the source code and comments below for details
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {//XmlPullParser is an interface
// This function does the actual parsing of the XML into a view. Parser is the Parser object fetched based on the XML layout
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final inflateAttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root; // The view object to return
try {
advanceToRootNode(parser); // Determine and process START_TAG and END_TAG
final String name = parser.getName(); // Get the current label
if (DEBUG) {
System.out.println("* * * * * * * * * * * * * * * * * * * * * * * * * *");
System.out.println("Creating root view: "
+ name);
System.out.println("* * * * * * * * * * * * * * * * * * * * * * * * * *");
}
if (TAG_MERGE.equals(name)) { // If you use the merge tag
if (root == null| |! attachToRoot) {// To use merge tags, you must have a parent layout and rely on the parent layout to load, otherwise an exception will be thrown
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);// Recursive generates layout views
} else { // If the merge tag is not used, TMP is created as a temporary root node and is eventually assigned to result
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs); //// Creates a view based on the label name
ViewGroup.LayoutParams params = null;
if(root ! =null) { // If rootView is not empty
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs); // Generate layoutParams based on rootView
if(! attachToRoot) {// If attachToRoot is false
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params); // Set a temporary params}}if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if(root ! =null && attachToRoot) { // If root is not empty and attachToRoot is true
root.addView(temp, params); // Add temp to rootView
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null| |! attachToRoot) {// If root is empty and attachToRoot is false
result = temp; // Assign temp, the View at the root, to result}}}...return result; // Return the result}}Copy the code
rInflate
As you can also see from the code above, either the merge tag or the non-merge tag ends up calling the rInflate() function, which is used to recursively walk down the XML layout, and the createViewFromTag() function is called to reflect the View object
See the source code and comments below for details
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
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)) { // If you need the REQUEST_FOCUS tag
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) { // If it is a "tag" tag
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) { // If it is an Include tag
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs); // Parse the include tag
} else if (TAG_MERGE.equals(name)) { // Merge tag
throw new InflateException("<merge /> must be the root element"); // Throw an exception directly
} else { // Other tags
final View view = createViewFromTag(parent, name, context, attrs); // Create a view based on the Tag and reflection
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true); // Call the rInflate function recursivelyviewGroup.addView(view, params); }}if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if(finishInflate) { parent.onFinishInflate(); }}Copy the code
createViewFromTag()
Finally, we get to the highlight, which is the function that actually creates the View based on the parsed Tag
LayoutInflater
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();
}
try {
View view = tryCreateView(parent, name, context, attrs); // Try using Factory to create View objects
if (view == null) { // If tryCreateView returns null
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
/ / sample: com. Aiwinn. Base. Widget. CameraSurfaceView
if (-1 == name.indexOf('. ')) { // If the current Tag contains ".
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs); }}finally {
mConstructorArgs[0] = lastContext; }}returnview; }... }Copy the code
In this function, tryCreateView() is first called to retrieve the View object, and if null, createView() is further called to create the View object
public final View createView(@NonNull Context viewContext, @NonNull String name, @Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// Create a view based on Tag reflection
Objects.requireNonNull(viewContext);
Objects.requireNonNull(name);
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 itclazz = Class.forName(prefix ! =null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);// Merge prefix and name to get the corresponding Class object
if(mFilter ! =null&& clazz ! =null) {
boolean allowed = mFilter.onLoadClass(clazz);
if(! allowed) { failNotAllowed(name, prefix, viewContext, attrs); } } constructor = clazz.getConstructor(mConstructorSignature);// Get the constructor object
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else{... } Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
final View view = constructor.newInstance(args); // Create an instantiation object of the View based on the obtained constructor
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;
} finally {
mConstructorArgs[0] = lastContext;
}catch{... }}}Copy the code
The code here is actually quite time-consuming because it is created using reflection, which is generally 3 times slower than creating objects directly, and iReader’s X2C framework is optimized for this
snacks
Creation and acquisition of Resources
The Resources object, mResources, is created by createResources(), and the ResourcesManager class gets the Resources
ContextImpl
private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
final String[] splitResDirs;
final ClassLoader classLoader;
try {
splitResDirs = pi.getSplitPaths(splitName);
classLoader = pi.getSplitClassLoader(splitName);
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
return ResourcesManager.getInstance().getResources(activityToken,
pi.getResDir(),
splitResDirs,
pi.getOverlayDirs(),
pi.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfig,
compatInfo,
classLoader);
}
Copy the code
ResourcesManager
public @Nullable Resources getResources(@Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
final ResourcesKey key = newResourcesKey( resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfig ! =null ? new Configuration(overrideConfig) : null.// CopycompatInfo); classLoader = classLoader ! =null ? classLoader : ClassLoader.getSystemClassLoader();
return getOrCreateResources(activityToken, key, classLoader);
} finally{ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); }}Copy the code
ContextImpl
@Override
public Resources getResources(a) {
return mResources;
}
Copy the code
From this we can also infer that LayoutInflaters are created by services because they need to obtain certain resources that system services can obtain
XmlBlock
The inflate() function is also involved in an important class, XmlResourceParser, which is responsible for iterating through XML tags, and whose real implementation class is the internal XmlBlock class xmlBlock. Parser, The functions that actually perform XML traversal are implemented by XmlBlock. For efficiency, these functions are implemented by JNI calling native functions at android_util_xmlblock-cpp
XmlBlock.java
@FastNative
/*package*/ static final native int nativeNext(long state);
@FastNative
private static final native int nativeGetNamespace(long state);
@FastNative
/*package*/ static final native int nativeGetName(long state);
@FastNative
private static final native int nativeGetText(long state);
@FastNative
private static final native int nativeGetLineNumber(long state); .Copy the code
“android_util_XmlBlock.cpp`
static jint android_content_XmlBlock_nativeNext(JNIEnv* env, jobject clazz, jlong token)
{
ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
if (st == NULL) {
return ResXMLParser::END_DOCUMENT;
}
do {
ResXMLParser::event_code_t code = st->next();
switch (code) {
case ResXMLParser::START_TAG:
return 2;
case ResXMLParser::END_TAG:
return 3;
case ResXMLParser::TEXT:
return 4;
case ResXMLParser::START_DOCUMENT:
return 0;
case ResXMLParser::END_DOCUMENT:
return 1;
case ResXMLParser::BAD_DOCUMENT:
goto bad;
default:
break; }}while (true);
bad:
jniThrowException(env, "org/xmlpull/v1/XmlPullParserException"."Corrupt XML binary file");
return ResXMLParser::BAD_DOCUMENT;
}
Copy the code
tryInflatePrecompiled
This function is a new function added to Android10’s source code. It is an optimization to use reflection to generate a View from the dex generated by XML precompilation to reduce the time it takes the XmlPullParser to parse THE XML. Reflection retrieves the View directly to the pre-compiled View object without recursively calling the rInflate
This is basically a real and complete solution to the complex layout caused by the lag problem
View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
boolean attachToRoot) {
if(! mUseCompiledView) {return null;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");
// Try to inflate using a precompiled layout.
String pkg = res.getResourcePackageName(resource);
String layout = res.getResourceEntryName(resource);
// View objects are reflected from the mPrecompiledClassLoader
try {
Class clazz = Class.forName("" + pkg + ".CompiledView".false, mPrecompiledClassLoader); // Get the Class of the precompiled view object
Method inflater = clazz.getMethod(layout, Context.class, int.class);
View view = (View) inflater.invoke(null, mContext, resource);
if(view ! =null&& root ! =null) {
// We were able to use the precompiled inflater, but now we need to do some work to
// attach the view to the root correctly.
XmlResourceParser parser = res.getLayout(resource);
try {
AttributeSet attrs = Xml.asAttributeSet(parser);
advanceToRootNode(parser);
ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
if (attachToRoot) {
root.addView(view, params);
} else{ view.setLayoutParams(params); }}finally{ parser.close(); }}return view;
} catch (Throwable e) {
if (DEBUG) {
Log.e(TAG, "Failed to use precompiled view", e); }}finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return null;
}
Copy the code
Write in the last
In the next article, we’ll come up with some optimizations to solve (or slow down) the lag caused by complex layouts. Stay tuned!
Reference article:
1. https://www.reddit.com/r/androiddev/comments/3sekn8/lets_party_like_its_1995_from_the_layoutinflater/
2. https://www.cnblogs.com/liyilin-jack/p/10282385.html
3. https://blog.csdn.net/axi295309066/article/details/60128009
4. https://github.com/RTFSC-Android/RTFSC/blob/master/BlinkLayout.md
5. https://juejin.cn/post/6844903785219751944
Copy the code
Solemnly declare
The copyright of this article belongs to Android Institute. It is prohibited to be reproduced without permission.