DataBinding原理深度研究

DataBinding原理深度研究

本文链接:https://blog.csdn.net/feather_wch/article/details/131819166

设计模式

1、目前安卓常用设计模式有MVC、MVP、MVVM

2、MVC

翻遍了整个安卓开发文档,都没有说安卓是mvc的。
但是Android就是MVC的

3、MVVM中VM和ViewModel什么关系?

  1. vm是viewmodel层,和jectpack组件viewmodel是两回事

DataBinding

4、DataBinding的作用是什么?

  1. databinding实现view和vm,双向绑定。

基本使用

5、双向绑定和单向绑定
例如,假设有一个 ViewModel 中的属性 name,我们想将其绑定到一个 TextView 上:

  • 单向绑定示例:android:text="@{user.name}",这会将 name 属性的值展示在 TextView 上,当 name 的值发生变化时,TextView 会自动更新。
  • 双向绑定示例:android:text="@={user.name}",这除了在单向绑定的基础上将 name 的值展示在 TextView 上,还可以在用户编辑 TextView 的内容时,将更改的内容反映回 name 属性。

6、Java版本User类

  1. BaseObservable + Bindable + notifyPropertyChanged(BR.xxx);
package com.personal.tax;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
public class User extends BaseObservable {
    String name;
    int age;
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Bindable
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }
    @Bindable
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
        notifyPropertyChanged(BR.age);
    }
}

7、kt版本Student类

  1. get,不能用@Bindable注解,怎么办?
  2. set,@Bindable才会产生BR.xxx
// 不可行
class Student : BaseObservable(){
    var name:String? = null
    // get,不能用@Bindable注解,怎么办?
    get(){
        return field
    }
}
// 可行
class Student{
    val name:ObservableField<String> by lazy{ ObservableField<String>()}
    val age:ObservableField<Int> by lazy{ ObservableField<Int>()}
}

8、DataBinding中报错,95%是布局xml错误
9、设置点击事件

//MyViewModel.java
public void appendNumber(String number){
xxx
}

xml中设置DataBinding的点击事件

Android:click=“@{()->vm.appendNumber(String.valueOf(1))}”

原理

1、DatBinding的布局是如何处理的?

  1. Andorid并不认识layout标签的布局
  2. DataBinding采用的APT注解处理器,会在编译期间进行处理
  3. 将布局拆分为两部分:第一部分layout部分,第二部分是我们实现的布局
  4. 核心点,两个布局用andorid:tag进行匹配

2、DataBinding会将原有布局拆分

  1. 原有布局拆activity_main_view.xml分为activity_main_view-layout.xmlactivity_main_view.xml
  2. DataBinding会根据第一个布局,加载第二个布局
  3. 每个控件用tag标记绑定在一起
    原有布局:activity_main_view.xml

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

    <data>
        <variable
            name="user"
            type="com.personal.tax.User" />
    data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainViewActivity">

        <TextView
            android:id="@+id/name_txt"
            android:text="@{user.name}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

        <TextView
            android:id="@+id/age_txt"
            android:text="@{user.age}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

    androidx.constraintlayout.widget.ConstraintLayout>
layout>

拆分后的DataBinding布局

// 目录:build\intermediates\data_binding_layout_info_type_merge\release\out\activity_main_view-layout.xml

<Layout directory="layout" filePath="app\src\main\res\layout\activity_main_view.xml"
    isBindingData="true" isMerge="false" layout="activity_main_view"
    modulePackage="com.personal.tax" rootNodeType="androidx.constraintlayout.widget.ConstraintLayout">
    <Variables name="user" declared="true" type="com.personal.tax.User">
        <location endLine="8" endOffset="42" startLine="6" startOffset="8" />
    Variables>
    <Targets>
        <Target tag="layout/activity_main_view_0"
            view="androidx.constraintlayout.widget.ConstraintLayout">
            <Expressions />
            <location endLine="32" endOffset="55" startLine="11" startOffset="4" />
        Target>
        <Target id="@+id/name_txt" tag="binding_1" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="user.name">
                    <Location endLine="18" endOffset="38" startLine="18" startOffset="12" />
                    <TwoWay>falseTwoWay>
                    <ValueLocation endLine="18" endOffset="36" startLine="18" startOffset="28" />
                Expression>
            Expressions>
            <location endLine="22" endOffset="54" startLine="16" startOffset="8" />
        Target>
        <Target id="@+id/age_txt" tag="binding_2" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="user.age">
                    <Location endLine="26" endOffset="37" startLine="26" startOffset="12" />
                    <TwoWay>falseTwoWay>
                    <ValueLocation endLine="26" endOffset="35" startLine="26" startOffset="28" />
                Expression>
            Expressions>
            <location endLine="30" endOffset="54" startLine="24" startOffset="8" />
        Target>
    Targets>
