Jetpck is the real luxury bucket

The introduction

  • View binding makes it easier to write code that interacts with views.
  • After view binding is enabled in a module, a binding class is generated for each XML layout file in that module.
  • Instances of the binding class contain direct references to all views that have ids in the corresponding layout.
  • In most cases, view binding replaces findViewById.

The overall preview

1. Instructions

1.1 Environment Configuration

1.1.1 Version requirements

ViewBinding is available in Android Studio 3.6 Canary 11 and later.

1.1.2 Enabling modules

//build.gradle
android {
        buildFeatures {
            viewBinding true
        }
    }
Copy the code

1.2 Syntax description

1.2.1 Layout Layout file

<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>Copy the code

Generation rule: Convert the name of the XML file to camel case and add the word “Binding” to the end. For example, if the file above is item_rv. XML, the bound class for ItemRvBindiing will be generated. Binding content: Generate binding classes that contain references to the root view as well as all views with ids. If the view does not add an ID, the corresponding bound class reference is not generated. Root view: Each binding class also contains a getRoot() method that provides a direct reference to the root view of the corresponding layout file. For example, the LinearLayout root view above.

1.2.2 Layout file ignored

<LinearLayout
            ...
            tools:viewBindingIgnore="true" >
        ...
    </LinearLayout>
Copy the code

Build optimization & Memory optimization: Enabling ViewBinding will generate binding classes for all layout files. If some layout files do not require binding class generation, you can add Settings to the root layout to disable it. Apk Slimming optimization: In release, shrinkResources can be added to unpack unused bound classes in Apk.

1.3 Scenario Examples

1.3.1 Activity

class ViewBindingActivity : AppCompatActivity() { private lateinit var binding: ActivityViewBindingBinding override fun onCreate(savedInstanceState: Bundle?) {super.oncreate (savedInstanceState) // traditional // setContentView(r.layout.activity_view_binding) //ViewBinding binding = ActivityViewBindingBinding.inflate(layoutInflater); Val view = binding. Root the setContentView (view) / / ViewBinding operation demonstrates binding. Rename. SetOnClickListener {binding. Name. Text = binding.name.text.toString() + "-rename" } } }Copy the code

Three steps

  • Call the static contained in the generated binding classinflate()Methods. This action creates an instance of the binding class for the Activity to use.
  • By calling thegetRoot()Method or use the Kotlin attribute syntax to get a reference to the root view.
  • Pass the root view tosetContentView(), making it an active view on the screen.

1.3.2 fragments

class ViewBindingFragmentSample : Fragment() { private var _binding: FragmentViewBindingBinding? = null private val binding get() = _binding!! override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle? ) : View? {// Traditional // return inflater.inflate(r.layout. fragment_view_binding, container, false) //style 1 _binding = FragmentViewBindingBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, SavedInstanceState) / / style 2 (you need to call onCreateView) in the traditional way. / / _binding = FragmentViewBindingBinding bind (view)} override fun  onDestroyView() { super.onDestroyView() _binding = null } }Copy the code

Three steps

  • Call the static contained in the generated binding classinflate()Methods. This creates an instance of the bound class for use by the Fragment.
  • By calling thegetRoot()Method or use the Kotlin attribute syntax to get a reference to the root view.
  • fromonCreateView()Method returns the root view (also available in theonViewCreated()forbind()), making it an active view on the screen.

1.3.3 ViewHolder

class RvAdapter(private val mData : List<String>) : RecyclerView.Adapter<RvAdapter.RvViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RvViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = ItemRvTextBinding.inflate(inflater)
        return RvViewHolder(binding)
    }

    override fun getItemCount(): Int {
        return mData.size
    }

    override fun onBindViewHolder(holder: RvViewHolder, position: Int) {
        holder.getBinding().title.text = mData[position]
    }

    class RvViewHolder(private val binding: ItemRvTextBinding) : RecyclerView.ViewHolder(binding.root) {
        fun getBinding() : ItemRvTextBinding {
            return binding
        }
    }
}
Copy the code

Three steps

