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<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<String,String>">
<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??"哈哈哈么么哒"">
<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??"你为啥为空"">
<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的主要内容,如果未能解决你的问题,请参考以下文章
Android JetPack组件之DataBinding的使用详解