[Jetpack]DataBinding源码浅析
DataBinding源码探析
DataBinding的出现实现了数据和UI的双向绑定,极大的方便了数据更新时UI能同步显示,下面就以一个简单的Demo作为入口,简要分析一下DataBinding源码。
创建DataBinding项目
- 修改build.gradle文件
android {
//......
dataBinding {
enabled = true
}
- 新建User类
class User(name: String, password: String) : BaseObservable() {
@Bindable
var name: String = name
set(value) {
field = value
notifyPropertyChanged(BR.name)
}
@Bindable
var password: String = password
set(value) {
field = value
notifyPropertyChanged(BR.password)
}
}
- 修改activity_main.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="user"
type="com.hanshow.databinddemo.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}" />
<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@{user.password}" />
</LinearLayout>
</layout>
- 修改MainActivity如下:
class MainActivity : AppCompatActivity() {
private var user:User? = null
private var binding: ActivityMainBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
user = User("mfw","123456")
//1.绑定布局
binding = DataBindingUtil.setContentView(this,R.layout.activity_main)
//2.给UIset数据
binding?.user = user
val runnable = Runnable {
run {
for (index in 1..10){
Thread.sleep(1000)
user?.let {
it ->
it.name = it.name + "1"
}
}
}
}
//3.子线程修改数据
Thread(runnable).start()
}
}
实现效果如下:
至此,一个简单的demo就写好了,为了学习源码,我们提出以下三个问题:
1.databinding如何实现Activity和xml文件的绑定?
2.databinding如何更新数据到xml?
3.为什么子线程更新数据后,UI能正常显示?
源码分析
- 入口1:DataBindingUtil.setContentView
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
setContentView方法重载:
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId, @Nullable DataBindingComponent bindingComponent) {
activity.setContentView(layoutId);
View decorView = activity.getWindow().getDecorView();
ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
从这个方法可以看出,Activity和xml文件的绑定还是通过setContentView来完成的,继续往下查看bindToAddedViews方法:
private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
ViewGroup parent, int startChildren, int layoutId) {
final int endChildren = parent.getChildCount();
final int childrenAdded = endChildren - startChildren;
if (childrenAdded == 1) {
final View childView = parent.getChildAt(endChildren - 1);
return bind(component, childView, layoutId);
} else {
final View[] children = new View[childrenAdded];
for (int i = 0; i < childrenAdded; i++) {
children[i] = parent.getChildAt(i + startChildren);
}
return bind(component, children, layoutId);
}
}
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}
最终调用到sMapper.getDataBinder,sMapper又是什么呢?
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
最后的最后会调到DataBinderMapperImpl的getDataBinder方法。
@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0) {
final Object tag = view.getTag();
if(tag == null) {
throw new RuntimeException("view must have a tag");
}
switch(localizedLayoutId) {
case LAYOUT_ACTIVITYMAIN: {
if ("layout/activity_main_0".equals(tag)) {
return new ActivityMainBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
}
}
}
return null;
}
这时候就不得不说一下databinding的中间产物了,首先会额外生成两个xml文件:
由于篇幅原因就不再剖析生成的xml文件内容,可以自行格式化以后进行分析,out目录下的是对databinding的数据解释和说明并通过tag和layout下的view进行绑定,再看看生成核心java文件:
核心Java类中的内容会按调用顺序分析,继续回到DataBinderMapperImpl的getDataBinder方法,判断view文件的tag,然后初始化ActivityMainBindingImpl,查看layout下的activity_main.xml,根布局的tag就是"layout/activity_main_0",所以下一步进入ActivityMainBindingImpl的构造方法:
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
}
继续查看mapBindings方法:
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;
}
会继续重载到mapBindings(DataBindingComponent bindingComponent, View view,
Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
boolean isRoot) 方法,该方法用于解析xml文件中的内容返回一个Object数据,然后继续看构造方法重载:
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 1
, (android.widget.TextView) bindings[1]
, (android.widget.TextView) bindings[2]
);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.tv1.setTag(null);
this.tv2.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
至此,可以看到xml中申明的view会在解析阶段存储在ActivityMainBindingImpl中,这也就是为什么能在Activity中通过binding直接访问View。
- 入口2:binding?.user = user
public void setUser(@Nullable com.hanshow.databinddemo.User User) {
updateRegistration(0, User);
this.mUser = User;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.user);
super.requestRebind();
}
binding?.user = user调用的是ActivityMainBindingImpl.setUser方法,然后继续深入:
protected boolean updateRegistration(int localFieldId, Observable observable) {
return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
CREATE_PROPERTY_LISTENER为一个CreateWeakListener常量,
private boolean updateRegistration(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
return unregisterFrom(localFieldId);
}
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
registerTo(localFieldId, observable, listenerCreator);
return true;
}
if (listener.getTarget() == observable) {
return false;//nothing to do, same object
}
unregisterFrom(localFieldId);
registerTo(localFieldId, observable, listenerCreator);
return true;
}
此方法就是把CreateWeakListener 和数据的id形成绑定关系,数据的Id可通过BR文件内容获得,BR文件内容如下:
public class BR {
public static final int _all = 0;
public static final int name = 1;
public static final int password = 2;
public static final int user = 3;
}
name和password是因为我们重写了set方法,并加入了@Bindable注解,而user 是定义在xml中的,都会在BR文件中生成对应的ID,然后继续看notifyPropertyChanged方法:
public void notifyPropertyChanged(int fieldId) {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, fieldId, null);
}
这个方法就跟踪到这,后面会继续讨论,接下来看super.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);
}
}
}
可以看到最后需要Android版本,但是最终都会调用到mRebindRunnable:
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();
}
};
然后继续看executePendingBindings:
public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
private void executeBindingsInternal() {
if (mIsExecutingPendingBindings) {
requestRebind();
return;
}
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;
}
这里最终会调用到executeBindings方法,但他是一个抽象方法,所以看他的实现就好了:
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String userName = null;
com.hanshow.databinddemo.User user = mUser;
java.lang.String userPassword = null;
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xbL) != 0) {
if (user != null) {
// read user.name
userName = user.getName();
}
}
if ((dirtyFlags & 0xdL) != 0) {
if (user != null) {
// read user.password
userPassword = user.getPassword();
}
}
}
// batch finished
if ((dirtyFlags & 0xbL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv1, userName);
}
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv2, userPassword);
}
}
// Listener Stub Implementations
// callback impls
// dirty flag
private long mDirtyFlags = 0xffffffffffffffffL;
/* flag mapping
flag 0 (0x1L): user
flag 1 (0x2L): user.name
flag 2 (0x3L): user.password
flag 3 (0x4L): null
flag mapping end*/
//end
}
到这里应该就都能看的懂了,就是通过一层层回调以后最终还是会通过setText来更新UI。
顺便说一下第三个问题的答案,因为我们通过mUIThreadHandler.post来更新UI,所以最终的setText还是在主线程。
- 入口3:notifyPropertyChanged(BR.name)
public void notifyPropertyChanged(int fieldId) {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, fieldId, null);
}
接上前面的,单数据发生变更时,都会调用 mCallbacks.notifyCallbacks:
public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
mNotificationLevel++;
notifyRecurse(sender, arg, arg2);
mNotificationLevel--;
if (mNotificationLevel == 0) {
if (mRemainderRemoved != null) {
for (int i = mRemainderRemoved.length - 1; i >= 0; i--) {
final long removedBits = mRemainderRemoved[i];
if (removedBits != 0) {
removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits);
mRemainderRemoved[i] = 0;
}
}
}
if (mFirst64Removed != 0) {
removeRemovedCallbacks(0, mFirst64Removed);
mFirst64Removed = 0;
}
}
}
然后看一下notifyRecurse方法:
private void notifyRecurse(T sender, int arg, A arg2) {
final int callbackCount = mCallbacks.size();
final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1;
// Now we've got all callbakcs that have no mRemainderRemoved value, so notify the
// others.
notifyRemainder(sender, arg, arg2, remainderIndex);
// notifyRemainder notifies all at maxIndex, so we'd normally start at maxIndex + 1
// However, we must also keep track of those in mFirst64Removed, so we add 2 instead:
final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE;
// The remaining have no bit set
notifyCallbacks(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
}
private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
final int endIndex, final long bits) {
long bitMask = 1;
for (int i = startIndex; i < endIndex; i++) {
if ((bits & bitMask) == 0) {
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
}
bitMask <<= 1;
}
}
mNotifier.onNotifyCallback是一个抽象方法,所以看他的实现类PropertyChangeRegistry,
然后继续调用WeakPropertyListener的onPropertyChanged方法:
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
ViewDataBinding binder = mListener.getBinder();
if (binder == null) {
return;
}
Observable obj = mListener.getTarget();
if (obj != sender) {
return; // notification from the wrong object?
}
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
又回调到ViewDataBind的handleFieldChange方法:
private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
if (mInLiveDataRegisterObserver) {
// We're in LiveData registration, which always results in a field change
// that we can ignore. The value will be read immediately after anyway, so
// there is no need to be dirty.
return;
}
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
requestRebind();
}
}
最后还是回到了requestRebind
本文地址:https://blog.csdn.net/qq_22783769/article/details/107952300
上一篇: 用css写一个有趣的奥运五环~。
下一篇: 使用css和js实现鼠标拖尾效果