The introduction

  • This is the third article in our Android 10 source code analysis series
  • Branches: android – 10.0.0 _r14
  • Read the full text for about 15 minutes

In this article you will learn the following, and the answers will be provided at the end of the article

  • What do the three arguments to the inflate method of a LayoutInflater stand for?
  • How does the system process merge and include
  • Why does merge tag optimize layout?
  • How is a View instantiated in XML?
  • Why do complex layouts stall? What optimizations have been made for Android 10?
  • What is BlinkLayout?

0xA01 Android 10 下 载 : 0xA02 Android 10 下 载 : APK installation process analysis APK can probably be divided into two parts of code and resources, then THE loading of APK is also divided into two parts of code and resources, the loading of code involves the creation, start and scheduling of the process, this paper mainly analyzes the loading of resources, If you have not seen how APK is generated and the installation process of APK, you can click the link below to go to:

  • How is APK generated
  • APK installation process

1. The Android resource

Android resources are divided into two parts: Assets and RES

Assets resources

Assets are stored in the Assets directory, which contains some original files that can be organized in any way. These files are eventually packaged intact into APK files and retrieved from AssetManager, as shown below

AssetManager assetManager = context.getAssets();
InputStream is = assetManager.open("fileName");
Copy the code

Res resources

Res resources are stored in the RES directory of the main project. These resources generally generate a resource ID for us to use during compilation. Res directory includes animator, Anim, Color, Drawable, Layout, Menu, RAW, Values, XML, etc. Get the Resources object with getResource()

Resources res = getContext().getResources();
Copy the code

Resources. Arsc records information about all application resource directories, including the name, type, value, ID, and configured dimension information of each resource. If the resource is a file, Resouces will find the file name based on the resource ID, and AssetManger will find the specific resource based on the file name. For resources.arSC, see 0xA01 ASOP Application Framework: How is APK generated

2. Load and parse resources to View generation

The following code must not be very strange, in the Activity of a common few lines of code

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main_activity)
}
Copy the code

So let’s analyze what happens when we call setContentView, Then check the setContentView method in the Activity of frameworks/base/core/Java/android/app/Activity. The Java

Public void the setContentView (@ LayoutRes int layoutResID) {/ / is called PhoneWindow actually. The setContentView method getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }Copy the code

Call getWindow () method returns the mWindow, mWindow is Windowd object, in fact is the only class PhoneWindow. Call it the setContentView method

2.1 the Activity – > PhoneWindow

PhoneWindow is the only implementation class for Window, which has the following structure:

When calling the Activity. The setContentView method actually calls is PhoneWindow. The setContentView method frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

Public void setContentView(int layoutResID) {// mContentParent is FrameLayout with ID ID_ANDROID_CONTENT. If (mContentParent == null) {installDecor(); } else if (! HasFeature (FEATURE_CONTENT_TRANSITIONS)) {// FEATURE_CONTENT_TRANSITIONS, which flags if the current content is loaded and no animation is required, Will be calling removeAllViews mContentParent. RemoveAllViews (); } // Check if FEATURE_CONTENT_TRANSITIONS if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else {// Parse the specified XML resource file mLayOutinflater.inflate (layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb ! = null && ! isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }Copy the code
  • Check if mContentParent is empty. If it is, call installDecor method to generate mDecor and assign it to mContentParent
  • Use the FEATURE_CONTENT_TRANSITIONS flag to determine if transitions have been loaded
  • If FEATURE_CONTENT_TRANSITIONS is set, add a Scene to overlaunch, otherwise call mLayoutInflater.inflate(layoutResID, mContentParent) to parse the resource file, Create the View and add to the mContentParent View

2.2 PhoneWindow – > LayoutInflater

When calling PhoneWindow. The setContentView method, called after LayoutInflater. Inflate method, To parse the XML resource file frameworks/base/core/Java/android/view/LayoutInflater. Java

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root ! = null); }Copy the code

Inflate it has multiple overloaded methods, and the last one called is the inflate(resource, root, root! Method = null) frameworks/base/core/Java/android/view/LayoutInflater. Java

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); // Generate compiled_view.dex from XML and reflect the View to compiled_view.dex. View View = tryInflatePrecompiled(resource, res, root, root) is not supported in current release versions. attachToRoot); if (view ! = null) { return view; } // Get the resource parser XmlResourceParser XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); }}Copy the code

