jetpack之databinding

Posted 做一个苦行僧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jetpack之databinding相关的知识,希望对你有一定的参考价值。

简单的Databingding代码 对应的xml文件名称 activity_default_data.xml

<?xml version="1.0" encoding="utf-8"?>
<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>

        <import type="java.util.Map" />

        <variable
            name="name"
            type="String" />

        <variable
            name="user"
            type="com.jetpack.demo.databinding.User" />

        <variable
            name="map"
            type="Map&lt;String,String>" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/teal_200"
        tools:context=".databinding.DefaultDataActivity">

        <TextView
            android:id="@+id/user_tv"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text='@user.fristName??"哈哈哈么么哒"'
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="hhah " />

        <TextView
            android:id="@+id/string_test"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text='@name??"你为啥为空"'
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/user_tv"
            tools:text="hhah " />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

然后回生成 ActivityDefaultDataBinding 类,其实就是xml文件驼峰之后后面加上DataBinding

我们在xml中 使用databinding的相关语法,其实在编译完成之后,会生成两个xml文件,这两个xml文件的路径

/build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_default_data-layout.xml

/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_default_data.xml

系统会将我们写的有databinding语法的代码 生成自己的xml代码

activity_default_data-layout.xml 的内容

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="app/src/main/res/layout/activity_default_data.xml"
    isBindingData="true" isMerge="false" layout="activity_default_data"
    modulePackage="com.jetpack.demo" rootNodeType="androidx.constraintlayout.widget.ConstraintLayout">
    <Variables name="name" declared="true" type="String">
        <location endLine="11" endOffset="27" startLine="9" startOffset="8" />
    </Variables>
    <Variables name="user" declared="true" type="com.jetpack.demo.databinding.User">
        <location endLine="15" endOffset="54" startLine="13" startOffset="8" />
    </Variables>
    <Variables name="map" declared="true" type="Map&lt;String,String&gt;">
        <location endLine="19" endOffset="42" startLine="17" startOffset="8" />
    </Variables>
    <Imports name="Map" type="java.util.Map">
        <location endLine="7" endOffset="38" startLine="7" startOffset="8" />
    </Imports>
    <Targets>
        <Target tag="layout/activity_default_data_0"
            view="androidx.constraintlayout.widget.ConstraintLayout">
            <Expressions />
            <location endLine="47" endOffset="55" startLine="22" startOffset="4" />
        </Target>
        <Target id="@+id/user_tv" tag="binding_1" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="user.fristName??&quot;哈哈哈么么哒&quot;">
                    <Location endLine="32" endOffset="53" startLine="32" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="32" endOffset="51" startLine="32" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="36" endOffset="32" startLine="28" startOffset="8" />
        </Target>
        <Target id="@+id/string_test" tag="binding_2" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="name??&quot;你为啥为空&quot;">
                    <Location endLine="42" endOffset="42" startLine="42" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="42" endOffset="40" startLine="42" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="46" endOffset="32" startLine="38" startOffset="8" />
        </Target>
    </Targets>
</Layout>

我们看到生产的xxx_layout.xml文件 。其实就是将我们在编写Activity中的xml 转化成,系统databinding库能够实现方式,一个控件id 对应一个tag ,最后我们在系统进行控件绑定的时候 其实使用的是tag

系统生成的另外一个xml文件 跟Activity中的xml同名,但是在id绑定的时候 多了一个tag


<?xml version="1.0" encoding="utf-8"?>


<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/teal_200"
    android:tag="layout/activity_default_data_0"
    tools:context=".databinding.DefaultDataActivity">

    <TextView
        android:id="@+id/user_tv"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:tag="binding_1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="hhah " />

    <TextView
        android:id="@+id/string_test"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:tag="binding_2"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/user_tv"
        tools:text="hhah " />
</androidx.constraintlayout.widget.ConstraintLayout>

每个控件都对应了一个tag,这个tag其实就是上面activity_default_data-layout.xml 中的对应的tag

DefaultDataActivtiy中的简单代码逻辑

class DefaultDataActivity : AppCompatActivity() 
    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        val binding = ActivityDefaultDataBinding.inflate(layoutInflater)
        binding.lifecycleOwner = this
        //R.layout.activity_default
        setContentView(binding.root)
        binding.user = User(null,2)
    