  • Call the static contained in the generated binding classinflate()Methods. This creates an instance of the binding class for use by the ViewHolder.
  • inonCreateViewHolderCreate the ViewHolder to be the active view on the screen.
  • fromonBindViewHolder()Update the binding class’s property fetch.

1.4 ViewBinding class

1.4.1 File Description

1.4.1.1 Binding classpath

JavaModel: app/build/generated/data_binding_base_class_source_out/buildTypes/out/packageName/databinding

Layout file: app/build/intermediates/data_binding_layout_info_type_merge buildTypes/out

1.4.1.2 Binding Class Content

JavaModel

  • Action 1: Generates a view from the Layout file and associates it with the parent layout (possibly).
  • Action 2: Binds references to subview components based on the generated view.
public final class ActivityViewBindingBinding implements ViewBinding { @NonNull private final LinearLayout rootView; // the root object @nonNULL public final LinearLayout detail; @nonNULL Public Final TextView name; @nonNULL public Final TextView name; @NonNull public final Button rename; @NonNull public final RecyclerView rv; // Private constructor, which can only be used in bind(), The principle of least know private ActivityViewBindingBinding (@ NonNull LinearLayout rootView, @ NonNull LinearLayout detail, @NonNull TextView name, @NonNull Button rename, @NonNull RecyclerView rv) { this.rootView = rootView; this.detail = detail; this.name = name; this.rename = rename; this.rv = rv; } @override @nonnull public LinearLayout getRoot() {return rootView; } @ NonNull / / inflate overloads public static ActivityViewBindingBinding inflate (@ NonNull LayoutInflater inflater) {return inflate(inflater, null, false); } @nonnull //inflate overloads the method: Generate view and bind the child view component reference public static ActivityViewBindingBinding inflate (@ NonNull LayoutInflater inflater, @Nullable ViewGroup parent, Boolean attachToParent) {// Generate View root = inflater.inflate(r.layout.activity_view_binding, parent, false); If (attachToParent) {// Whether to add a parent layout. Tip: This affects whether the layout file's root view parameter is valid. } return bind(root); / / binding child View component reference} @ NonNull / / binding child View component reference public static ActivityViewBindingBinding bind (@ NonNull View rootView) {/ / The body  of this method is generated in a way you would not otherwise write. // This is done to optimize the compiled bytecode for size and performance. int id; missingId: { id = R.id.detail; LinearLayout detail = RootView.findViewById (id); if (detail == null) { break missingId; } id = R.id.name; TextView name = rootView.findViewById(id); if (name == null) { break missingId; } id = R.id.rename; Button rename = rootView.findViewById(id); if (rename == null) { break missingId; } id = R.id.rv; RecyclerView rv = rootView.findViewById(id); if (rv == null) { break missingId; } / / return the final binding class return new ActivityViewBindingBinding ((LinearLayout) rootView, detail, name, rename, rv); } // View parsing error exception throws String missingId = rootView.getResources().getResourcename (id); throw new NullPointerException("Missing required view with ID: ".concat(missingId)); }}Copy the code

Layout file

