Hi, I’m Halliday, who blogs with a mind full of stuff. Today I’m going to talk about XML and Views on Android. I’ll start by analyzing the XML layout parsing inflate process, and then introduce some industry solutions such as:

Mention article:

  • JakeWharton: famousButterknife,
  • Android built-in: Bidirectional bindingDataBinding, leaving out findViewByIdViewBindingandKotlin extension,

Performance optimization:

  • Palm read: advance the XML to view process to compile timex2c,
  • Custom Factory to create view ideasViewOpt,
  • Tmall: compress XML into binary files, which can be dynamically distributed and streamed for parsingVirtualView,

The passage is about 5000 words and takes about 13 minutes to read. If individual big picture is fuzzy, can go to personal site to read.

inflate

Java layer

Source based on 29 and compileSdkVersion androidx. Appcompat: appcompat: 1.1.0

Usually, we are using XML in the development of the layout, the benefits of this is that one can drag and drop preview, the second is simple and clear syntax, and then in the Activity setContentView, you can complete the layout loading, what is the specific process? It is divided into three steps: IO reads the XML file, Parser parses the XML structure to get the view tree, and reflection creates the view. So let’s start with setContentView,

//AppCompatActivity.java
void setContentView(int layoutResID) {
    // Leave it to the proxy class
    getDelegate().setContentView(layoutResID);
}
 //AppCompatDelegateImpl.java void setContentView(int resId) {  // By default, the parent layout is specified as Content  ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);  //from obtain the service object through the system service  //inflate the layout, while specifying contentParent as the parent  LayoutInflater.from(mContext).inflate(resId, contentParent); } Copy the code

You can see that the core implementation is handed over to the LayoutInflater, following the Inflate method,