最后调用了setContentView(binding.root)的逻辑,之后我们就可以直接使用binding.userTv来使用上面的TextView控件

下面看 ActivityDefaultDataBinding.inflate(layoutInflater)的过程,以及最终绑定xml控件的过程

setContentView(binding.root)

上面的时序图中 是在ActivityDefaultDataBindingImpl的构造方法中初始化 控件的

public ActivityDefaultDataBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) 
    this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));



protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
        int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) 
    Object[] bindings = new Object[numBindings];
    mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
    return bindings;

上面的代码 都是通过apt技术 系统的库生成的。最终调用到6个参数的mapBindings方法中

private static void mapBindings(DataBindingComponent bindingComponent, View view,
        Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
        boolean isRoot) 
    final int indexInIncludes;
    final ViewDataBinding existingBinding = getBinding(view);
    if (existingBinding != null) 
        return;
    
    Object objTag = view.getTag();
    final String tag = (objTag instanceof String) ? (String) objTag : null;
    boolean isBound = false;
    if (isRoot && tag != null && tag.startsWith("layout")) 
        final int underscoreIndex = tag.lastIndexOf('_');
        if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) 
            final int index = parseTagInt(tag, underscoreIndex + 1);
            if (bindings[index] == null) 
                bindings[index] = view;
            
            indexInIncludes = includes == null ? -1 : index;
            isBound = true;
         else 
            indexInIncludes = -1;
        
     else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) 
        int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
        if (bindings[tagIndex] == null) 
            bindings[tagIndex] = view;
        
        isBound = true;
        indexInIncludes = includes == null ? -1 : tagIndex;
     else 
        // Not a bound view
        indexInIncludes = -1;
    
    if (!isBound) 
        final int id = view.getId();
        if (id > 0) 
            int index;
            if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
                    bindings[index] == null) 
                bindings[index] = view;
            
        
    

    if (view instanceof  ViewGroup) 
        final ViewGroup viewGroup = (ViewGroup) view;
        final int count = viewGroup.getChildCount();
        int minInclude = 0;
        for (int i = 0; i < count; i++) 
            final View child = viewGroup.getChildAt(i);
            boolean isInclude = false;
            if (indexInIncludes >= 0 && child.getTag() instanceof String) 
                String childTag = (String) child.getTag();
                if (childTag.endsWith("_0") &&
                        childTag.startsWith("layout") && childTag.indexOf('/') > 0) 
                    // This *could* be an include. Test against the expected includes.
                    int includeIndex = findIncludeIndex(childTag, minInclude,
                            includes, indexInIncludes);
                    if (includeIndex >= 0) 
                        isInclude = true;
                        minInclude = includeIndex + 1;
                        final int index = includes.indexes[indexInIncludes][includeIndex];
                        final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
                        int lastMatchingIndex = findLastMatching(viewGroup, i);
                        if (lastMatchingIndex == i) 
                            bindings[index] = DataBindingUtil.bind(bindingComponent, child,
                                    layoutId);
                         else 
                            final int includeCount =  lastMatchingIndex - i + 1;
                            final View[] included = new View[includeCount];
                            for (int j = 0; j < includeCount; j++) 
                                included[j] = viewGroup.getChildAt(i + j);
                            
                            bindings[index] = DataBindingUtil.bind(bindingComponent, included,
                                    layoutId);
                            i += includeCount - 1;
                        
                    
                
            
            if (!isInclude) 
                mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
            
        
    

这里 其实就是根据上面的系统生成的xml文件找到对应的binding_x 的控件,然后进行赋值,最后调用到

ActivityDefaultDataBindingImpl的构造函数,在这其中调用了super,这里其实会调用父类的构造方法对我们xml文件中的android:id="@+id/user_tv" android:id="@+id/string_test" 两个id的控件进行赋值,这样就有了。我们最终的 可以通过binding.userTv 或者控件的说法

private ActivityDefaultDataBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) 
    super(bindingComponent, root, 0
        , (android.widget.TextView) bindings[2]
        , (android.widget.TextView) bindings[1]
        );
    this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
    this.mboundView0.setTag(null);
    this.stringTest.setTag(null);
    this.userTv.setTag(null);
    setRootTag(root);
    // listeners
    invalidateAll();