Layout>

拆分后的View xml布局

// 目录:\build\intermediates\incremental\release\mergeReleaseResources\stripped.dir\layout\activity_main_view.xml



    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainViewActivity" android:tag="layout/activity_main_view_0" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools">

        <TextView
            android:id="@+id/name_txt"
            android:tag="binding_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

        <TextView
            android:id="@+id/age_txt"
            android:tag="binding_2"   
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

    androidx.constraintlayout.widget.ConstraintLayout>

3、variable的作用是什么?建立绑定

    <data>
        <variable
            name="user"
            type="com.personal.tax.User" />
    </data>

4、DataBindingUtil.setContentView流程

#【DataBindingUtil】
setContentView() {
-->activity.setContentView(layoutId);
-->decorView = activity.getWindow().getDecorView();
-->contentView = decorView.findViewById(android.R.id.content);
-->bindToAddedViews(xxx, contentView, 0, layoutId)
  -->bind(xxx, childView, layoutId)
    -->sMapper.getDataBinder();
      #【DataBinderMapperImpl】- 找到apt生成的
        -->getDataBinder(xxx, View view, int layoutId) 
          -->tag = view.getTag(); // TAG 找到 目标布局
          -->if ("layout/activity_main_view_0".equals(tag)) return new ActivityMainViewBindingImpl(component, view);
            #【ActivityMainViewBindingImpl】
            -->mapBindings(xxx, 3, xxx) // 3代表有3个tag
              -->两个layout对应解析,生成Object[] // 大量消耗性能
            --> 构造方法()
              -->super(xxx, root, 1, (android.widget.TextView)bindings[2], (android.widget.TextView)bindings[1]); // 静态代码块绑定控件
                #【ViewDataBinding】
                -->静态代码块中:OnAttachStateChangeListener()->binding.mRebindRunnable.run() // 控件只要变化就会执行Runnable,100个控件100个Runnable执行
                |———————————————控件改变后触发———————————————————|
                |-->mRebindRunnable.run()
                |  -->executePendingBindings();
                |   #【ActivityMainViewBindingImpl】
                |    -->executeBindings()
                |      -->this.ageTxt.setText(userAge);
                |      -->双向绑定@={user.name} 特殊处理:给控件设置监听器XXXBindingAdapter,一但修改控件,就拿到值,修改到其他控件中。
              -->this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
              -->this.mboundView0.setTag(null);
              --》this.ageTxt.setTag(null);

        this.nameTxt.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();

5、Databinding耗时点:

  1. ActivityMainViewBindingImpl构造方法里面会解析两个布局形成object[]
  2. ViewDataBinding#静态代码块中:控件只要变化就会执行Runnable,100个控件100个Runnable执行
OnAttachStateChangeListener()->binding.mRebindRunnable.run()
// OnAttachStateChangeListener()是View的接口

6、binding.lifecycleOwner = this

lifecycleOwner.getLifecycle().addObserver(this.mOnStartListener);
OnStartListener
@OnLifecycleEvent(Event.ON_START)
->public void onStart() {
-->dataBinding = (ViewDataBinding)this.mBinding.get();
-->dataBinding.executePendingBindings();
-->会遍历绑定请求队列,计算所有待处理的绑定表达式,并将结果应用于View

7、为什么会有待处理的绑定请求?

  1. 优化性能:触发View更新的请求会放到请求队列中,不会立即执行View更新
  2. 调用 dataBinding.executePendingBindings() 时会立即执行所有待处理的绑定请求

问题

1、为什么双向绑定不会死循环

  1. 数据改变->控件A改变->拿到值->控件B改变->不会再设置监听器了

2、type="com.personal.tax.User"这里全类名是使用的反射吗?

  1. 不是!
    3、为什么有的有tag有的没有?
    4、tag有什么用?
    5、为什么DataBinding之前没人用?很卡顿,经过了多年的优化,才好。
    6、DataBinding中有哪些设计模式?
    7、DataBinding中有哪些巧妙的技术?
    8、DataBinding APT如何检查到xml中有layout标签?

你可能感兴趣的:(Android,MVVM,android)