大部分展示类App都是Summary->Detail结构的,Detail中常常包含了对Model的修改。这种结构有几个事关Model的问题:
- 如何使用KVO同步Model和后台View的状态
- 如何在Utils中方便获取到全局数据
Android architecture
google官方的方案是ViewModel、LiveData
- ViewModel ViewModel是整个页面数据和逻辑的集合,比较像MVC中的C+M的绑定部分。数据同步靠两点:1.每次resume(或者其他合适的时机)都重新加载Model,Model的复用由Repo保证。2.Model的所有字段都KVO绑定到View上,所有供View绑定的字段都是final的。内存不泄露由LifecycleAware保证。亦即,所有KVO字段、observer都和生命周期绑定,页面间数据传递则由主动刷新(onResume)保证。页面间的Model是不同的对象,Model中的POJO可能是相同的(依赖Repo实现)。
- LiveData LiveData与生命周期绑定,维护了内存的有效性。
这个方案非常简单和暴力,隐含的意义就是POJO数据即便泄露也不存在问题(因为Repo是不知道什么时候释放引用的,所以一旦放到Repo中,只能主动删除以释放)
由于不怕POJO泄漏,Utils中获取数据极其简单。
传递Ref,Model与生命周期绑定
基本思想就是Repo对Model做ARC,手动做简单GC。
目标是减少POJO数据的泄漏。具体与官方类似,只是Repo对POJO进行计数、在Activity#onResume时清理内存中的数据。
之所以在onResume清理,是因为绝大部分情况下,onResume是最后执行的Activate,此时的数据获取已经触发,这样即便是前一个Activity startActivity立即finish自己释放掉的资源也已经被后续页面重新持有了。
此时,不同Activity包括不同Observer都是持有同一个对象,保证了数据修改的完美同步。
不太容易做无生命周期Utils(比如说日志)Model的获取(完全与生命周期无关的逻辑,例如后台轮询定位,会拿不到数据),但是,比用单例传完之后删要简单。
这种方法较后面的方法会更符合Repo的定义,维护起来会更简单。
demo代码:
ModelHolder:
public class ModelHolder<T> {
public final T mModel;
public final String mKey;
public final Set<String> mOwners = new HashSet<>();
public ModelHolder(T model) {
mModel = model;
mKey = String.valueOf(model.hashCode());
}
}
public class Repo {
private static Repo sIntance;
private Repo(Application application) {
application.registerActivityLifecycleCallbacks(mCallback);
}
public static Repo getInstance(Application application) {
if (sIntance == null) {
synchronized (Repo.class) {
if (sIntance == null) {
sIntance = new Repo(application);
}
}
}
return sIntance;
}
private final Map<String, ModelHolder> mModels = new HashMap<>();
private final Set<String> mUnreferedKeys = new HashSet<>();
private final Map<String, Set<ModelHolder>> mOwnerModelMapping = new HashMap<>();
private Application.ActivityLifecycleCallbacks mCallback =
new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
for (String key : mUnreferedKeys) {
ModelHolder holder = mModels.get(key);
if (holder.mOwners.isEmpty()) {
mModels.remove(key);
}
}
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
release(activity);
}
};
public <T> T get(String key, Activity owner) {
ModelHolder<T> holder = mModels.get(key);
if (holder == null) {
return null;
}
String ownerKey = String.valueOf(owner.hashCode());
holder.mOwners.add(ownerKey);
Set<ModelHolder> holders = mOwnerModelMapping.get(ownerKey);
if (holders == null) {
holders = new HashSet<>();
mOwnerModelMapping.put(ownerKey, holders);
}
holders.add(holder);
return holder.mModel;
}
public <T> String put(T model) {
ModelHolder<T> holder = new ModelHolder<>(model);
mModels.put(holder.mKey, holder);
return holder.mKey;
}
private void release(Activity owner) {
String ownerKey = String.valueOf(owner.hashCode());
Set<ModelHolder> holders = mOwnerModelMapping.remove(ownerKey);
for (ModelHolder holder : holders) {
holder.mOwners.remove(ownerKey);
if (holder.mOwners.isEmpty()) {
mUnreferedKeys.add(holder.mKey);
}
}
holders.clear();
}
}
Model层做同步
现在公司App有很多数据监听发生在后台页面,重构如果贸然去掉这些监听,一定会引入很麻烦的Bug。其实还有一个巧妙的方法来解决这个问题:在Model层做同步。
Model层同步是说,使用通用的方式(EventBus之类的)保证Observer监听的不同Model对象的修改是一致的。
基本可以用CPU cache更新策略来理解。A进行修改,写回主存并在总线上发送invalidate信息,所有接收到invalidate信息的同地址的cache都从主存中读取最新数据(使用时)。
也就是说,这里的同步靠的是更新消息。
当然如果使用EventBus,除了初始值之外,全局的POJO也能够保证同一个。更究极初始化也能使用同一个POJO,但是看起来好奇怪,而且妥妥的POJO泄漏。
因为是分布式的存储,Utils似乎就拿不到Model了。
这种方法对于使用的侵入是最小的,传取都不用修改。而且如果Model本身是Observable的,实现成本是最低的。
公司App里还有个神奇的问题,Google的LiveData完全无解。Model的Id其实有两种:
- repoId, 对应着内存地址,这个id唯一标识一个对象,没有任何业务意义
- bizId,对应着业务属性的id,例如userId,指代了业务上的相同。这个Id大部分时候可以和repoId互换
但是,在公司App中,repoId和bizId是不通用的,因为不同页面下同一个bizId对应的Model是有区别的(大多是行为trace相关)。这样直接用LiveData那一套,就跪了,只能在bizId上再包装出一个数据id,非常麻烦。而任何一个其他方法,也都需要维护bizid相同的对象的数据同步,此时,这个方法就更加简便了。
分布式的Model
上面Model层同步的方法,稍加改变可以做出一个更加稳妥的方案,也可以当做最复杂,却最有效的最终方案。
Model有生命周期,且注册了EventBus。Repo和Model之间有一对事件,Repo会发出getInstance事件,全局唯一存在的Model对象响应此事件,并将自己的实例回传给Repo。当且仅当Model显示声明要有Repo临时持有时(为了解决A start B,并finish 自己导致Model被回收的情况),Model被Repo持有,并且在响应getInstance后,将自己remove掉。
Model的生命周期只有两个节点,构造时注册EventBus的监听。绑定的所有生命周期对象都inactive且不persist时,反注册EventBus监听。当然,在某个生命周期对象inactive时,要把对应的Observer清理掉。
这样,由于没有集中持有Model的地方,最大可能的减少了POJO内存泄漏的可能性。也保证了Utils随时能取到需要的数据。
demo代码:
ModelHolder
public class ModelHolder<T extends Observable> {
private final Map<Activity, List<Observer>> mObservers = new HashMap<>();
private Application.ActivityLifecycleCallbacks mCallback = new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
List<Observer> observers = mObservers.remove(activity);
if (observers == null) {
return;
}
for (Observer observer : observers) {
mModel.deleteObserver(observer);
}
if (mPersistOnDestroy) {
Repo.getInstance().persist(ModelHolder.this);
} else {
if (mObservers.isEmpty()) {
EventBus.getDefault().unregister(ModelHolder.this);
activity.getApplication().unregisterActivityLifecycleCallbacks(mCallback);
}
}
}
};
public final T mModel;
public boolean mPersistOnDestroy;
public String mKey;
public ModelHolder(Application application, T value) {
mModel = value;
mKey = String.valueOf(mModel.hashCode());
EventBus.getDefault().register(this);
application.registerActivityLifecycleCallbacks(mCallback);
}
@Subscribe
public void instanceRequested(GetInstance getInstance) {
if (getInstance.mKey.equals(mKey)) {
EventBus.getDefault().post(new InstanceGot(this));
Repo.getInstance().remove(this);
mPersistOnDestroy = false;
}
}
public void bind(Activity activity, Observer... observers) {
mObservers.put(activity, Arrays.asList(observers));
for (Observer observer : observers) {
mModel.addObserver(observer);
}
}
public static class GetInstance {
public String mKey;
public GetInstance(String key) {
mKey = key;
}
}
public static class InstanceGot {
public ModelHolder mHolder;
public InstanceGot(ModelHolder holder) {
mHolder = holder;
}
}
}
Repo
public class Repo {
private static Repo sIntance;
public static Repo getInstance() {
if (sIntance == null) {
synchronized (Repo.class) {
if (sIntance == null) {
sIntance = new Repo();
}
}
}
return sIntance;
}
private final Set<ModelHolder> mPersists = new HashSet<>();
private final ThreadLocal<Object> mHolder = new ThreadLocal<>();
private Repo() {
EventBus.getDefault().register(this);
}
public void persist(ModelHolder object) {
mPersists.add(object);
}
public void remove(ModelHolder object) {
mPersists.remove(object);
}
@Subscribe
public void onInstance(ModelHolder.InstanceGot instance) {
mHolder.set(instance.mHolder);
}
public <T> T get(String key) {
EventBus.getDefault().post(new ModelHolder.GetInstance(key));
return (T) mHolder.get();
}
}