安卓应用移植鸿蒙(四):移植Android的ViewModel和LiveData

经过几天的努力,终于把ViewModel和LiveData移植过来了,代码开源地址:

ViewModel_LiveData_for_ohoshttps://gitee.com/ethan-osc_admin/viewmodel_for_ohosicon-default.png?t=L892https://gitee.com/ethan-osc_admin/viewmodel_for_ohos

一.基本介绍

        ViewModel和LiveData做安卓开发的小伙伴都很熟悉了,我就不过多介绍了,有了他们,我们可以实现MVVM框架,可以让系统自动管理数据的生命周期了,是安卓开发中不可获取的重要组件。笔者在移植网络的库的时候,需要用到这两个组件,所以移植了过来,代码是基于androidx.life.xxx的2.2.0版本移植而来。

二.基本修改点

1.安卓中的LifeCycleOwner

鸿蒙中叫ILifecycle,用以感知生命周期,这个变量可以全局替换

2.Lifecycle

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代替

3.boolean shouldBeActive()

修改了一个比较重要的函数 boolean shouldBeActive() ,数据是否继续分发,就是依赖于这个函数,具体看注释

@Override
boolean shouldBeActive() {
            //和安卓有所区别,安卓是Event和State分开的,而鸿蒙是放一起的,这里的本意是判断
            //页面是否是判断页面是否已经将要显示,或者已经显示,对应的安卓状态是started和resume
            //而鸿蒙里面对应的状态是 ON_ACTIVE
            return mOwner.getLifecycle().getLifecycleState()
                                .compareTo(Lifecycle.Event.ON_ACTIVE) == 0;
}

4.安卓和鸿蒙的基础单元的生命周期

鸿蒙的生命周期状态少于安卓,但是基本能对应上,记住几个常用的:

安卓 : 鸿蒙

onCreate:on_start

onResume:on_active

onStart:on_active

onDestory:on_stop

5.SavedStateViewModelFactory

移植的时候,放弃了SavedStateViewModelFactory,这个类的本意是创建的Factory存入Map,以便后面可以复用,由于代码比较复杂,所涉及的安卓库较多,放弃了移植,不过目前根据Demo看来,ViewModel和LiveData的功能都正常

6.增加非粘粘模式(NoStick)

LiveData做了一点修改,Observe的时候,在全局模式的时候,刚创建的Slice2在刚创建的时候会收到之前的Value更新的信息,这个也是安卓使用者,Activity对应多个Fragment中数据串扰的痛点所在,所以本库在保证原汁原味的安卓大部分的代码下,做了创新,多了一个observeNoStick接口,意思创建后,不接收过去的任何信息。避免共享的时候,数据串扰

三.如果使用鸿蒙里的ViewModel

1.集成和AppCompatActivity以及Fragment一样的类

安卓里面,之所以可以使用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 {
          ......  
}

2.举个例子

因为鸿蒙里,使用了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创建之后的消息

安卓应用移植鸿蒙(四):移植Android的ViewModel和LiveData_第1张图片

可以看到目前只起来了Slice1,并且分别点击了发送局部Llife事件,和发送全局Life事件,其实只有Slice1收到消息,当然,因为SIlce2还没被创建,然后我们点击跳转Slice2

安卓应用移植鸿蒙(四):移植Android的ViewModel和LiveData_第2张图片

 可以看到发生三件事:

1)Slice1和Slice2的局部VIewModel,他们的地址是不同的,这符合要验证的第一点

2)SLice1和Slice的全局VIewModel(生命周期依赖于Ability)所具有的地址是一样,他们是一个对象,符合我们要验证的第二点

3)Slice2创建的时候,没收到了SLice1的局部事件,收到了全局事件,符合LiveData的粘粘模式,而非粘粘模式监听的没收到,这符合第三点

然后再次点击Slice2的发送全局事件的按钮,看log

安卓应用移植鸿蒙(四):移植Android的ViewModel和LiveData_第3张图片

此时,因为Slice1处于Inavtive状态,所以Slice的全局事件,它是收不到的,局部事件更不可能收到了,而因为Slice2处于Active状态了,所以他监听的两个全局事件(粘粘模式和非粘粘模式)都收到了事件,符合第四点预期

 如果此时点击返回,那么的预想是,SLice1可以收到刚刚Slice2发送的全局事件,那么整个流程就通常了,我们返回看看:

安卓应用移植鸿蒙(四):移植Android的ViewModel和LiveData_第4张图片

 符合预期4!

 五)结论

经过笔者多次的验证,目前的ViewModel已经可以完全正常工作,希望大家多多体验,可以看看笔者开源后的代码!

ViewModel_LiveData_for_ohosicon-default.png?t=L892https://gitee.com/ethan-osc_admin/viewmodel_for_ohos#%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E移植完之后,有个和这个很相似的通讯库叫LiveEventBus,打算下来移植这个,估计还需要一段时间才能和大家见面

你可能感兴趣的:(HarmonyOS(鸿蒙)学习,android,harmonyos)