[聚合文章] Google Architecture ViewModel

软件架构 2017-12-06 16 阅读

关于ViewModel 介绍,文章不过多阐述。官方文档

本文将从三个方面做一定阐述

为什么需要ViewModel

  • 你需要处理配置变化

    • 笔者认为,用户可以随时改变配置(i.e 旋转屏幕,切换语言,切换系统文字大小 ...),这可能会导致当前Activity重建,这些都不受开发者的控制,但是你又不得不处理它
    • 可能很多APP在配置清单文件中申明了每一个Activity orientation = portrait ,但是你无法禁止用户去改变语言、文字大小。这样就可能会导致Activity被移除或者重新创建
  • 为什么 onSaveInstanceState 依旧不够

    传统的做法都是在配置发生变化即 onSaveInstanceState 方法去save data, 在onCreate去restore data

    但是这里有两个限制

    • onSaveInstanceState 方法不能够缓存较大的数据,笔者之前尝试缓存上百兆数据发现抛出了 TransactionTooLargeException
    • 保存的数据一定需要实现 serializable 或者 Parceable , 但是有时候这些数据来自第三方库,我们不能修改它,对于某些场景,很难在 onSaveonSaveInstanceState 中保存数据

基于上述两点,ViewModel应运而生

  • 配置改变前后数据存储与恢复

一些例子

基础功能

public class ZeroViewModel extends ViewModel {
    public User user;
}
public class ZeroDemo extends AppCompatActivity {
    private TextView tv;
    private ZeroViewModel vm;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tv_btn);
        tv = findViewById(R.id.tv_simple);
        
        vm = ViewModelProviders.of(this).get(ZeroViewModel.class);
        System.out.println("szw vm.user = " + vm.user);
    }
    
    // android:onClick="onClickSimpleButton"
    public void onClickSimpleButton(View v) {
        vm.user = new User(23, "jorden");
    }
}

旋转屏幕,vm.user 依旧 != null

同一个Activity不同实例

  1. 同时存在两个实例
public class SameClass01 extends AppCompatActivity {
    private TextView tv;
    private ZeroViewModel vm;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tv_btn);
        tv = findViewById(R.id.tv_simple);

        vm = ViewModelProviders.of(this).get(ZeroViewModel.class);
        System.out.println("szw SameClass01 : " + vm.user);

    }
    // launch the second instance
    // android:onClick="onClickSimpleButton"
    public void onClickSimpleButton(View v) {
        vm.user = new User(100, "SuperMario");
        startActivity(new Intent(this, SameClass01.class));
    }
}

即使有两个同类的Activity实例,第一个vm.user 持有的依旧是Mario,第二个vm.user 持有null

  1. finish再重新创建
public class SameClass02 extends AppCompatActivity {
    private TextView tv;
    private ZeroViewModel vm;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tv_btn);
        tv = findViewById(R.id.tv_simple);
        vm = ViewModelProviders.of(this).get(ZeroViewModel.class);
        System.out.println("szw SameClass02 onCreate() : " + vm.user);
    }
    // android:onClick="onClickSimpleButton"
    public void onClickSimpleButton(View v) {
        vm.user = new User(22, "test");
    }

    // android:onClick="onClickSimpleButton2"
    public void onClickSimpleButton2(View v) {
        System.out.println("szw SameClass02 : saved = "+vm.user);
    }
}

先启动SameClass02 ,执行onClickSimpleButton,finish重新打开,日志输出null

上述两个例子表现正常

和Static申明的变量比较

  1. 基础比较
public class SameVm {
    public static User user;
}
public class ZeroDemo extends AppCompatActivity {
    private TextView tv;
    private ZeroViewModel vm;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tv_btn);
        tv = findViewById(R.id.tv_simple);

        String value = savedInstanceState == null ? "emptyBundle" : savedInstanceState.getString("key");
        System.out.println("szw onCreate() " + value);

        vm = ViewModelProviders.of(this).get(ZeroViewModel.class);
        System.out.println("szw vm.user = " + vm.user);

        System.out.println("szw static = "+SameVm.user);
    }

    // android:onClick="onClickSimpleButton"
    public void onClickSimpleButton(View v) {
        vm.user = new User(23, "jorden");
        SameVm.user = new User(21, "king");
    }
}

旋转屏幕后,日志输出

szw vm.user = User{id=23, name='jorden'}
szw static = User{id=21, name='king’}
  1. 终止应用 Terminate Application

和1同样的操作

szw vm.user = null
szw static = null

从上面两个例子,感觉没什么不同。他们都能缓存数据,终止应用程序都会被销毁

它们的不同之处:

  • ViewModel 主要是为了解耦,有点类似于MVP中的P,ViewModel 是MvvM中VM。你能在ViewModel中做异步操作(i.e访问网络),你可以改变data并且让View接受到通知LiveData
  • static value 能被任何类修改,但是ViewModel 是Activity的私有变量,有点类似ThreadLocal
  • ViewModel 可以判断Activity是正常销毁或者配置改变,进而做出不同的响应,finish->removedata ,configurationchange->savedata,静态变量却不能

相关注意事项

  1. ViewModel不要应用Activity等相关实例,容易造成内存泄漏
  2. 如果你需要在ViewMolde中获取Resource LocationManager等系统服务,可以继承AndroidViewModel
  3. ViewModel本身不支持事件模型(EventBus),你可以使用LiveData,当然为了解决旋转屏幕后,再次注册Observer,重复提示,可以使用 SingleLiveEvent

源码如下

public class DupliViewModel extends ViewModel {
    private SingleLiveEvent<String> message = new SingleLiveEvent<>();

    public void fetchMessage(){
        message.setValue("A New Value");
    }

    public LiveData<String> getMessage() {
        return message;
    }
}
public class DupliObserverDemo extends AppCompatActivity {
    private TextView tv;
    private DupliObserverDemo self;
    private DupliViewModel vm;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tv_btn);
        self = this;
        tv = findViewById(R.id.tv_simple);

        vm = ViewModelProviders.of(this).get(DupliViewModel.class);
        vm.getMessage().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                System.out.println("szw updated ~");
                Toast.makeText(self, "updated "+s, Toast.LENGTH_SHORT).show();
            }
        });
    }
    
    // android:onClick="onClickSimpleButton"
    public void onClickSimpleButton(View v) {
        vm.fetchMessage();
    }

}

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。