搭建自己的数据绑定框架
# 一、简介
我们在搭建MVVM框架时一般都需要配合databinding一起使用,只需要在layout.xml中使用layout标签+data标签,编译器就可以为我们自动生成相应的viewBinding类,当为这些view Binding类设置viewModel后,就可以在viewModel中通知view进行更新了。
MVVM的核心思想是数据驱动,这篇文章就是借鉴了databinding的部分实现方式搭建的一套简易的数据绑定框架。
# 二、整体架构
# 三、搭建步骤
先定义view层与viewModel层之间的接口
/**
* 可被观察的包含binding属性的数据模型,viewModel进行实现
*/
public interface PropertiesObservable {
void addOnPropertyChangedCallback(OnPropertyChangedCallback callback);
void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback);
/**
* 实际的观察者,viewBinding进行实现
*/
abstract class OnPropertyChangedCallback {
public abstract void onPropertyChanged(PropertiesObservable sender, Enum<?> propertyId);
}
}
接着在viewModel层将来自view层的观察者用弱引用包装后进行存储
public class BaseViewModel extends AndroidViewModel implements PropertiesObservable {
//观察数据变化的所有对象,用弱引用进行包装防止内存泄漏
private final Set<WeakReference<OnPropertyChangedCallback>> propertyChangedCallbacks = new HashSet<>();
@Override
public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
propertyChangedCallbacks.add(new WeakReference<>(callback));
...
}
@Override
public void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
for (WeakReference<OnPropertyChangedCallback> weakReference : propertyChangedCallbacks) {
OnPropertyChangedCallback changedCallback = weakReference.get();
if (callback == changedCallback) {
propertyChangedCallbacks.remove(weakReference);
break;
}
}
...
}
在view层viewBinding侧进行注册监听
/**
* 设置viewModel并注册监听viewModel的数据变化
*/
protected void setViewModel(BaseViewModel viewModel) {
if (this.viewModel != null) {
this.viewModel.removeOnPropertyChangedCallback(this);
}
this.viewModel = viewModel;
this.viewModel.addOnPropertyChangedCallback(this);
...
}
此时我们已经利用了观察者模式实现了view层对viewModel层的监听,但还需要明确的知道viewModel层的谁数据变化了,以及view层的谁绑定了该数据,显然需要一个key在viewModel层关联方法,在view层关联View,而需要绑定的数据在源码阶段就已经确定了,所以用常量和枚举都可以,这里选择枚举,另外需要绑定的数据这里也简单一点指定到某个方法。
由于方法名并不是固定的,在view层利用反射调用虽然可行,但用代理类进行包装显然更加合理。
public interface OnBindingCallback<T> {
T get();
}
这样在view层只要能拿到枚举key对应的OnBindingCallback对象并调用其get方法就能拿到需要绑定的数据方法的最新值了。
再定义一个类用于存储枚举key和其对应的代理对象
public class BindingCallbacks {
protected Map<Enum<?>, OnBindingCallback<?>> callbacks = new HashMap<>();
/**
* 获取实际方法的OnBindingCallback代理对象
*/
public OnBindingCallback<?> getCallback(Enum<?> type) {
return callbacks.get(type);
}
}
手动添加枚举key和其对应的代理对象显然不够优雅,我们借鉴databinding引入apt技术,定义一个注解用来标记需要绑定的数据方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE) //源码级别保留,编译后丢弃
public @interface Binding {
}
并在编译阶段生成相应的枚举key后自动将其加入到BindingCallbacks.callbacks中
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
...
for (Element element : roundEnvironment.getElementsAnnotatedWith(Binding.class)) {
if (element.getKind() == ElementKind.METHOD && element.getModifiers().contains(Modifier.PUBLIC)) {
ExecutableElement executableElement = (ExecutableElement) element;
if (executableElement.getParameters().size() == 0) {
...
//当方法名为get和is开头时移除该关键字
String functionName = element.getSimpleName().toString();
if (functionName.startsWith("get")) {
functionName = functionName.substring(3);
} else if (functionName.startsWith("is")) {
functionName = functionName.substring(2);
}
//将其后的第一个字母设为小写
functionName = functionName.substring(0, 1).toLowerCase() + functionName.substring(1);
//将该处理过的方法名作为枚举的一个类别
if (!enumConstants.contains(functionName)) {
enumSpecBuilder.addEnumConstant(functionName);
enumConstants.add(functionName);
}
//为BindingCallbacks的构造器添加一对枚举类别和该方法的对应关系
classHolder.constructorSpecBuilder.addStatement("callbacks.put($T.$L, new $T(){public $T get(){return model.$L();}})", ClassName.bestGuess(masterPkg + "." + CLASS_NAME_ENUM), functionName, ClassName.bestGuess(CLASS_CALLBACK_LISTENER), TypeName.get(executableElement.getReturnType()).box(), executableElement.getSimpleName().toString());
}
}
}
if (masterPkg != null) {
for (String key : bindingMap.keySet()) {
ClassHolder classHolder = bindingMap.get(key);
classHolder.classSpecBuilder.addMethod(classHolder.constructorSpecBuilder.build());
//生成BindingCallbacks特定类
writeClass(key.substring(0, key.lastIndexOf('.')), bindingMap.get(key).classSpecBuilder.build(), filer);
}
//生成BDR枚举类
writeClass(masterPkg, enumSpecBuilder.build(), filer);
}
return true;
}
直接看下编译后的自动生成的类便于理解
public class ListViewModel$BindingCallbacks extends BindingCallbacks {
public ListViewModel$BindingCallbacks(ListViewModel model) {
callbacks.put(BDR.emptyText, new OnBindingCallback(){
public String get(){
return model.getEmptyText();
}
});
...
}
}
Processor自动为每个ViewModel创建一个特定的BindingCallbacks,并在其构造器里将ViewModel中加了注解的方法进行包装,使用去除了get关键字的方法名作为枚举key,put进callbacks中,这样只要创建该特定的BindingCallbacks对象就能从callbacks中拿到所有的代理对象。
public BaseViewModel(@NonNull Application application) {
super(application);
bindingCallbacks = new BindingCallbacks();
Class<?> thisClass = getClass();
//查找当前对象到最底层BaseViewModel之间所有的枚举key和代理对象
while (thisClass != null && thisClass != BaseViewModel.class) {
BindingCallbacks callbacks = getBindingCallbacks(thisClass);
if (callbacks != null) {
bindingCallbacks.callbacks.putAll(callbacks.callbacks);
}
thisClass = thisClass.getSuperclass();
}
}
/**
* 通过反射创建viewModel对应BindingCallbacks类
*/
private BindingCallbacks getBindingCallbacks(Class<?> clazz) {
String classNameWithPkg = clazz.getName() + "$BindingCallbacks";
try {
Class<?> callbacksClass = Class.forName(classNameWithPkg);
return (BindingCallbacks) callbacksClass.getConstructor(clazz).newInstance(this);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
此时viewModel侧的基础封装都已经完成,通过viewModel->BindingCallbacks->key:OnBindingCallback->找到需要绑定的具体方法。
接下来搭建view层的基础框架
定义一个赋值接口类,用于给不同类型的view赋值
/**
* 赋值接口类
*/
public interface ValueBinder<V extends View, Val> {
void setValue(V view, Val value);
}
再定义一个BindHolder用于存储已绑定的view和对应的赋值器对象
private static class BindHolder {
final View view;
final ValueBinder valueBinder;
BindHolder(View view, ValueBinder valueBinder) {
this.view = view;
this.valueBinder = valueBinder;
}
}
通过bind方法将view、ValueBinder和枚举key三者进行关联
private final Map<Enum<?>, BindHolder> bindingView = new HashMap<>();
/**
* 通过id进行绑定
*/
public void bind(int viewId, ValueBinder<? extends View, ?> valueBinder, Enum<?> type) {
View view = rootView.findViewById(viewId);
bind(view, valueBinder, type);
}
/**
* 通过view进行绑定
*/
public void bind(View view, ValueBinder<? extends View, ?> valueBinder, Enum<?> type) {
if (view != null) {
putAndSet(new BindHolder(view, valueBinder), type);
}
}
private void putAndSet(BindHolder bindHolder, Enum<?> type) {
//dui对绑定关系进行保存
bindingView.put(type, bindHolder);
if (viewModel != null) {
//获取被绑定方法的包装类
OnBindingCallback<?> onBindingCallback = viewModel.getCallback(type);
if (onBindingCallback != null) {
//调用get方法就是调用的被绑定的方法,拿到返回值后交给valueBinder进行赋值
bindHolder.valueBinder.setValue(bindHolder.view, onBindingCallback.get());
}
}
}
最后处理来自viewModel层的数据变化事件
@Override
public void onPropertyChanged(PropertiesObservable sender, Enum<?> propertyId) {
//如果propertyId为BDR.all则更新所有已绑定的view
if (propertyId == null || propertyId.ordinal() == 0) {
resetAllValues();
return;
}
if (bindingView.containsKey(propertyId)) {
//根据枚举key找到绑定的view
BindHolder bindHolder = bindingView.get(propertyId);
if (bindHolder != null && bindHolder.view != null) {
//根据枚举key找到绑定的代理类
OnBindingCallback<?> onBindingCallback = viewModel.getCallback(propertyId);
if (onBindingCallback != null) {
//调用代理类的get方法获取最新的值后赋值给view
//noinspection unchecked
bindHolder.valueBinder.setValue(bindHolder.view, onBindingCallback.get());
}
return;
}
bindingView.remove(propertyId);
}
}
到这里基础框架就全部搭建完成了,如果想查看完整代码可通过https://github.com/aopmeta/EasyBinding (opens new window)获取,且其中还包含了对list数据的封装以及demo的演示。