当前位置:网站首页>Databinding source code analysis
Databinding source code analysis
2022-07-01 08:55:00 【Helan pig】
layout Layout important files generated during compilation ( With activity_main.xml For example ):
1、app\build\intermediates\data_binding_layout_info_type_package The directory activity_main-layout.xml, Main processing data , Record node information , Generate tag,twoway Whether records are bound in both directions , Intercept part of the code
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="app\src\main\res\layout\activity_main.xml"
isBindingData="true" isMerge="false" layout="activity_second"
modulePackage="com.example.myapplication" rootNodeType="android.widget.LinearLayout">
<Variables name="user" declared="true" type="com.example.myapplication.User">
<location endLine="8" endOffset="51" startLine="6" startOffset="8" />
</Variables>
<Targets>
<Target tag="layout/activity_main_0" view="LinearLayout">
<Expressions />
<location endLine="28" endOffset="18" startLine="12" startOffset="4" />
</Target>
<Target id="@+id/idName" tag="binding_1" view="TextView">
<Expressions>
<Expression attribute="android:text" text="user.name">
<Location endLine="21" endOffset="38" startLine="21" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="21" endOffset="36" startLine="21" startOffset="28" />
</Expression>
</Expressions>
<location endLine="21" endOffset="40" startLine="17" startOffset="8" />
</Target>
</Targets>
</Layout>
2、appbuild\generated\data_binding_base_class_source_out Under the ActivityMainBinding.java,View and Model Example
// Generated by data binding compiler. Do not edit!
package com.example.myapplication.databinding;
public abstract class ActivityMainBinding extends ViewDataBinding {
... Omit the part
@NonNull
public final TextView idName;
@Bindable
protected User mUser;
protected ActivityMainBinding(Object _bindingComponent, View _root,
int _localFieldCount, LinearLayout llId, NavigatorBar navigatorBar,
TextView idName ) {
super(_bindingComponent, _root, _localFieldCount);
this.llId = llId;
this.navigatorBar = navigatorBar;
this.idName = idName;
}
public abstract void setUser(
@Nullable User user);
@Nullable
public User getUser() {
return mUser;
}
@NonNull
public static ActivityMainBinding inflate(
@NonNull LayoutInflater inflater, @Nullable ViewGroup root, boolean attachToRoot) {
return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
}
/**
* Omit some @Deprecated Method
*/
@NonNull
public static ActivityMainBinding inflate(
@NonNull LayoutInflater inflater) {
return inflate(inflater, DataBindingUtil.getDefaultComponent());
}
public static ActivityMainBinding bind(@NonNull View view) {
return bind(view, DataBindingUtil.getDefaultComponent());
}
}
3、app\build\generated\ap_generated_sources Under the BR.java. When the data changes , This identifier can be used to notify DataBinding, Update with new data UI.
public class BR {
public static int _all = 0;
public static int user = 1;
}Let's analyze the source code
adopt layout The layout map gets the corresponding Binding.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
}
}Get into DataBindingUtil Take a look in the middle , Find out DataBindingUtil.setContentView In fact, the end result is activity.setContentView.( For custom view, Also provided inflate Method , You can set layout). The code is adjusted according to the calling order ,
/**
* Utility class to create {@link ViewDataBinding} from layouts.
*/
public class DataBindingUtil {
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
private static DataBindingComponent sDefaultComponent = null;
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId, @Nullable DataBindingComponent bindingComponent) {
// The foothold is also activity.setContentView
activity.setContentView(layoutId);
// The final root node of various layout source codes id All are android.R.id.content, So here is the root node id Of view
View decorView = activity.getWindow().getDecorView();
ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
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) {
// If there is only one coin View
final View childView = parent.getChildAt(endChildren - 1);
return bind(component, childView, layoutId);
} else {
// If there is duo Height View
final View[] children = new View[childrenAdded];
for (int i = 0; i < childrenAdded; i++) {
children[i] = parent.getChildAt(i + startChildren);
}
return bind(component, children, layoutId);
}
}
}Get into DataBindingUtil Class bind Let's see
@SuppressWarnings("unchecked")
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}Focus on sMapper. Is a static member variable , That is, the class is initialized when it is called
public class DataBindingUtil {
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
private static DataBindingComponent sDefaultComponent = null;
}
/**
* This class is dynamically generated at compile time , There is another one inside DataBinderMapperImpl, I'll talk about it later
*/
public class DataBinderMapperImpl extends MergedDataBinderMapper {
DataBinderMapperImpl() {
// Called by the parent class addMapper Method
// Note that what is added here is not itself , It is another implementation class with the same name to be mentioned below
addMapper(new com.example.myapplication.DataBinderMapperImpl());
}
}
public class MergedDataBinderMapper extends DataBinderMapper
public void addMapper(DataBinderMapper mapper) {
Class<? extends DataBinderMapper> mapperClass = mapper.getClass();
// If it didn't exist before , Join in mExistingMappers
if (mExistingMappers.add(mapperClass)) {
mMappers.add(mapper);
// Get the mapper Rely on others DataBinderMapper, Add in
final List<DataBinderMapper> dependencies = mapper.collectDependencies();
for(DataBinderMapper dependency : dependencies) {
// Others that will depend on DataBinderMapper And add it
addMapper(dependency);
}
}
}
}addMaper Methods are generated DataBinderMapperImpl And what else it depends on DataBinderMapper Add to mExistingMappers and mMappers Collection . Next entry com.example.myapplication Under bag DataBinderMapperImpl Of getDataBinder have a look
package com.example.myapplication;
public class DataBinderMapperImpl extends DataBinderMapper {
private static final int LAYOUT_ACTIVITYSTARTDETAIL = 1;
private static final int LAYOUT_ACTIVITYMAIN = 2;
private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(2);
static {
// take databinding The layout file of the class corresponds to the constant of the class definition
INTERNAL_LAYOUT_ID_LOOKUP.put(com.example.myapplication.R.layout.activity_start_detail, LAYOUT_ACTIVITYSTARTDETAIL);
INTERNAL_LAYOUT_ID_LOOKUP.put(com.example.myapplication.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);
}
@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
// according to layout Layout file id Get custom id
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0) {
// retrievable tag, As mentioned earlier, it is automatically generated during compilation tag
final Object tag = view.getTag();
if(tag == null) {
throw new RuntimeException("view must have a tag");
}
// according to tag Load the corresponding bindingImpl
switch(localizedLayoutId) {
case LAYOUT_ACTIVITYSTARTDETAIL: {
if ("layout/activity_start_detail_0".equals(tag)) {
return new ActivityStartDetailBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_start_detail is invalid. Received: " + tag);
}
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;
}
@Override
public List<DataBinderMapper> collectDependencies() {
ArrayList<DataBinderMapper> result = new ArrayList<DataBinderMapper>(8);
result.add(new androidx.databinding.library.baseAdapters.DataBinderMapperImpl());
return result;
}
}
therefore DataBindingUtil.setContentView The last thing the method returns is based on layout File generated during compilation ViewBinding Implementation class of ( such as activity_main.xml Corresponding ActivityMainBindingImpl), Take a look at this class entering the constructor
public class ActivityMainBindingImpl extends ActivityMainBinding {
@Nullable
private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
@Nullable
private static final android.util.SparseIntArray sViewsWithIds;
static {
sIncludes = null;
sViewsWithIds = new android.util.SparseIntArray();
...
sViewsWithIds.put(R.id.idName, 5);
}
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 6, sIncludes, sViewsWithIds));
}
private ActivityStartDetailBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(XXXXX);
// take tag empty
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
@Override
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x1L;
}
requestRebind();
}
}The first constructor calls the parent class ViewDataBinding Of mapBindings Method
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")) {
// Determine whether it is the root layout , The root layout of tag = layout/activity_xxx_ Numbers
final int underscoreIndex = tag.lastIndexOf('_');
if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
// With tag The next number is the subscript , To the corresponding tag Of View Put it into the array position of the specified subscript
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)) {
// Judge tag Whether or not to binding_ start
// With tag The next number is the subscript , To the corresponding tag Of View Put it into the array position of the specified subscript
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++) {
// Traverse ViewGroup Of children
final View child = viewGroup.getChildAt(i);
// Is it include Marking of layout
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) {
// Yes include Layout To deal with
// 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);
}
}
}
}We can know mapBindings Method returns bindings Array , What is stored in the array is... In the layout file View, And the array subscript to generate tag The numbers in are used as View stay bindings Corresponding subscript in array . Now you can go back to the construction method and look down .
public ActivityStartDetailBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 6, sIncludes, sViewsWithIds));
}
private ActivityStartDetailBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 0
, (android.widget.Button) bindings[5]
, (android.widget.FrameLayout) bindings[3]
, (com.sf.trtms.lib.widget.NavigatorBar) bindings[1]
, (androidx.recyclerview.widget.RecyclerView) bindings[4]
, (android.view.View) bindings[2]
);
// take tag empty
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}ActivityStartDetailBindingImpl Called ActivityStartDetailBinding Construction method of ,ActivityStartDetailBinding The constructor of calls ViewDataBinding Construction method of .
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) {
// Equipment system API No less than 16
// Conduct mChoreographer Initialization and mFrameCallback The concrete realization of
mChoreographer = Choreographer.getInstance();
mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mRebindRunnable.run();
}
};
} else {
// Bound to the main thread Handler
mFrameCallback = null;
mUIThreadHandler = new Handler(Looper.myLooper());
}
}(Choreographer yes UI Important classes for rendering , Later, we will add an explanation to the performance optimization ). No matter what api How much? , When a message is received, it will eventually execute mRebindRunnable.run(); This is the later story. , We go back to ActivityStartDetailBindingImpl The construction method of continues to go down invalidateAll() Method .
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x8L;
}
requestRebind();
}
/** ViewDataBinding Methods **/
protected void requestRebind() {
if (mContainingBinding != null) {
mContainingBinding.requestRebind();
} else {
final LifecycleOwner owner = this.mLifecycleOwner;
if (owner != null) {
// Determine whether the lifecycle holder is active (Started and resume)
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) {
//API No less than 16, Just use mChoreographer To render
// Above ViewDataBinding In the constructor mFrameCallback Finally called mRebindRunnable
mChoreographer.postFrameCallback(mFrameCallback);
} else {
// Use UI Main thread Handler perform Runnable
mUIThreadHandler.post(mRebindRunnable);
}
}
}
No matter api How much will be executed in the end mRebindRunnable Of run Method
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.
//view no attach To window Is to remove listening
mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
return;
}
}
executePendingBindings();
}
};
public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
private void executeBindingsInternal() {
... Judge data compliance
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;
}If there is, the last call is ActivityMainBindingImpl Of executeBindings()
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
...
androidx.databinding.ObservableField<java.lang.String> userName = null;
com.example.myapplication.User user = mUser;
java.lang.String userNameGet = null;
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xdL) != 0) {
if (user != null) {
// read user.name
userName = user.getName();
}
// Pay attention to this 0 It's generated automatically localFieldId, It will be useful in the future =.=
updateRegistration(0, userName);
if (userName != null) {
// read user.name.get()
userNameGet = userName.get();
}
}
...
}
// According to different flag bits ( Compile time dynamic decision ), Refresh UI Control
if ((dirtyFlags & 0xdL) != 0) {
// The data is assigned here , according to @+id As defined in id Name defines the control
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.idName, userNameGet);
}
}
// dirty flag
private long mDirtyFlags = 0xffffffffffffffffL;
/* flag mapping
flag 0 (0x1L): user.name
flag 1 (0x2L): user
flag 2 (0x3L): null
flag mapping end*/
//endsummary
- During the compilation process, a code with tag Layout file for ( Root layout layout/XXX_0, other binding_ Numbers )
- DataBindingUtil.setContentView According to the new layout file generated during compilation tag Get the corresponding layout ViewBinding Implementation class of
- Put the layout sub View, All stored in the array , And then we'll put the... In the array View Assigned to the generated ViewBinding Properties of , In this way, you can directly use the generated ViewBinding Property to use view, There is no need to call findViewById 了 .bindings[index] = view. The subscript of the data is based on tag Generated by the number of
- According to different flag bits ( Compile time dynamic decision ), Refresh the corresponding UI Control .
mDirtyFlags The tag bit is very important , If used ObservableField And other classes for automatic refresh , Then the corresponding mapping will be included in the tag bit , If you are using String、Int And so on , Will not contain .
Dynamic setting data binding has 2 Ways of planting :
The first one is : Entity class inheritance BaseObservable, Or do it yourself Observable; Use ObservableField<>, Generic types can fill in the types they need , Note that you must initialize . Basic data types can also be used directly ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble and ObservableParcelable; Property set to public.
The second kind : Entity class inheritance BaseObservable, Or do it yourself Observable; In the attribute that needs to be refreshed get Method @Bindable annotation , This will automatically generate BR class ( A lot of times BR Files are not automatically generated , Then restart AS Well ...); Corresponding set Method call notifyPropertyChanged(BR.xxx) refresh .
The above is the first way executeBindings, Let's look at the second way
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String userName = null;
com.example.myapplication.User user = mUser;
if ((dirtyFlags & 0x3L) != 0) {
if (user != null) {
// read user.name
userName = user.getName();
...
}
}
// batch finished
if ((dirtyFlags & 0x3L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.idName, userName);
...
}
}
private long mDirtyFlags = 0xffffffffffffffffL;
/* flag mapping
flag 0 (0x1L): user
flag 1 (0x2L): null
flag mapping end*/mDirtyFlags The mapping of has also changed . Compare the two situations , In addition to the change of flag bit, there is also the time of attribute assignment , If it is ObservableField Data of type will call... With the attribute value as a parameter updateRegistration() Method
protected boolean updateRegistration(int localFieldId, Observable observable) {
return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
private boolean updateRegistration(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
// The observation object is empty , Remove monitor
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;
}
// Attribute listening class , There are other types CREATE_LIST_LISTENER、CREATE_MAP_LISTENER、
//CREATE_LIVE_DATA_LISTENER
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
}
};
Get into registerTo Method , Take a look at the implementation :
protected void registerTo(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
return;
}
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
//listenerCreator.create Will create a WeakListener object
// Create... By constructing methods WeakPropertyListener object , And through the getListener return WeakListener object
listener = listenerCreator.create(this, localFieldId);
mLocalFieldObservers[localFieldId] = listener;
if (mLifecycleOwner != null) {
listener.setLifecycleOwner(mLifecycleOwner);
}
}
listener.setTarget(observable);
}Get into setTarget()
public void setTarget(T object) {
unregister();
//mTarget Namely ObservableField object
mTarget = object;
if (mTarget != null) {
//mObservable It is created when using the constructor , So it's actually a WeakPropertyListener type
//mListener = new WeakListener<Observable>(binder, localFieldId, this);
mObservable.addListener(mTarget);
}
}
// Add attribute change listening
public void addListener(Observable target) {
target.addOnPropertyChangedCallback(this);
}addListener() Methods target Namely ObservableField object , It is an observer . adopt addOnPropertyChangedCallback() Method , Equivalent to target Register an observer
public interface Observable {
void addOnPropertyChangedCallback(OnPropertyChangedCallback callback);
void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback);
abstract class OnPropertyChangedCallback {
/**
* Called by an Observable whenever an observable property changes.
* @param sender The Observable that is changing.
* @param propertyId The BR identifier of the property that has changed. The getter
* for this property should be annotated with {@link Bindable}.
*/
public abstract void onPropertyChanged(Observable sender, int propertyId);
}
}
stay WeakPropertyListener The specific implementation of :
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
implements ObservableReference<Observable> {
final WeakListener<Observable> mListener;
public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
mListener = new WeakListener<Observable>(binder, localFieldId, this);
}
...
@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);
}
}
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;
}
// Determine whether the data has changed
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
// The same process as before , Final update UI The data of .
requestRebind();
}
}onFieldChange Used to judge whether the data has changed , Take a look at it ActivityMainBindingImpl The specific implementation of :
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
switch (localFieldId) {
//localFieldId Is in executeBindings Method is automatically generated , from updateRegistration Step by step
case 0 :
return onChangeUserName((androidx.databinding.ObservableField<java.lang.String>) object, fieldId);
...
}
return false;
}
private boolean onChangeUserName(androidx.databinding.ObservableField<java.lang.String> UserName, int fieldId) {
if (fieldId == BR._all) {
synchronized(this) {
// According to the previous annotation of flag bit mapping , This method is used to judge user.name Is there a change
//flag 0 (0x1L): user.name
mDirtyFlags |= 0x1L;
}
return true;
}
return false;
}summary
When the data source changes , Callbacks WeakPropertyListener Of onPropertyChanged() Method . stay WeakPropertyListener Hold in ViewDataBinding object , Calling the handleFieldChange() Method , To update View.
Here we are ,DataBinding The start-up part of is analyzed .
Reverse binding
Two way binding means more than data binding UI, meanwhile UI Data can be refreshed during update , The grammar is @={}. Let's look at the process of reverse binding .CheckBox Inherit CompoundButton We use it CompoundButton Of checked Attribute combination CompoundButtonBindingAdapter Source code to illustrate the process of reverse binding .
@BindingMethods({
@BindingMethod(type = CompoundButton.class, attribute = "android:buttonTint", method = "setButtonTintList"),
@BindingMethod(type = CompoundButton.class, attribute = "android:onCheckedChanged", method = "setOnCheckedChangeListener"),
})
@InverseBindingMethods({
@InverseBindingMethod(type = CompoundButton.class, attribute = "android:checked"),
})
public class CompoundButtonBindingAdapter {
@BindingAdapter("android:checked")
public static void setChecked(CompoundButton view, boolean checked) {
if (view.isChecked() != checked) {
view.setChecked(checked);
}
}
@BindingAdapter(value = {"android:onCheckedChanged", "android:checkedAttrChanged"},
requireAll = false)
public static void setListeners(CompoundButton view, final OnCheckedChangeListener listener,
final InverseBindingListener attrChange) {
if (attrChange == null) {
view.setOnCheckedChangeListener(listener);
} else {
view.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (listener != null) {
listener.onCheckedChanged(buttonView, isChecked);
}
attrChange.onChange();
}
});
}
}
}@InverseBindingMethods: Element is @InverseBindingMethod An array of , Used to annotate class .
@InverseBindingMethod: Reverse binding method , Used to determine how to monitor view Which attribute changes or callbacks getter Method . Contains the following 4 Attributes :
- type: contain attribute Of view type .
- attribute: Properties that support bidirectional binding (string Format ).
- event: It can be omitted , Used to inform DataBinding System attribute Has been changed , The default is attribute + "AttrChanged".(UI Notification data )
- method: It can be omitted , Used to from view How to get data , If you don't set it, you will find it automatically "is" or "get"+attribute Method .
@InverseBindingMethod(type = CompoundButton.class, attribute = "android:checked") Events are omitted event(checkedAttrChanged) and method(isChecked)
@BindingAdapter Annotation sets the event invocation time
@BindingAdapter("android:checked")
checkbox Use in checked Attribute , Automatically called setChecked Method ..
We are layout Use two-way binding in
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="@={user.checked}"/>When Checked The event is triggered when the attribute changes checkeAttrChanged, adopt bindingAdapter Annotation call setListeners, Used InverseBindingListener Interface for users to throw event notifications .InverseBindingListener The specific implementation code of the interface is generated by the framework , In general, it is to get the current value of the control property , Then update the data with this value .
InverseBindingListener It's an abstract interface , There is one onChange Method , look down layout Of binding Class InverseBindingListener Realization .
private android.databinding.InverseBindingListener checkboxandroidCheck = new android.databinding.InverseBindingListener() {
@Override
public void onChange() {
// This logic is actually used to update user In entity class checked Field
// Inverse of user.checked.get()
// is user.checked.set((java.lang.Boolean) callbackArg_0)
boolean callbackArg_0 = checkbox.isChecked();// In fact, that is method
// localize variables for thread safety
// user.checked != null
boolean checkedUserObjectnul = false;
// user.checked
android.databinding.ObservableField<java.lang.Boolean> checkedUser = null;
// user
com.example.myapplication.User user = mUser;
// user.checked.get()
java.lang.Boolean CheckedUser1 = null;
// user != null
boolean userObjectnull = false;
userObjectnull = (user) != (null);
if (userObjectnull) {
checkedUser = user.checked;
checkedUserObjectnul = (checkedUser) != (null);
if (checkedUserObjectnul) {
checkedUser.set((java.lang.Boolean) (callbackArg_0));
}
}
}
};summary
The whole reverse binding process is actually :
- @InverseBindingMethod(type = CompoundButton.class, attribute = "android:checked",event="checkedAttrChanged",method="isChecked" )
Define the properties that need to be reverse bound (checked), And configuration event(checkedAttrChanged) and method(isChecked). @BindingAdapter(value = {"android:onCheckedChanged","android:checkedAttrChanged"},requireAll = false) The system will automatically respond to event Find the corresponding way (setListeners), Configure the call time .setListeners Call in InverseBindingListener.onchange()stay layout Use two-way binding in android:checked="@={user.checked}.Automatically in binding Class InverseBindingListener Realization . The callback method onChange Refresh data in
see TextViewBindingAdapter I found that there are still some points not mentioned
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
public static String getTextString(TextView view) {
return view.getText().toString();
}
This annotation method is equivalent to working on view Get data on getter Method .
- attribute: Properties that support bidirectional binding (string Format ).
- event: It can be omitted , Used to inform DataBinding System attribute Events that have changed , The default is attribute + "AttrChanged". Need to pass through @BindingAdapter Set the call time .
reflection :
1、 After the event driven reverse binding succeeds ,UI Changing the data will also change , In normal logic , Will continue to trigger one-way binding , Why not fall into an infinite loop ?
To break this cycle , The usual way is to update UI front , Verify that the old and new data are the same , If they are the same, do not refresh . such as TextView, Inside setText The method does not test the consistency of old and new data , So in TextViewBindingAdapter It's rebinding inside android:text attribute , Add verification logic .
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
// check
final CharSequence oldText = view.getText();
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
...
view.setText(text);
}2、DataBinding And ButterKnife The difference between
ButterKnife Plug ins use annotations to find controls and register listeners , Reduce duplicate code , But it's not simple enough : Such as to TextView To set a copy, you must call setText(); To get editText You must get the contents of before editText object ; to view You must also get this before setting monitoring view Object etc. . But it did DataBinding after , All these redundancies can be simplified , So you just need to focus on business logic .
------------------------------ Welcome more criticism and correction
边栏推荐
- 日常办公耗材管理解决方案
- The meaning of yolov5 training visualization index
- It technology ebook collection
- Interrupt sharing variables with other functions and protection of critical resources
- Nacos - service discovery
- How to solve the problem of fixed assets management and inventory?
- Shell脚本-if else语句
- Football and basketball game score live broadcast platform source code /app development and construction project
- Matlab [function derivation]
- 3、Modbus通讯协议详解
猜你喜欢