//LayoutInflater.java
View inflate(int resource, ViewGroup root, boolean attachToRoot) {
    // Try to precompile the view. Google is still working on this feature, so ignore it
    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if(view ! =null) {
 return view;  }  / / get XmlResourceParser  XmlResourceParser parser = res.getLayout(resource);  // Start parsing  return inflate(parser, root, attachToRoot); } Copy the code

XmlResourceParser is an interface that implements the XmlPullParser (parsing XML layout structures) and AttributeSet (parsing XML tag attributes) interfaces.

//LayoutInflater.java
The //inflate method has a comment that states that the preprocessed XML binary file is used for parsing, not the original file, which will be analyzed later
View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
    // Get the attributes of the XML tag
    final AttributeSet attrs = Xml.asAttributeSet(parser);
 View result = root;  // Locate the root node of the view tree  advanceToRootNode(parser);  final String name = parser.getName();  if (TAG_MERGE.equals(name)) {// The root node is the merge label  if (root == null| |! attachToRoot) { // Merge label must specify parent layout, otherwise throw exception  throw new InflateException("<merge /> can be used only with a valid "  + "ViewGroup root and attachToRoot=true");  }  / / parsing  rInflate(parser, root, inflaterContext, attrs, false);  } else {  // Get the root view  final View temp = createViewFromTag(root, name, inflaterContext, attrs);  ViewGroup.LayoutParams params = null;  if(root ! =null) {  // Generate parameters to the root view using the passed contentParent layout  params = root.generateLayoutParams(attrs);  if(! attachToRoot) { temp.setLayoutParams(params);  }  }  / / parsing  rInflateChildren(parser, temp, attrs, true);  if(root ! =null && attachToRoot) {  // The passed contentParent is the parent layout  root.addView(temp, params);  }  // Return the root view without passing in the parent layout  if (root == null| |! attachToRoot) { result = temp;  }  }  return result; } Copy the code

Follow up on rInflateChildren,

//LayoutInflater.java
void rInflateChildren(...).{
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

// Iterating, recursing (for example, there is another LinearLayout inside the LinearLayout) void rInflate(XmlPullParser parser, View parent, Context context,  AttributeSet attrs, boolean finishInflate){  / / the view tree depth  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();  // Perform different operations according to different tag names  if (TAG_REQUEST_FOCUS.equals(name)) {//requestFocus  pendingRequestFocus = true;  consumeChildElements(parser);  } else if (TAG_TAG.equals(name)) {//tag  / /...  } else if (TAG_MERGE.equals(name)) {//merge  throw new InflateException("<merge /> must be the root element");  } else {// Focus on  / / create the view  final View view = createViewFromTag(parent, name, context, attrs);  final ViewGroup viewGroup = (ViewGroup) parent;  final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);  / / recursion  rInflateChildren(parser, view, attrs, true);  viewGroup.addView(view, params);  }  }  / /... } Copy the code

Follow up createViewFromTag,

//LayoutInflater.java
View createViewFromTag(View parent, String name, Context context, 
                       AttributeSet attrs,boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        // If it is a lower-case view, take the value of the class attribute as the name
 name = attrs.getAttributeValue(null."class");  }  // Select a factory to create the view. You can use setFactory/setFactory2 to define the factory to interfere with the creation of the view  View view = tryCreateView(parent, name, context, attrs);  if (view == null) {// If the factory cannot handle the view, create it manually  if (-1 == name.indexOf('. ')) {  // If there is no package name like , prefix android.view.  // At run time, the actual instance is the subclass PhoneLayoutInflater, which first selects one of the three prefixes:  //android.widget. android.webkit. android.app.  // If none of the three prefixes can be found, give the parent class the prefix android.view.  view = onCreateView(context, parent, name, attrs);  } else {  // There is a package name  view = createView(context, name, null, attrs);  }  }  return view; } Copy the code

Follow up the createView,

//LayoutInflater.java
// Create a view by reflection
View createView(Context viewContext, String name,String prefix, AttributeSet attrs){
    // Fetch the constructor from the cache
    Constructor<? extends View> constructor = sConstructorMap.get(name);
 Class<? extends View> clazz = null;  / / load the class clazz = Class.forName(prefix ! =null ? (prefix + name) : name, false. mContext.getClassLoader()).asSubclass(View.class);  // If there is no constructor in the cache, use clazz to get the constructor with two parameters  if (constructor == null) {  //Class
                 [] mConstructorSignature = new Class[] {Context.class, AttributeSet.class}  constructor = clazz.getConstructor(mConstructorSignature);  }  / /...  // Reflection creates a view  View view = constructor.newInstance(args);  return view; } Copy the code

So that’s the normal process, if you have a factory, you can create the view in tryCreateView. With factories, you can do global things like switch skins, fonts, etc.

//LayoutInflater.java
View tryCreateView(View parent, String name,Context context,AttributeSet attrs) {
    View view;
    // Select a factory to create the view. You can use setFactory/setFactory2 to define the factory to interfere with the creation of the view
    if(mFactory2 ! =null) {
 // Create a view with a factory  view = mFactory2.onCreateView(parent, name, context, attrs);  } else if(mFactory ! =null) {  // Create a view with a factory  view = mFactory.onCreateView(name, context, attrs);  } else {  view = null;  }  if (view == null&& mPrivateFactory ! =null) {  // Create a view with a factory  view = mPrivateFactory.onCreateView(parent, name, context, attrs);  }  return view; } Copy the code

The overall flow chart is as follows.

Note that the current system’s AppCompatActivity does help us set up a default factory,

AppCompatActivity#onCreate ->

​ delegate.installViewFactory();

AppCompatDelegateImpl#installViewFactory ->

​ LayoutInflaterCompat.setFactory2(layoutInflater, this);

In AppCompatDelegateImpl,

AppCompatDelegateImpl#createView ->

return mAppCompatViewInflater.createView(…) ;

Seen in AppCompatViewInflater, some of our more common views are converted to AppCompat’s view, and their creation does not require reflection logic.

//AppCompatViewInflater.java
View createView(...). {
    switch (name) {
        case "TextView":
            view = createTextView(context, attrs);
 verifyNotNull(view, name);  break;  case "ImageView":  view = createImageView(context, attrs);  verifyNotNull(view, name);  break;  / /...  }  return view; }  protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {  //TextView is replaced with AppCompatTextView  return new AppCompatTextView(context, attrs); } Copy the code

Native layer

So who are the specific instances of Parser in the Java layer? Follow up XmlResourceParser Parser = Res.getLayout (Resource), and finally find xmlblock.parser, we try to follow the getName method of Parser, His implementation is given to nativeGetName in the Native layer,

Native source code is based on Android 9.0

Native function is dynamically registered, android_util_xmlblock.cpp:

//android_util_XmlBlock.cpp

// An array of native functions that require dynamic registration
static const JNINativeMethod gXmlBlockMethods[] = {
    { "nativeGetName"."(J)I", (void*) android_content_XmlBlock_nativeGetName }
 / /... }  static jint android_content_XmlBlock_nativeGetName(JNIEnv* env, jobject clazz,  jlong token){  ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);  if (st == NULL) {  return - 1;  }  return static_cast<jint>(st->getElementNameID()); } Copy the code

To see ResXMLParser’s getElementNameID method, resourceTypes.cpp:

//ResourceTypes.cpp
int32_t ResXMLParser::getElementNameID() const{
    if (mEventCode == START_TAG) {// At the beginning of the tag, such as 
        
        / / dtohl is what? todo1
        return dtohl(((const ResXMLTree_attrExt*)mCurExt)->name.index);
 }  if (mEventCode == END_TAG) {// At the end of the tag, such as   return dtohl(((const ResXMLTree_endElementExt*)mCurExt)->name.index);  }  return - 1; } Copy the code

To see what ResXMLTree_attrExt is, in resourcetypes.h:

//ResourceTypes.h
// is a structure
struct ResXMLTree_attrExt
{
    // The namespace of the current tag element
 struct ResStringPool_ref ns;  // The name of the current tag element, such as "View", is not a string, but a structure, see below  struct ResStringPool_ref name;  / /... };  // Struct with an int field representing the index in the string constant pool struct ResStringPool_ref {  // Start the index after ResStringPool_header (header) to find the position of the string in the pool in this table  uint32_t index; }; Copy the code

As you can see, when XML is processed in binary, multiple identical strings are compressed into a constant pool, such as:

Based on the location of the index field, you can know what the tag name is. The constant pool processing can reduce the size of the XML.

Todo1: what is dtohl? Google dtohl and you’ll find that these functions are defined in byteorder.h.

//ByteOrder.h
// The byte order is related to the device architecture. In the case of the ARM CPUS we use today, it is called small byte order.
#define DEVICE_BYTE_ORDER LITTLE_ENDIAN
#if BYTE_ORDER == DEVICE_BYTE_ORDER  // Return x without byte conversion
#define dtohl(x) (x)
#define dtohs(x) (x) #define htodl(x) (x) #define htods(x) (x) #else // Byte conversion is required #define dtohl(x) (android_swap_long(x)) #define dtohs(x) (android_swap_short(x)) #define htodl(x) (android_swap_long(x)) #define htods(x) (android_swap_short(x)) #endif  // The conversion operation is used static inline uint32_t android_swap_long(uint32_t v) {  return (v<<24) | ((v<<8) &0x00FF0000) | ((v>>8) &0x0000FF00) | (v>>24); }  static inline uint16_t android_swap_short(uint16_t v) {  return (v<<8) | (v>>8); } Copy the code

Hardy can’t follow us as far as he can. We know that the XML parsed at runtime is preprocessed binaries (as apK is packaged), so we can take a wild guess as to whether the run-time parsing is doing some streaming, pointer shifting, or other reads. For example, XML binaries are partitioned into various sections, such as file header, tag area, attribute area, string constant pool, and then parsed by means of pointer shift such as readShort, readLong, etc., to read the corresponding View tag and view attribute, similar to the process of THE JVM parsing bytecode. (Limited ability, only guess)

summary

  1. precompiledtryInflatePrecompiled: What Google is doing, not yet available, stay tuned.
  2. Preprocessing of XML files: binary compilation of XML is carried out during packaging to compress XML volume and improve parsing efficiency at runtime. (Guess: binary streaming, pointer shift operation, parse more efficiently than raw XML)

Butterknife

Butterknife processes annotations at compile time with Apt (annotation handler) and JavaPoet (tool that helps generate Java files) to create classes that save on the cumbersome operations of findViewById and setOnclickListener. The author of this project is no longer maintaining it. He recommended that we use ViewBinding, but let’s review it briefly

Introducing a dependency:

implementation 'com. Jakewharton: butterknife: 10.2.1'
annotationProcessor 'com. Jakewharton: butterknife - compiler: 10.2.1'
Copy the code

Simple use:

class ButterknifeActivity extends AppCompatActivity {
    @BindView(R.id.tv_name)
    TextView mTextView;
    @OnClick(R.id.tv_name)
    void submit(a) {
 Toast.makeText(this."click", Toast.LENGTH_SHORT).show();  }   void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_butterknife);  / / binding  mUnbinder = ButterKnife.bind(this);  // Direct access  mTextView.setText("butter knife");  } } Copy the code

Follow up the bind method,

//ButterKnife.java
static Unbinder bind(Activity target) {
    / / get DecorView
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
}  static Unbinder bind(Object target, View source) { Class<? > targetClass = target.getClass(); // Get the constructor  Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);  if (constructor == null) {  return Unbinder.EMPTY;  }  // Reflection creates instances of Unbinder  return constructor.newInstance(target, source); } Copy the code

Follow up findBindingConstructorForClass,

staticConstructor<? extends Unbinder> findBindingConstructorForClass(Class<? > cls) {    // Get the constructor from the cache
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if(bindingCtor ! =null || BINDINGS.containsKey(cls)) {
        return bindingCtor;
 }  String clsName = cls.getName();  // Classes in the Framework layer are not supported  if (clsName.startsWith("android.") || clsName.startsWith("java.")  || clsName.startsWith("androidx.")) {  return null;  }  // Load the ButterknifeActivity_ViewBinding class Class<? > bindingClass = cls.getClassLoader().loadClass(clsName +"_ViewBinding");  // Get the constructor with two parameters  bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);  // Cache the constructor  BINDINGS.put(cls, bindingCtor);  return bindingCtor; } Copy the code

The ButterknifeActivity_ViewBinding class is created by Butterknife, not much code,

class ButterknifeActivity_ViewBinding implements Unbinder {
    private ButterknifeActivity target;
    private View view7f0700b7;

    @UiThread
 public ButterknifeActivity_ViewBinding(final ButterknifeActivity target, View source) {  this.target = target;  View view;  // Source is a DecorView, where the edge is a simple source.findViewById(id)  view = Utils.findRequiredView(source, R.id.tv_name, "field 'mTextView' and method 'submit'");  // Force and assign to the mTextView of ButterknifeActivity  // So mTextView cannot be private. Private means you need to add reflection to the implementation, affecting performance  target.mTextView = Utils.castView(view, R.id.tv_name, "field 'mTextView'", TextView.class);  view7f0700b7 = view;  // Set the click event  view.setOnClickListener(new DebouncingOnClickListener() {  @Override  public void doClick(View p0) {  // Call the submit method in ButterknifeActivity  target.submit();  }  });  }   public void unbind(a) {  // Some cleanup logic when untying  this.target = null;  target.mTextView = null;  view7f0700b7.setOnClickListener(null);  view7f0700b7 = null;  } } Copy the code

As you can see, Butterknife uses reflection only when creating Unbinder instances, so the runtime performance impact is minimal. The usual process of Apt handling annotations and creating classes is not analyzed

Advantage:

  1. Omit findViewById, setOnclickListener these tedious operations
  2. Reflection operations are rare and have little impact on run-time performance

Disadvantages:

  1. Apt creates classes that increase IO time and class compilation time
  2. More classes mean larger package sizes

DataBinding ViewBinding/kotlin extension

DataBinding

DataBinding provides direct access to id controls in an XML layout using binding objects. DataBinding also enables bidirectional binding of data to the UI, i.e. data-driven UI refresh and UI operations that modify data

Simple use:

// app/build.gradle android{} switch
dataBinding {
    enabled = true
}
Copy the code

The XML layout is converted to a data binding layout, which is a layer of layout tags around the layout and an additional data tag to represent the data area.

<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    <data>
    </data>

 <LinearLayout>   <TextView  android:id="@+id/tv_name"/>  </LinearLayout> </layout> Copy the code

In the activity, get the binding object from DataBindingUtil,

class DBActivity extends AppCompatActivity {

    void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Get the binding object
 ActivityDBBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_d_b);  // Access the control directly  binding.tvName.setText("data binding");  } } Copy the code

So how does DataBinding do that? It’s also done by generating some extra classes, and if you’re interested in that, look at hardy’s earlier notes — DataBinding, We look at the generated class directly app/build/generated/data_binding_base_class_source_out/debug/out/com/holiday/srccodestudy/databinding/ActivityD BBinding. Java,

abstract class ActivityDBBinding extends ViewDataBinding {
    // The public TextView can be accessed directly
    public final TextView tvName;

    protected ActivityDBBinding(Object _bindingComponent, View _root,  int _localFieldCount,TextView tvName) {  super(_bindingComponent, _root, _localFieldCount);  this.tvName = tvName;  }   // Omit some inflate and bind methods } Copy the code

ViewBinding

ViewBinding eliminates bidirectional binding logic and is much lighter than DataBinding. It can be used in the same way but requires Android Studio 3.6 to start.

// app/build.gradle android{} switch
viewBinding {
    enabled = true
}
Copy the code

When turned on, Java classes are generated for all layouts by default, unlike DataBinding, which requires a layer of layout labels to be bundled. If ViewBinding is not required for individual layouts, add tools:viewBindingIgnore=”true” to the root TAB of the layout.

In an activity, it’s a little different,

class VBActivity extends AppCompatActivity {

    void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        / / to inflate
 ActivityVBBinding binding = ActivityVBBinding.inflate(LayoutInflater.from(this));  / / get rootView getRoot  setContentView(binding.getRoot());  // Access the control directly  binding.tvVb.setText("view binding");  } } Copy the code

I’m not going to focus on the implementation of ViewBinding, Directly see his classes generated app/build/generated/data_binding_base_class_source_out/debug/out/com/holiday/srccodestudy/databinding/ActivityVB Binding. Java, same path as DataBinding,

final class ActivityVBBinding implements ViewBinding {
    private final LinearLayout rootView;
    // The public TextView can be accessed directly
    public final TextView tvVb;

 private ActivityVBBinding(LinearLayout rootView, TextView tvVb) {  this.rootView = rootView;  this.tvVb = tvVb;  }   // Have one more getRoot method than DataBinding  public LinearLayout getRoot(a) {  return rootView;  }   // Omit some inflate and bind methods } Copy the code

ViewBinding eliminates DataBinding’s bidirectional binding (no need to deal with DataBinding’s annotations, expressions, etc.) and focuses on finding ViewByID, so it’s lighter and faster to compile.

Kotlin extension

If your project uses Kotlin, you can also use kotlin’s extension to avoid the findViewById operation.

Using the Kotlin extension,

// app/build.gradle
apply plugin: 'kotlin-android-extensions'
Copy the code

Used in an activity,

class KotlinActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_kotlin)
        // Access the control directly
 tv_kotlin.text = "Kotlin Extensions"  } } Copy the code

One obvious problem with using the Kotlin extension is the “streaking” of the controls. For example, when I type TV into an activity, it will prompt the controls for other pages.

If you accidentally import a control from another page, you will be fine at compile time and throw an exception at run time. That is, with the Kotlin extension, all the controls are in an unsafe streaking state.

Kotlin -> Show Kotlin Bytecode -> Decompile;

final class KotlinActivity extends AppCompatActivity {
    private HashMap _$_findViewCache;// Control cache

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 this.setContentView(1300004);  / / find the TextView  TextView var10000 = (TextView)this._$_findCachedViewById(id.tv_kotlin);  // At run time, the validity check will throw XXX must not be null if a control from another page is imported  Intrinsics.checkExpressionValueIsNotNull(var10000, "tv_kotlin");  // Use the control  var10000.setText((CharSequence)"Kotlin Extensions");  }   public View _$_findCachedViewById(int var1) {  if (this._$_findViewCache == null) {  this._$_findViewCache = new HashMap();  }  // Find the control in the cache  View var2 = (View)this._$_findViewCache.get(var1);  if (var2 == null) {  // If you don't find it on the first attempt, go to findViewById (null if you have a control from another page).  var2 = this.findViewById(var1);  this._$_findViewCache.put(var1, var2);  }  return var2;  } } Copy the code

As for how Kotlin inserted the code, the ability is limited, hardy does not know, there are friends who know the comment area to chat ~

summary

If you don’t do bidirectional data and UI binding, just to avoid findViewById, use a lighter ViewBinding instead of DataBinding. DataBinding and ViewBinding ensure null security and type safety while avoiding the tedious work of findViewById. That is, findViewById does not get null and View cast exception. Of course, these two methods also can not avoid the generation of class compilation time and the package size of the problem, should be combined with the specific scenario to use. As for the Kotlin extension, there is a problem with the control streaking, which is not recommended.

So far, the introduction of efficiency here, let’s start performance optimization article ~

x2c

X2c uses Apt+JavaPoet technology to transform THE XML layout into a View class at compile time, which saves the time of parsing XML at run time.

Introducing a dependency:

annotationProcessor 'com. Zhangyue. We: x2c - apt: 1.1.2'
implementation 'com. Zhangyue. We: x2c - lib: 1.0.6'
Copy the code

Simple use:

Declare an annotation to the layout file
@Xml(layouts = "layout_x2c_test")
class X2CActivity extends AppCompatActivity {

    void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);  setContentView(R.layout.activity_x2c);  // Get the view via X2C  View view = X2C.inflate(this, R.layout.layout_x2c_test, null);  } } Copy the code

Follow up X2C inflate,

//X2C.java

static View inflate(Context context, int layoutId, ViewGroup parent) {
    return inflate(context, layoutId, parent, true);
}
 // Load the XML file and detect if there is a Corresponding Java class, use a Java class, otherwise use LayoutInflater static View inflate(Context context, int layoutId, ViewGroup parent, boolean attach) {  // Get the Java class corresponding to the XML layout  View view = getView(context, layoutId);  if(view ! =null) {  if(parent ! =null) {  parent.addView(view);  }  return view;  } else {  // No Java classes, LayoutInflater parsing logic  return LayoutInflater.from(context).inflate(layoutId, parent, attach);  } }  static View getView(Context context, int layoutId) {  // Get the cached view creator  IViewCreator creator = sSparseArray.get(layoutId);  if (creator == null) {  // If no, one is generated  int group = generateGroupId(layoutId);  String layoutName = context.getResources().getResourceName(layoutId);  layoutName = layoutName.substring(layoutName.lastIndexOf("/") + 1);  String clzName = "com.zhangyue.we.x2c.X2C" + group + "_" + layoutName;  // Load the class X2C0_layout_x2c_test generated by Apt to reflect the view creator  creator = (IViewCreator) context.getClassLoader().loadClass(clzName).newInstance();  // Cache it  sSparseArray.put(layoutId, creator);  }  // Use the View creator to create the view  return creator.createView(context); } Copy the code

To see the view creator X2C0_layout_x2c_test,

class X2C0_layout_x2c_test implements IViewCreator {

    View createView(Context context) {
        return new com.zhangyue.we.x2c.layouts.X2C0_Layout_X2c_Test().createView(context);
    }
} Copy the code

Following X2C0_Layout_X2c_Test, you can see that the tags and attributes of the XML are parsed into the corresponding Settings of the Java class,

class X2C0_Layout_X2c_Test implements IViewCreator {

    View createView(Context ctx) {
        Resources res = ctx.getResources();
        LinearLayout linearLayout0 = new LinearLayout(ctx);
 linearLayout0.setGravity(Gravity.CENTER_HORIZONTAL);  linearLayout0.setOrientation(LinearLayout.VERTICAL);  / /...  TextView textView2 = new TextView(ctx);  LinearLayout.LayoutParams layoutParam2 = new LinearLayout.LayoutParams(  ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);  textView2.setId(R.id.tv);  textView2.setText("Copywriting Content");  textView2.setLayoutParams(layoutParam2);  linearLayout0.addView(textView2);  return linearLayout0;  } } Copy the code

Advantage:

  1. Advancing XML parsing to compile time eliminates the time and memory required for runtime parsing
  2. Reflection is used only when getting the View creator and has little impact on runtime performance

Disadvantages:

  1. Apt creates classes that increase IO time and class compilation time
  2. More classes mean larger package sizes

Therefore, it is usually used only for a few pages with high complexity and performance bottlenecks.

ViewOpt

The solution is optimized from the point of view of avoiding reflection by using a custom Factory to create the view, bypassing the reflection logic. The core process is to collect the set of views used in XML using merg.xml, then Apt generates a class to process the set, and then intervenes with the default Factory to insert its own view creation logic.

class BaseActivity extends AppCompatActivity {

    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        // Insert your own logic, leaving the creation of the view to the proxy class
        View view = ViewOpt.createView(name, context, attrs);
 if(view ! =null) {  return view;  }  // Go to the default factory  return super.onCreateView(parent, name, context, attrs);  } } Copy the code

For more details, go to Android’s “Step Back” layout load optimization reading ~

Extension: VirtualView

VirtualView is a set of solutions produced in the e-commerce business scenario of Tmall’s re-operation. It can be compiled into binary files (small size, fast parsing) by writing XML, and sent to the client for rendering, with dynamic capabilities. Take a look at Hardy’s previous series on Hardcore Virtualview.

Hardy speculates in the Inflate section: is the binary parsing of XML in Android operated in a streaming, pointer shift fashion? The reason for this is because I have seen similar operations in the VirtualView file format and template compilation article.

conclusion

In both the performance enhancement and performance optimization sections, we can see that different implementations are selected for different business scenarios and requirements. There is no perfect technology, only the right one

The resources

  • Summary – Inflate process analysis
  • Csdn-android explores the LayoutInflater setFactory
  • GitHub – butterknife
  • Nuggets – Jetpack Notes -DataBinding
  • GitHub – X2C
  • Nuggets – Android “Step back” layout loading optimization
  • GitHub – Virtualview & Nuggets – Hardcore Virtualview

This article was typeset using MDNICE