This approach does three main things:

  • Compiled_view.dex is generated from XML precompilation and the View is generated by reflection
  • Get XmlResourceParser
  • Parsing the View

Note: tryInflatePrecompiled is not supported in current releases.

Private void initPrecompiledViews() {// Precompiled layouts are not supported in this release. // enabled This is always false Boolean enabled = false; initPrecompiledViews(enabled); } private void initPrecompiledViews(boolean enablePrecompiledViews) { mUseCompiledView = enablePrecompiledViews; if (! mUseCompiledView) { mPrecompiledClassLoader = null; return; }... } View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root, Boolean attachToRoot () {// mUseCompiledView always false if (! mUseCompiledView) { return null; } / / obtain the resources need to parse file PKG and layout String PKG. = res getResourcePackageName (resource); String layout = res.getResourceEntryName(resource); Class clazz = class.forname ("" + PKG + ".CompiledView", false, mPrecompiledClassLoader); Method inflater = clazz.getMethod(layout, Context.class, int.class); View view = (View) inflater.invoke(null, mContext, resource); if (view ! = null && root ! // Add the generated view to the root layout XmlResourceParser Parser = res.getLayout(resource); try { AttributeSet attrs = Xml.asAttributeSet(parser); advanceToRootNode(parser); ViewGroup.LayoutParams params = root.generateLayoutParams(attrs); // If attachToRoot=true add to the root layout if (attachToRoot) {root.addView(view, params); SetLayoutParams (params); setLayoutParams(params); setLayoutParams(params); } } finally { parser.close(); } } return view; } catch (Throwable e) { } finally { } return null; }Copy the code
  • The tryInflatePrecompiled method is new in Android 10. This is an optimization that runs in the compiler, Because the more complex the layout file, the more time it takes XmlPullParser to parse the XML, tryInflatePrecompiled generates compiled_view.dex from the XML and then reflects the View. This reduces the time it takes the XmlPullParser to parse the XML and then determine whether to add it to the root layout based on the attachToRoot parameter or set the LayoutParams parameter back to the caller
  • A global variable mUseCompiledView is used to control whether tryInflatePrecompiled is enabled. MUseCompiledView is always false according to source code analysis

Now that you know about the tryInflatePrecompiled method, what do the three parameters in the inflate method mean

  • Resource: ID of the XML layout file to parse
  • Root: indicates the root layout
  • AttachToRoot: Whether to add to the parent layout root

Resource = resource ID, root = resource ID, attachToRoot = resource ID

  • When attachToRoot == true and root! = null, the newly parsed View is added to root, and root is returned as the result
  • When attachToRoot == false and root! = null, the newly parsed View is returned as a result, and root generates and sets LayoutParams for the newly parsed View
  • When attachToRoot == false and root == NULL, the newly parsed View is returned directly as the result

TryInflatePrecompiled returns an empty view. Resources getLayout is used to obtain XmlResourceParser

2.3 LayoutInflater – > Resources

XmlResourceParser is obtained by calling Resources’ getLayout method, GetLayout method again to invoke the Resources loadXmlResourceParser frameworks/base/core/Java/android/content/res/Resources. Java

public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException { return loadXmlResourceParser(id, "layout"); } XmlResourceParser loadXmlResourceParser(@AnyRes int id, @nonNULL String type) throws NotFoundException {// TypedValue Is used to store resources final TypedValue value = obtainTempTypedValue();  try { final ResourcesImpl impl = mResourcesImpl; // Get the XML resource and save it to TypedValue Imp.getValue (id, value, true); Type == typedValue.type_string) {// For the specified XML resource, Load the parser return impl. LoadXmlResourceParser (value. The string. The toString (), id, value. AssetCookie, type); } throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + " type #0x" + Integer.toHexString(value.type) + " is not valid"); } finally { releaseTempTypedValue(value); }}Copy the code

TypedValue is a dynamic data container that stores the resources of Resource, obtains THE XML resources and saves them to TypedValue, and then calls the loadXmlResourceParser method of ResourcesImpl to load the corresponding parser

2.4 Resources – > ResourcesImpl

ResourcesImpl implements Resource access, which includes the AssetManager and all caches, and uses the getValue method of Resource to retrieve XML resources and save them to TypedValue. The layout resource is then parsed by calling the loadXmlResourceParser method of ResourcesImpl frameworks/base/core/java/android/content/res/ResourcesImpl.java

XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie, @NonNull String type) throws NotFoundException { if (id ! = 0) { try { synchronized (mCachedXmlBlocks) { final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies; final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles; final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks; / / first from the cache lookup XML resource final int num = cachedXmlBlockFiles. Length; for (int i = 0; i < num; i++) { if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] ! = null && cachedXmlBlockFiles[I].equals(file)) {// Call newParser to build an XmlResourceParser object, Return cachedXmlBlocks[I]. NewParser (id); }} // If there is none in the cache, create an XmlBlock, And put it in the cache / / XmlBlock is compiled a XML file wrapper class final XmlBlock block. = mAssets openXmlBlockAsset (assetCookie, file); if (block ! = null) { final int pos = (mLastCachedXmlBlockIndex + 1) % num; mLastCachedXmlBlockIndex = pos; final XmlBlock oldBlock = cachedXmlBlocks[pos]; if (oldBlock ! = null) { oldBlock.close(); } cachedXmlBlockCookies[pos] = assetCookie; cachedXmlBlockFiles[pos] = file; cachedXmlBlocks[pos] = block; // Call the newParser method to build an XmlResourceParser object, returning to the caller return block.newParser(id); } } } catch (Exception e) { final NotFoundException rnf = new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id)); rnf.initCause(e); throw rnf; } } throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id)); }Copy the code

The newParser method is called after the XML resource is retrieved from the cache. If it is not in the cache, the AssetManger openXmlBlockAsset method is called to create an XmlBlock and put into the cache. XmlBlock is compiled a XML file wrapper class frameworks/base/core/Java/android/content/res/AssetManager. Java

XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException { Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { ensureOpenLocked(); // Call the native method nativeOpenXmlAsset, load the specified XML resource file, Final Long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName); if (xmlBlock == 0) { throw new FileNotFoundException("Asset XML file: " + fileName); } // Create the XmlBlock, encapsulate the XmlBlock, return to the caller final XmlBlock block = new XmlBlock(this, XmlBlock); incRefsLocked(block.hashCode()); return block; }}Copy the code

Finally, the native method nativeOpenXmlAsset is called to open the specified XML file and load the corresponding resources. Method to check the navtive NativeOpenXmlAsset frameworks/base/core/jni/android_util_AssetManager CPP

{"nativeOpenXmlAsset", "(JILjava/lang/String;) J", (void*)NativeOpenXmlAsset} static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint jcookie, jstring asset_path) { ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie); . const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie); std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table); status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true); asset.reset(); . return reinterpret_cast<jlong>(xml_tree.release()); }Copy the code
  • The NativeOpenXmlAsset method at the C++ layer creates the ResXMLTree object, which returns the address of ResXMLTree at the C++ layer
  • The return value of the Java layer nativeOpenXmlAsse t method xmlBlock is the address of the C++ layer’s ResXMLTree object, which is then wrapped into the xmlBlock and returned to the caller

When the xmlBlock is created, the newParser method is called, building an XmlResourceParser object and returning it to the caller

2.5 ResourcesImpl – > XmlBlock

XmlBlock is a wrapper class for compiled XML files. XmlResourceParser is responsible for traversing XML tags. Its real implementation is the internal XmlBlock class xmlBlock. Parser. The functions that actually perform XML traversal are implemented by XmlBlock. To improve efficiency, JNI calls native functions. Then check the newParser method frameworks/base/core/Java/android/content/res/XmlBlock. Java

Public XmlResourceParser newParser(@anyres int resId) {synchronized (this) {// mNative is the address of the ResXMLTree object in C++ layer if (mNative ! // build an object called ResXMLParser on the C++ layer. // build an object called ResXMLParser on the C++ layer. Return new Parser(nativeCreateParseState(mNative, resId), this); } return null; }}Copy the code

This method does two things

  • MNative is the address of the ResXMLTree object in the C++ layer. Call the native method nativeCreateParseState to build a ResXMLParser object in the C++ layer. Returns the address of the ResXMLParser object at the C++ layer
  • The Java layer takes the ResXMLParser address in the C++ layer, builds the Parser, wraps the ResXMLParser, and returns it to the caller

Then check the native methods nativeCreateParseState frameworks/base/core/jni/android_util_XmlBlock CPP

{"nativeCreateParseState", "(JI)J", (void*) android_content_XmlBlock_nativeCreateParseState } static jlong android_content_XmlBlock_nativeCreateParseState(JNIEnv* env, jobject clazz, jlong token, jint res_id) { ResXMLTree* osb = reinterpret_cast<ResXMLTree*>(token); if (osb == NULL) { jniThrowNullPointerException(env, NULL); return 0; } ResXMLParser* st = new ResXMLParser(*osb); if (st == NULL) { jniThrowException(env, "java/lang/OutOfMemoryError", NULL); return 0; } st->setSourceResourceId(res_id); st->restart(); return reinterpret_cast<jlong>(st); }Copy the code
  • Token corresponds to Java layer mNative and is the address of the ResXMLTree object in the C++ layer
  • Call the C++ layer android_content_XmlBlock_nativeCreateParseState method to find the ResXMLTree object based on the token
  • Build a ResXMLParser object in the C++ layer and return to the Java layer the address of the corresponding ResXMLParser object in the C++ layer
  • The Java layer gets the address of ResXMLParser in the C++ layer and wraps it in Parser

2.6 Back to LayoutInflater

After a series of jumps, we end up calling the xmlBlock. newParser method to get the resource resourceParser XmlResourceParser, and then go back to the inflate method where LayoutInflater is invoked. Then call rInflate method resolution View frameworks/base/core/Java/android/View/LayoutInflater. Java

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, Boolean attachToRoot) {synchronized (mConstructorArgs) {// obtain context final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; // Store the root layout View result = root; Try {// Handle START_TA G and END_TAG advanceToRootNode(parser); final String name = parser.getName(); // Merge tag, RInflate method will merge under the label of all child view added to the root layout / / this is why the merge tag can simplify the layout of the effect of the if (TAG_MERGE. Equals (name)) {if (root = = null | |! attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } // Parse all views under the merge tag and add rInflate(parser, root, inflaterContext, attrs, false) to the root layout; } else {// If it is not a merge tag, call createViewFromTag to parse the layout view, Top View final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; // If the root layout is not empty and attachToRoot is false, set the layout parameter if (root! = null) {/ / obtain the root layout LayoutParams params = root. GenerateLayoutParams (attrs); // attachToRoot to false, set LayoutParams if (! attachToRoot) { temp.setLayoutParams(params); }} rInflateChildren(Parser, temp, attrs, true); // If root is not empty and attachToRoot is false, add the parsed View to the root layout if (root! = null && attachToRoot) { root.addView(temp, params); } / / if the root layout is empty or attachToRoot is false, returns the current View of the if (root = = null | |! attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { final InflateException ie = new InflateException(e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (Exception e) { throw ie; } finally { } return result; }}Copy the code
  • To parse merge tags, merge tags must have a parent layout and depend on the parent layout to load
  • The rInflate method adds all views under the merge tag to the root layout
  • If it’s not a merge tag, call createViewFromTag to parse the layout View and return temp, which is actually the Top View in our XML
  • Calling the rInflateChildren method, passing in the temp parameter, inside the rInflateChildren method calls the rInflate method, which parses any child views under the current View

To see what the attachToRoot and root parameters mean, here’s a summary: *

  • When attachToRoot == true and root! = null, the newly parsed View is added to root, and root is returned as the result
  • When attachToRoot == false and root! = null, the newly parsed View is returned as a result, and root generates and sets LayoutParams for the newly parsed View
  • When attachToRoot == false and root == NULL, the newly parsed View is returned directly as the result

Either the merge tag or the merge tag ends up calling the rInflate method to parse the View tree. The difference is that if the merge tag passes the parameter finishInflate to false, If it isn’t the merge tag parameters passed finishInflate is true frameworks/base/core/Java/android/view/LayoutInflater. Java

void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, Boolean finishInflate) throws XmlPullParserException, IOException {// Obtain the depth of the number final int depth = parser.getDepth(); int type; boolean pendingRequestFocus = false; // View by View 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)) {// Android :focusable="true", obtain the focus of the View pendingRequestFocus = true; consumeChildElements(parser); } else if (tag_tag.equals (name)) {// Parse android:tag parseViewTag(parser, parent, attrs); } else if (tag_include.equals (name)) { 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 {final View View = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); // The rInflateChildren method is called internally by the rInflateChildren method, which plodes through all the child views rInflateChildren(Parser, View, attrs, true); // Add parsed View viewgroup. addView(View, params); } } if (pendingRequestFocus) { parent.restoreDefaultFocus(); } // If finishInflate is true, the onFinishInflate method is called if (finishInflate) {parent.onfinishinflate (); }}Copy the code

The entire View tree parse process is as follows:

  • Gets the depth of the View tree
  • View by View parsing
  • Parse Android :focusable=”true” to get the focus of the View
  • Parsing android: Tag tag
  • Resolve include tags, and include tags cannot be used as the root layout
  • Merge tags are parsed and must be used as the root layout
  • According to the element name resolution, generate the corresponding View
  • The rInflate method, called inside the rInflateChildren method, parses all child views through depth-first traversal
  • Add parsed views

Note: By analyzing the source code, the following points need to be noted

  • The include tag cannot be the root element and needs to be placed in the ViewGroup
  • The merge tag must be the root element. To use the merge tag, it must have a parent layout and be loaded depending on the parent layout
  • When XmlResourseParser traverses the XML, the more complex the layout, the more layers are nested and the longer it takes, so you can optimize the layout by using the meger tag to reduce the nesting of layers

Call createViewFromTag during parsing, parse the element name, generate the View, Next check createViewFromTag method frameworks/base/core/Java/android/view/LayoutInflater. Java

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) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } // If theme is set, build a ContextThemeWrapper 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 {// If name is blink, create BlinkLayout // If factory, View = tryCreateView(parent, name, context, attrs); // If the tryCreateView method returns an empty View, call onCreateView. CreateView if (View == NULL) {Final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; Try {// if you use CustomView, you need to specify the full path in XML, // for example: com.hi.dhl.CustomView, then there is a. // We can use this to determine the built-in View, If (-1 == name.indexof ('.')) {View = onCreateView(context, parent, name, attrs); } else {View = createView(context, name, null, attrs); } /** * The onCreateView method is different from the createView method * onCreateView method: gives the built-in View a prefix, such as: Android. widget, which eventually calls createView * createView: */} Finally {mConstructorArgs[0] = lastContext; } } return view; } catch (InflateException e) { throw e; } catch (ClassNotFoundException e) { throw ie; } catch (Exception e) { throw ie; }}Copy the code
  • Parse the View tag and, if theme is set, build a ContextThemeWrapper
  • Call the tryCreateView method, if name is blink, create BlinkLayout, if set factory, parse according to factory. This is the Hook entry left by the system, we can manually create View interference system. Add more features
  • If the tryCreateView method returns an empty View, onCreateView and createView methods are called respectively. OnCreateView parses the built-in View and createView parses the custom View

During parsing, will call tryCreateView method first, take a look at what did tryCreateView method within frameworks/base/core/Java/android/view/LayoutInflater. Java

public final View tryCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @nonnull AttributeSet attrs) {// BlinkLayout is a subclass of FrameLayout, an inner class in LayoutInflater, and if the current tag is TAG_1995, If (name.equals(TAG_1995)) {// Let's party like it's 1995! Let's party like it's 1995! , said to celebrate Easter 1995 return New BlinkLayout(Context, attrs); } // If (mFactory2!) {// If (mFactory2! = null) { view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory ! = null) { view = mFactory.onCreateView(name, context, attrs); } else { view = null; } if (view == null && mPrivateFactory ! = null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); } return view; }Copy the code
  • If name is blink, BlinkLayout is created and sent back to the caller
  • If factory is set, parse according to factory, which is the Hook entry left to us by the system. We can create a View manually by interfering with the system, adding more functions, such as night mode, and returning the View to the caller

The tryCreateView method is called. If the View returned by this method is empty, the onCreateView method is called to parse the built-in View, and the createView method is called to parse the custom View

What’s the difference between the onCreateView method and createView method

  • OnCreateView method: the createView method is called by adding a prefix to the built-in View, such as Android. widget
  • CreateView method: Builds a View object from the path name of the entire class using reflection

To look at the implementation of these two methods, LayoutInflater is an abstract class. We are actually using PhoneLayoutInflater, which has the following structure

PhoneLayoutInflater overwrites LayoutInflater’s onCreatView method, This method is to add a prefix to the built-in front View frameworks/base/core/Java/com/android/internal/policy/PhoneLayoutInflater Java

private static final String[] sClassPrefixList = { "android.widget.", "android.webkit.", "android.app." }; protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { for (String prefix : sClassPrefixList) { try { View view = createView(name, prefix, attrs); if (view ! = null) { return view; } } catch (ClassNotFoundException e) { } } return super.onCreateView(name, attrs); }Copy the code

The onCreateView method adds a prefix to the built-in View and then calls createView. The actual View building is still done inside LayoutInflater’s createView method. CreateView method according to the full path name of a class using reflection mechanism to build the View object frameworks/base/core/Java/android/View/LayoutInflater. Java

public final View createView(@NonNull Context viewContext, @NonNull String name, @Nullable String prefix, @Nullable AttributeSet attrs) throws ClassNotFoundException, InflateException { ... // If (constructor == null) {// If no constructor is found in the cache, use reflection to build the View object from the pathname of the entire Class. = null ? (prefix + name) : name, false, mContext.getClassLoader()).asSubclass(View.class); if (mFilter ! = null && clazz ! = null) { boolean allowed = mFilter.onLoadClass(clazz); if (! allowed) { failNotAllowed(name, prefix, viewContext, attrs); Constructor = clazz.getconstructor (mConstructorSignature); constructor = clazz.getconstructor (mConstructorSignature) constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else {// If the cache constructor is found in the cache if (mFilter! = null) { Boolean allowedState = mFilterMap.get(name); If (allowedState == null) {clazz = class.forname (prefix! = null ? (prefix + name) : name, false, mContext.getClassLoader()).asSubclass(View.class); . } else if (allowedState.equals(Boolean.FALSE)) { failNotAllowed(name, prefix, viewContext, attrs); }}}... Try {// Create View final View with constructor View = constructive.newinstance (args); If (View instanceof ViewStub) {// If it is a ViewStub, set LayoutInflater final ViewStub ViewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } return view; } finally { mConstructorArgs[0] = lastContext; } } catch (NoSuchMethodException e) { throw ie; } catch (ClassCastException e) { throw ie; } catch (ClassNotFoundException e) { throw e; } catch (Exception e) { throw ie; } finally { } }Copy the code
  • First look for the constructor in the cache and use it directly if one exists
  • If not, use reflection to build the View object from the pathname of the complete class

At this point, the process of finding and parsing APK layout XML resource files -> View generation is over here

conclusion

So let’s answer each of these questions in turn

What do the three parameters of the inflate of a LayoutInflater mean?

  • Resource: ID of the XML layout file to parse
  • Root: indicates the root layout
  • AttachToRoot: Whether to add to the parent layout root

Resource = resource ID, root = resource ID, attachToRoot = resource ID

  • When attachToRoot == true and root! = null, the newly parsed View is added to root, and root is returned as the result
  • When attachToRoot == false and root! = null, the newly parsed View is returned as a result, and root generates and sets LayoutParams for the newly parsed View
  • When attachToRoot == false and root == NULL, the newly parsed View is returned directly as the result

How does the system process merge and include

  • To use merge tags, you must have a parent layout and depend on the parent layout to load
  • Merge is not a ViewGroup, it is not a View, it is a declaration of views, waiting to be added to the root layout when the merge tag is used
  • The Merge tag must be the root element in XML
  • The opposite include cannot be the root element and needs to be placed in a ViewGroup
  • To use the include tag, you must specify a valid Layout attribute
  • It doesn’t matter if you use the include tag to parse the included layout

Why does merge tag optimize layout?

When the merge tag is encountered during parsing, the rInflate method is called, as shown in the following code

Final View View = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); // The rInflateChildren method is called internally by the rInflateChildren method, which plodes through all the child views rInflateChildren(Parser, View, attrs, true); // Add parsed View viewgroup. addView(View, params);Copy the code

Parse all the child views under the Merge tag and add them to the root layout

How is a View instantiated?

View is divided into system View and custom View, by calling onCreateView and createView methods for different processing

  • OnCreateView method: the createView method is called by adding a prefix to the built-in View, such as Android. widget
  • CreateView method: Builds a View object from the path name of the entire class using reflection

Why do complex layouts stall? What optimizations have been made for Android 10?

  • The traversal of XML by XmlResourseParser takes longer as the layout becomes more complex and the levels become more nested
  • Calls to onCreateView and createView methods are time-consuming to createView objects through reflection
  • On Android 10, the tryInflatePrecompiled method was added to reduce the time it takes XmlPullParser to parse XML. However, a global variable mUseCompiledView is used to control whether tryInflatePrecompiled is enabled. MUseCompiledView is always false according to the source code. So tryInflatePrecompiled is currently not available in release versions

What is BlinkLayout?

BlinkLayout inherits FrameLayout, a flickering layout in which wrapped content flickers all the time. Let’s party like it’s 1995 BlinkLayout is celebrating Easter 1995. If you are interested, take a look at the discussion on Reddit to see how the source code is implemented

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; // loop makeBlink() every 500ms; } // trigger dispatchDraw invalidate(); return true; } return false; }}); } private void makeBlink() {// Send delay 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); } @Override protected void dispatchDraw(Canvas canvas) { if (mBlinkState) { super.dispatchDraw(canvas); }}}Copy the code

BlinkLayout sends messages to the Handler every 500ms, loops through the handleMessage and calls the invalidate method. To trigger the dispatchDraw method, do a flash effect

reference

  • www.reddit.com/r/androidde…
  • Github.com/RTFSC-Andro…
  • www.yuque.com/beesx/beesa…

conclusion

Dedicated to share a series of Android system source code, reverse analysis, algorithm, translation, Jetpack source code related articles, is trying to write a better article, if this article is helpful to you to give a star, what is not written in the article clearly, or have better advice, welcome to leave a message, welcome to learn together, Moving forward together on the technological road.

Plan to establish a most complete and latest AndroidX Jetpack related components of the actual combat project and related components of the principle of analysis article, is gradually increasing Jetpack new members, the warehouse continues to update, you can go to check: Androidx-jetpack-practice, if this warehouse is helpful to you, please give me a thumbs up and I will finish more project practices for new members of Jetpack one after another.

algorithm

Since LeetCode has a large question bank, hundreds of questions can be selected for each category. Due to the limited energy of each person, it is impossible to brush all the questions. Therefore, I sorted the questions according to the classic types and the difficulty of the questions.

  • Data structures: arrays, stacks, queues, strings, linked lists, trees…
  • Algorithms: Search algorithm, search algorithm, bit operation, sorting, mathematics,…

Each problem will be implemented in Java and Kotlin, and each problem has its own solution ideas, time complexity and space complexity. If you like algorithms and LeetCode like me, you can pay attention to my LeetCode problem solution on GitHub: Leetcode-Solutions-with-Java-And-Kotlin, come to learn together And look forward to growing with you.

Android 10 source code series

I’m writing a series of Android 10 source code analysis articles. Knowing the system source code is not only helpful in analyzing problems, but also very helpful in the interview process. If you like to study Android source code as MUCH as I do, You can follow my Android10-source-Analysis on GitHub, and all articles will be synchronized to this repository.

  • How is APK generated
  • APK installation process
  • 0xA03 Android 10 source code analysis: APK loading process of resource loading
  • Android 10 source code: APK
  • Dialog loading and drawing process and use in Kotlin, DataBinding
  • More……

Tool series

  • Shortcuts to AndroidStudio that few people know
  • Shortcuts to AndroidStudio that few people know
  • All you need to know about ADB commands
  • How to get video screenshots efficiently
  • 10 minutes introduction to Shell scripting
  • How to package Kotlin + Android Databinding in your project

The reverse series

  • Dynamically debug APP based on Smali file Android Studio
  • The Android Device Monitor tool cannot be found in Android Studio 3.2