我们看看最后的invalidataAll()做了什么


@Override
public void invalidateAll() 
    synchronized(this) 
            mDirtyFlags = 0x8L;
    
    requestRebind();



protected void requestRebind() 
    // 嵌套databinding的时候此处才不为null,像include里面
    if (mContainingBinding != null) 
        mContainingBinding.requestRebind();
     else 
        final LifecycleOwner owner = this.mLifecycleOwner;
        if (owner != null) 
            Lifecycle.State state = owner.getLifecycle().getCurrentState();
            if (!state.isAtLeast(Lifecycle.State.STARTED)) 
                return; // wait until lifecycle owner is started
            
        
        synchronized (this) 
            if (mPendingRebind) 
                return;
            
            mPendingRebind = true;
        
        // 区分api 来进行刷新操作 16以上使用if分支
        if (USE_CHOREOGRAPHER) 
            mChoreographer.postFrameCallback(mFrameCallback);
         else 
            mUIThreadHandler.post(mRebindRunnable);
        
    

其中mFrameCallback 的创建赋值在ViewBinding的构造函数中,在子类构建的时候调用了super


protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) 
    mBindingComponent = bindingComponent;
    mLocalFieldObservers = new WeakListener[localFieldCount];
    this.mRoot = root;
    if (Looper.myLooper() == null) 
        throw new IllegalStateException("DataBinding must be created in view's UI Thread");
    
    if (USE_CHOREOGRAPHER) 
        mChoreographer = Choreographer.getInstance();
        mFrameCallback = new Choreographer.FrameCallback() 
            @Override
            public void doFrame(long frameTimeNanos) 
                mRebindRunnable.run();
            
        ;
     else 
        mFrameCallback = null;
        mUIThreadHandler = new Handler(Looper.myLooper());
    

这里利用了系统的 刷新机制,最终调用到mRebindRunnable的run方法

private final Runnable mRebindRunnable = new Runnable() 
    @Override
    public void run() 
        synchronized (this) 
            mPendingRebind = false;
        
        processReferenceQueue();
        if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) 
            // Nested so that we don't get a lint warning in IntelliJ
            if (!mRoot.isAttachedToWindow()) 
                // Don't execute the pending bindings until the View
                // is attached again.
                mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                return;
            
        
        executePendingBindings();
    
;

再run方法中 会检测 view是否attachToWindow, 如果没有, 其实会注册一个监听

ROOT_REATTACHED_LISTENER (在ViewBinding的静态代码块中初始化) ,最终都是走到executePendingBindings()

public void executePendingBindings() 
    if (mContainingBinding == null) 
        executeBindingsInternal();
     else 
        mContainingBinding.executePendingBindings();
    


/**
 * Evaluates the pending bindings without executing the parent bindings.
 */
private void executeBindingsInternal() 
    //默认事false
    if (mIsExecutingPendingBindings) 
        requestRebind();
        return;
    
    //在ActivityDefaultDataBindingImpl中重写了调用invalidateAll的时候 赋值了此处为true
    if (!hasPendingBindings()) 
        return;
    
    mIsExecutingPendingBindings = true;
    mRebindHalted = false;
    if (mRebindCallbacks != null) 
        mRebindCallbacks.notifyCallbacks(this, REBIND, null);

        // The onRebindListeners will change mPendingHalted
        if (mRebindHalted) 
            mRebindCallbacks.notifyCallbacks(this, HALTED, null);
        
    
    if (!mRebindHalted) 
        executeBindings();
        if (mRebindCallbacks != null) 
            mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
        
    
    mIsExecutingPendingBindings = false;

看到这么多notifyCallbacks ,不用纠结这些细节,最终都会调用executeBindings()方法中,而这个方法

ActivityDefaultDataBindingImpl 中 有其实现类


