跨Activity KVO问题思考

大部分展示类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();
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值