经过几天的努力,终于把ViewModel和LiveData移植过来了,代码开源地址:
ViewModel_LiveData_for_ohoshttps://gitee.com/ethan-osc_admin/viewmodel_for_ohoshttps://gitee.com/ethan-osc_admin/viewmodel_for_ohos
ViewModel和LiveData做安卓开发的小伙伴都很熟悉了,我就不过多介绍了,有了他们,我们可以实现MVVM框架,可以让系统自动管理数据的生命周期了,是安卓开发中不可获取的重要组件。笔者在移植网络的库的时候,需要用到这两个组件,所以移植了过来,代码是基于androidx.life.xxx的2.2.0版本移植而来。
鸿蒙中叫ILifecycle,用以感知生命周期,这个变量可以全局替换
Lifecycle EVENT安卓里和State是分开的,而鸿蒙里是合并了,发生了EVENT代表了置为了相应状态
//摘录自己安卓的Lifecycle 类
public enum Event {
/**
* Constant for onCreate event of the {@link LifecycleOwner}.
*/
ON_CREATE,
/**
* Constant for onStart event of the {@link LifecycleOwner}.
*/
ON_START,
/**
* Constant for onResume event of the {@link LifecycleOwner}.
*/
ON_RESUME
.....
}
public enum State {
DESTROYED,
INITIALIZED,
.....
RESUMED;
}
//摘录自Lifecycle类
public static enum Event {
UNDEFINED,
ON_START,
ON_INACTIVE,
ON_ACTIVE,
ON_BACKGROUND,
ON_FOREGROUND,
ON_STOP;
private Event() {
}
}
所以原来的代码里有很多状态相关的类,都用鸿蒙的Event代替
修改了一个比较重要的函数 boolean shouldBeActive() ,数据是否继续分发,就是依赖于这个函数,具体看注释
@Override
boolean shouldBeActive() {
//和安卓有所区别,安卓是Event和State分开的,而鸿蒙是放一起的,这里的本意是判断
//页面是否是判断页面是否已经将要显示,或者已经显示,对应的安卓状态是started和resume
//而鸿蒙里面对应的状态是 ON_ACTIVE
return mOwner.getLifecycle().getLifecycleState()
.compareTo(Lifecycle.Event.ON_ACTIVE) == 0;
}
鸿蒙的生命周期状态少于安卓,但是基本能对应上,记住几个常用的:
安卓 : 鸿蒙
onCreate:on_start
onResume:on_active
onStart:on_active
onDestory:on_stop
移植的时候,放弃了SavedStateViewModelFactory,这个类的本意是创建的Factory存入Map,以便后面可以复用,由于代码比较复杂,所涉及的安卓库较多,放弃了移植,不过目前根据Demo看来,ViewModel和LiveData的功能都正常
LiveData做了一点修改,Observe的时候,在全局模式的时候,刚创建的Slice2在刚创建的时候会收到之前的Value更新的信息,这个也是安卓使用者,Activity对应多个Fragment中数据串扰的痛点所在,所以本库在保证原汁原味的安卓大部分的代码下,做了创新,多了一个observeNoStick接口,意思创建后,不接收过去的任何信息。避免共享的时候,数据串扰
安卓里面,之所以可以使用ViewModel和LiveData,是因为Activity和Fragment都继承了LifeCycleOwner,ViewModelStoreOwner和HasDefaultViewModelProviderFactory,而鸿蒙里面Ability和Slice只实现了ILifecycle,所以笔者参照Android的代码,自定义了CompatAbility,和CompatSlice,使用ViewModel和LiveData需要继承这两个基础类
public class CompatSlice extends AbilitySlice implements
ViewModelStoreOwner, HasDefaultViewModelProviderFactory {
......
}
public class CompatAbility extends Ability implements
ViewModelStoreOwner, HasDefaultViewModelProviderFactory {
......
}
因为鸿蒙里,使用了Ability为承载单元,Slice为子类单元(类似于谷歌推荐的一个Activity,多个Fragment的方式),所以主要验证这种场景下ViewModel和LiveData是否工作正常,先定义ViewModel的类,以及Model的数据类型,这里只简单打印日志
public class NoteViewModel extends ViewModel {
public MutableLiveData> notes = new MutableLiveData<>();
public void fetchNotes() {
notes.setValue(NoteModel.fetchNotes());
}
public void fetchGlobalNotes() {
notes.setValue(NoteModel.fetchGlobalNotes());
}
}
//用法和Android一样,然后定义个辅助类,验证数据
public class NoteModel {
public String content;
public String modifyTime;
private NoteModel(String content, String modifyTime){
this.content = content;
this.modifyTime = modifyTime;
}
public static List fetchNotes(){
List noteModels = new ArrayList<>();
noteModels.add(new NoteModel("这是一个普通内容","2021-03-31"));
return noteModels;
}
public static List fetchGlobalNotes(){
List noteModels = new ArrayList<>();
noteModels.add(new NoteModel("this is 全局Life","2021-08-31"));
return noteModels;
}
}
然后定义两个Slice,可以相互切换,代码如下:
Slice1代码:
//调用示例主要片段:
public class LifeAbilitySlice extends CompatSlice {
private static final String TAG = "LifeAbilitySlice";
private NoteViewModel vm;
private NoteViewModel globalVm;
@Override
protected void onStart(Intent intent) {
super.onStart(intent);
setUIContent(ResourceTable.Layout_life_request_layout);
LifeAbilitySlice2 life2 = new LifeAbilitySlice2();
findComponentById(ResourceTable.Id_btn_life).setClickedListener(component -> {
vm.fetchNotes();
});
findComponentById(ResourceTable.Id_btn_life_global).setClickedListener(component -> {
globalVm.fetchGlobalNotes();
});
findComponentById(ResourceTable.Id_btn_to_life2).setClickedListener(component -> {
present(life2,new Intent());
});
initSliceViewModel();
initGlobalViewModel();
}
//验证本Slice中的事件响应
private void initSliceViewModel(){
ViewModelProvider provider = ViewModelProviders.of(this);
vm = provider.get(NoteViewModel.class);
Log.d(TAG, "slice vm addr:" + vm);
vm.notes.observe(this, noteModels -> {
Log.d(TAG, "noteModels:" + noteModels.get(0).content);
});
}
//验证基于所依赖的Ability的事件响应,经常出现的场景是多个Slice公有一个Ability的情况
private void initGlobalViewModel(){
ViewModelProvider globalProvider = ViewModelProviders.of((CompatAbility) getAbility());
globalVm = globalProvider.get(NoteViewModel.class);
Log.d(TAG, "global vm addr:" + globalVm);
globalVm.notes.observe(this, noteModels -> {
Log.d(TAG, "global noteModels:" + noteModels.get(0).content);
});
}
}
Slice2的代码
public class LifeAbilitySlice2 extends CompatSlice {
private static final String TAG = "LifeAbilitySlice2";
private NoteViewModel vm;
private NoteViewModel globalVm;
@Override
protected void onStart(Intent intent) {
super.onStart(intent);
setUIContent(ResourceTable.Layout_life2_request_layout);
findComponentById(ResourceTable.Id_btn_life).setClickedListener(component -> {
vm.fetchNotes();
});
findComponentById(ResourceTable.Id_btn_life_global).setClickedListener(component -> {
globalVm.fetchGlobalNotes();
});
initSliceViewModel();
initGlobalViewModel();
}
private void initSliceViewModel(){
ViewModelProvider provider = ViewModelProviders.of(this);
vm = provider.get(NoteViewModel.class);
Log.d(TAG, "slice vm addr:" + vm);
vm.notes.observe(this, noteModels -> {
Log.d(TAG, "noteModels:" + noteModels.get(0).content);
appendtext(noteModels.get(0).content);
});
}
private void initGlobalViewModel(){
ViewModelProvider globalProvider = ViewModelProviders.of((CompatAbility) getAbility());
globalVm = globalProvider.get(NoteViewModel.class);
Log.d(TAG, "global vm addr:" + globalVm);
globalVm.notes.observe(this, noteModels -> {
Log.d(TAG, "observe global noteModels:" + noteModels.get(0).content);
});
globalVm.notes.observeNoSticky(this, noteModels -> {
Log.d(TAG, "observeNoSticky global noteModels:" + noteModels.get(0).content);
});
}
}
两个的代码基本一样,SLice1只比Slice2多了一个跳转Slice2的功能
我们要验证以下几个:
1)依赖于Slice生命周期的每次创建的对象,是新的对象
2)依赖于全局同一个Ability的生命周期,每次创建的ViewModel对象,是唯一的(只有一个实例)
3)Slice之间的消息不互相串扰,即SLice2和Slice1之间非全局消息互相不干扰
4)全局消息,粘粘(原来的模式)在Observe的消息可以收到,不论是创建之前还是之后的,非粘粘模式下,只收到Observe创建之后的消息
可以看到目前只起来了Slice1,并且分别点击了发送局部Llife事件,和发送全局Life事件,其实只有Slice1收到消息,当然,因为SIlce2还没被创建,然后我们点击跳转Slice2
可以看到发生三件事:
1)Slice1和Slice2的局部VIewModel,他们的地址是不同的,这符合要验证的第一点
2)SLice1和Slice的全局VIewModel(生命周期依赖于Ability)所具有的地址是一样,他们是一个对象,符合我们要验证的第二点
3)Slice2创建的时候,没收到了SLice1的局部事件,收到了全局事件,符合LiveData的粘粘模式,而非粘粘模式监听的没收到,这符合第三点
然后再次点击Slice2的发送全局事件的按钮,看log
此时,因为Slice1处于Inavtive状态,所以Slice的全局事件,它是收不到的,局部事件更不可能收到了,而因为Slice2处于Active状态了,所以他监听的两个全局事件(粘粘模式和非粘粘模式)都收到了事件,符合第四点预期
如果此时点击返回,那么的预想是,SLice1可以收到刚刚Slice2发送的全局事件,那么整个流程就通常了,我们返回看看:
符合预期4!
经过笔者多次的验证,目前的ViewModel已经可以完全正常工作,希望大家多多体验,可以看看笔者开源后的代码!
ViewModel_LiveData_for_ohoshttps://gitee.com/ethan-osc_admin/viewmodel_for_ohos#%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E移植完之后,有个和这个很相似的通讯库叫LiveEventBus,打算下来移植这个,估计还需要一段时间才能和大家见面