@Override
protected void executeBindings() 
    long dirtyFlags = 0;
    synchronized(this) 
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    
    boolean nameJavaLangObjectNull = false;
    com.jetpack.demo.databinding.User user = mUser;
    java.lang.String userFristName = null;
    java.lang.String name = mName;
    boolean userFristNameJavaLangObjectNull = false;
    java.lang.String userFristNameJavaLangObjectNullJavaLangStringUserFristName = null;
    java.lang.String nameJavaLangObjectNullJavaLangStringName = null;

    if ((dirtyFlags & 0x9L) != 0) 



            if (user != null) 
                // read user.fristName
                userFristName = user.getFristName();
            


            // read user.fristName == null
            userFristNameJavaLangObjectNull = (userFristName) == (null);
        if((dirtyFlags & 0x9L) != 0) 
            if(userFristNameJavaLangObjectNull) 
                    dirtyFlags |= 0x20L;
            
            else 
                    dirtyFlags |= 0x10L;
            
        
    
    if ((dirtyFlags & 0xaL) != 0) 



            // read name == null
            nameJavaLangObjectNull = (name) == (null);
        if((dirtyFlags & 0xaL) != 0) 
            if(nameJavaLangObjectNull) 
                    dirtyFlags |= 0x80L;
            
            else 
                    dirtyFlags |= 0x40L;
            
        
    
    // batch finished

    if ((dirtyFlags & 0x9L) != 0) 

            // read user.fristName == null ? "哈哈哈么么哒" : user.fristName
            userFristNameJavaLangObjectNullJavaLangStringUserFristName = ((userFristNameJavaLangObjectNull) ? ("哈哈哈么么哒") : (userFristName));
    
    if ((dirtyFlags & 0xaL) != 0) 

            // read name == null ? "你为啥为空" : name
            nameJavaLangObjectNullJavaLangStringName = ((nameJavaLangObjectNull) ? ("你为啥为空") : (name));
    
    // batch finished
    if ((dirtyFlags & 0xaL) != 0) 
        // api target 1

        androidx.databinding.adapters.TextViewBindingAdapter.setText(this.stringTest, nameJavaLangObjectNullJavaLangStringName);
    
    if ((dirtyFlags & 0x9L) != 0) 
        // api target 1

        androidx.databinding.adapters.TextViewBindingAdapter.setText(this.userTv, userFristNameJavaLangObjectNullJavaLangStringUserFristName);
    

我们就可以看到 我们在最开始的时候书写的xml 文件 看到最熟悉的setText了。

以某个属性的修改来看 属性修改之后,怎么到去通知UI刷新的

public void setUser(@Nullable com.jetpack.demo.databinding.User User) 
    this.mUser = User;
    synchronized(this) 
        mDirtyFlags |= 0x1L;
    
    notifyPropertyChanged(BR.user);
    super.requestRebind();

这里最终调用了super.requestRebind()方法其实最后还是调用到ViewBinding中的requestRebind()方法中

protected void requestRebind() 
    if (mContainingBinding != null) 
        mContainingBinding.requestRebind();
     else 
        final LifecycleOwner owner = this.mLifecycleOwner;
        if (owner != null) 
            Lifecycle.State state = owner.getLifecycle().getCurrentState();
            if (!state.isAtLeast(Lifecycle.State.STARTED)) 
                return; // wait until lifecycle owner is started
            
        
        synchronized (this) 
            if (mPendingRebind) 
                return;
            
            mPendingRebind = true;
        
        if (USE_CHOREOGRAPHER) 
            mChoreographer.postFrameCallback(mFrameCallback);
         else 
            mUIThreadHandler.post(mRebindRunnable);
        
    

那么归根结底最终还是回到了ActivityDefaultDataBindingImpl的executeBindings方法中

双向绑定 " = "的奥秘

我们再databinding中,可以使用 = 来实现双向绑定,这里将之前的xml 稍微改造简单一些

注意:这里@= 后面的值一般不能写不确定的表达式,类似if的都不行

再看看ActivityDefaultDataBindingImpl中的代码 增加了一个TextWatcher

 这样当User数据修改的时候能够触发 userTv的ui的刷新,当其他方式例如直接调用了binding.userTv.setText("xx")的时候,也能够 触发TextWatcher的调用

以上是关于jetpack之databinding的主要内容,如果未能解决你的问题,请参考以下文章

jetpack之databinding

Android JetPack组件之DataBinding的使用详解

Android jetpack Databinding 双向绑定

Jetpack DataBinding

Jetpack DataBinding

Jetpack入门(三)viewModel介绍及dataBinding原理