  • Purpose: to generate JavaModel objects
<? The XML version = "1.0" encoding = "utf-8" standalone = "yes"? > < Layout directory = "Layout" filePath = "app/SRC/main/res/Layout/activity_view_binding XML" / / because it enabled ViewBinding, So isBindingData is false isBindingData="false" isMerge="false" Layout ="activity_view_binding" ModulePackage = "com. Kejiyuanren. Jetpack rootNodeType" = "android. The widget. The LinearLayout" > / / marked Targets information, To generate the corresponding JavaModel <Targets> <Target tag="layout/activity_view_binding_0" view="LinearLayout"> <Expressions /> <location endLine="57" endOffset="14" startLine="1" startOffset="0" /> </Target> <Target id="@+id/name" view="TextView"> <Expressions /> <location endLine="14" endOffset="36" startLine="9" startOffset="4" /> </Target> <Target id="@+id/detail" view="LinearLayout"> <Expressions /> <location endLine="43" endOffset="18" startLine="22" startOffset="4" /> </Target> <Target id="@+id/rename" view="Button"> <Expressions /> <location endLine="41" endOffset="35" startLine="36" startOffset="8" /> </Target> <Target id="@+id/rv" view="androidx.recyclerview.widget.RecyclerView"> <Expressions /> <location endLine="55" endOffset="41" startLine="51" startOffset="4" /> </Target> </Targets> </Layout>Copy the code

1.4.2 ViewBinding principle analysis

Parsing entry: Parsing is done from databinding-Compiler, as it is called at compile time.

Where does 1.4.2.1 come from?

Conclusion: The XML file in RES/Layout is used to parse and generate element objects for caching.

(1) LayoutXmlProcessor -> processResources()

public boolean processResources(final ResourceInput input) throws ParserConfigurationException, SAXException, XPathExpressionException, IOException {... ProcessFileCallback callback = new ProcessFileCallback() {... If (input. IsIncremental ()) {/ / incremental compilation processIncrementalInputFiles (input, the callback); } else {processAllInputFiles(input, callback); }... }Copy the code

LayoutXmlProcessor -> processAllInputFiles()

private static void processAllInputFiles(ResourceInput input, ProcessFileCallback callback) throws IOException, XPathExpressionException SAXException, a ParserConfigurationException {... for (File firstLevel : Input.getrootinputfolder ().listFiles()) {if (firstLevel.isDirectory()) {// Whether the directory is a path // whether the folder starts with layout if (LAYOUT_FOLDER_FILTER. Accept (firstLevel, firstLevel getName ())) {/ / callback. Create the corresponding folder processLayoutFolder (firstLevel); //noinspection ConstantConditions // for (File xmlFile: FirstLevel. ListFiles (XML_FILE_FILTER)) {/ / processing layout file callback. ProcessLayoutFile (xmlFile); }} else {... }Copy the code

LayoutXmlProcessor -> processSingleFile()

public boolean processSingleFile(@NonNull RelativizableFile input, @NonNull File output) throws ParserConfigurationException, SAXException, XPathExpressionException, IOException {/ / parse the XML files final ResourceBundle. LayoutFileBundle bindingLayout = LayoutFileParser. ParseXml (input, output, mResourceBundle.getAppPackage(), mOriginalFileLookup); if (bindingLayout ! = null && ! BindingLayout. IsEmpty ()) {/ / parsing out the element object caching mResourceBundle. AddLayoutBundle (bindingLayout, true); return true; } return false; }Copy the code

(4) LayoutFileParser -> parseXml()

public static ResourceBundle.LayoutFileBundle parseXml(@NonNull final RelativizableFile input, @NonNull final File outputFile, @NonNull final String pkg, @NonNull final LayoutXmlProcessor.OriginalFileLookup originalFileLookup) throws ParserConfigurationException, IOException, SAXException, XPathExpressionException {... / / parsing continues to return parseOriginalXml (RelativizableFile fromAbsoluteFile (originalFile, input getBaseDir ()), PKG, encoding); ... }Copy the code

LayoutFileParser -> parseOriginalXml()

private static ResourceBundle.LayoutFileBundle parseOriginalXml( @NonNull final RelativizableFile originalFile, @nonNULL Final String PKG, @NonNULL Final String Encoding) throws IOException {...... Xmlparser. ElementContext data = getDataNode(root); xmlParser. ElementContext data = getDataNode(root); XMLParser.ElementContext rootView = getViewNode(original, root); ... / / generated element object ResourceBundle. LayoutFileBundle bundle = new ResourceBundle. LayoutFileBundle (originalFile, xmlNoExtension, original.getParentFile().getName(), pkg, isMerge); final String newTag = original.getParentFile().getName() + '/' + xmlNoExtension; ParseData (original, data, bundle); parseData(original, data, bundle); parseExpressions(newTag, rootView, isMerge, bundle); return bundle; ... }Copy the code

1.4.2.2 Which way?

Conclusion: Using the element object cache generated in the previous section, parse and generate middleware Layout files (XML files in the Build directory).

LayoutXmlProcessor -> writeLayoutInfoFiles()

// public void writeLayoutInfoFiles(File xmlOutDir, JavaFileWriter writer) throws JAXBException {/ / collection of objects traverse the for (a List < ResourceBundle. LayoutFileBundle > layouts: mResourceBundle.getLayoutBundles() .values()) { for (ResourceBundle.LayoutFileBundle layout : Layouts) {// Generate layout file writeXmlFile(writer, xmlOutDir, layout); }}... }Copy the code

LayoutXmlProcessor -> writeXmlFile()

private void writeXmlFile(JavaFileWriter writer, File xmlOutDir, ResourceBundle. LayoutFileBundle layout) throws JAXBException {/ / generated file name String filename = generateExportFileName (layout);  Writer.writetofile (new File(xmlOutDir, filename), layout.toxml ()); }Copy the code

1.4.2.3 Where to?

Conclusion: Parse and generate the ViewBinding class using the middleware Layout file generated in the previous section.

(1) BaseDataBinder -> init()

Init {input.filestoconsider.foreach {it.inputStream().use {// Convert middleware layout XML files into LayoutFileBundle val bundle = ResourceBundle. LayoutFileBundle. FromXML (it) / / cache into a ResourceBundle ResourceBundle. AddLayoutBundle (bundle, true)}... }Copy the code

(2) BaseDataBinder -> generateAll()

Fun generateAll(Writer: JavaFileWriter) {...... // Group sort by filename, Hand traveled through all of the ResourceBundle ResourceBundle. LayoutFileBundlesInSource. GroupBy {it. MFileName}. The forEach {val layoutName = Key val layoutModel = BaseLayoutModel(it. Value) //BaseLayoutBinderWriter Generates val binderWriter = BaseLayoutBinderWriter(layoutModel, libTypes) //BaseLayoutBinderWriter: binderwriter.write (); Writer. WriteToFile () writer. WriteToFile (binderWriter. Write ())... }Copy the code

(3) BaseLayoutBinderWriter -> write()

// Parse data: createType() and generate JavaFile: javaFile() fun write() = javaFile(binderTypeName.packageName(), createType()) { addFileComment("Generated by data binding compiler. Do not edit!" )}Copy the code

(4) BaseLayoutBinderWriter -> createType()

//解析所有,细节就不跟了
private fun createType() = classSpec(binderTypeName) {
        superclass(viewDataBinding)
        addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
        addFields(createBindingTargetFields())
        addFields(createVariableFields())
        addMethod(createConstructor())
        addMethods(createGettersAndSetters())
        addMethods(createStaticInflaters())
    }
Copy the code

Summary of principle analysis:

  • Step1: Layout file (XML resource file) -> Parse -> Element Cache.
  • Step2: Element cache -> Parse -> Layout file (middleware).
  • Step3: Layout file (middleware) -> Parse -> ViewBinding class.

2. Horizontal comparisons

2.1 the findViewById

View binding has some significant advantages over using findViewById:

  • Null security: Because the view binding creates a direct reference to the view, there is no risk that Null pointer exceptions will be thrown because the view ID is invalid.
  • Type safety: Fields in each binding class have a type that matches the view they reference in the XML file. This means that there is no risk of class conversion exceptions.

These differences mean that incompatibilities between layout and code will cause builds to fail at compile time rather than run time.

2.2 DataBinding

Both view binding and data binding generate binding classes that can be used to directly reference the view. However, view binding is designed to handle simpler use cases and has the following advantages over data binding:

  • Faster compilation: View bindings do not need to handle annotations, so compilation times are shorter.
  • Ease of use: View bindings do not require a specially marked XML layout file and are therefore faster to adopt in your application. When view binding is enabled in a module, it is automatically applied to all layouts of that module.

Conversely, view binding has the following limitations compared to data binding:

  • View bindings do not support layout variables or layout expressions and therefore cannot be used to declare dynamic interface content directly in AN XML layout file.
  • View binding does not support bidirectional data binding.

Recommendation: In some cases, it is best to use both view binding and data binding in your project. You can use data binding in a layout that requires advanced functionality, and view binding in a layout that does not.

3. Summary

ViewBinding is lightweight compared to DataBinding, and not every time you kill a chicken, you don’t need a knife. Forget findViewById as soon as possible!

Xiaobian blog series

Jetpack buckets