Nacos - 配置管理

3、Modbus通讯协议详解

Public network cluster intercom +gps visual tracking | help the logistics industry with intelligent management and scheduling

Nacos - 配置管理

Redis——Lettuce连接redis集群

Computer tips

Do you know how data is stored? (C integer and floating point)

FreeRTOS学习简易笔记

大型工厂设备管理痛点和解决方案

MD文档中插入数学公式,Typora中插入数学公式
随机推荐
中考体育项目满分标准(深圳、安徽、湖北)
电视机尺寸与观看距离
C语言指针的进阶(上篇)
Matlab tips (16) consistency verification of matrix eigenvector eigenvalue solution -- analytic hierarchy process
中小企业固定资产管理办法哪种好?
Shell script -for loop and for int loop
Set the type of the input tag to number, and remove the up and down arrows
It is designed with high bandwidth, which is almost processed into an open circuit?
How to solve the problem of fixed assets management and inventory?
FreeRTOS learning easy notes
基础:2.图像的本质
固定资产管理系统让企业动态掌握资产情况
pcl_ Viewer command
Centos7 shell script one click installation of JDK, Mongo, Kafka, FTP, PostgreSQL, PostGIS, pgrouting
Personal decoration notes
Shell script -select in loop
Shell script echo command escape character
Shell脚本-case in语句
Nacos - 服务发现
Shell script - string