由于您是第一次使用 MVVM,我们可以尽量保持简单。
[ View 组件 C] ---- (observes) [ ViewModel 组件 B ] ---- [ Repository ]
根据关注点分离规则,ViewModel 应该公开 LiveData。 LiveData 使用 Observers 来观察数据变化。 ViewModel 的目的是将数据层与 UI 分离。 ViewModel 不应该知道 Android 框架类。
在 MVVM 架构中,ViewModel 的作用是从存储库中获取数据。您可以考虑使用 Room 将 json 文件存储为本地数据源,或者将 Json API 保留为远程数据源。无论哪种方式,一般实现如下:
组件 A - 实体(实现您的 getter 和 setter)
方法一:使用房间
@Entity(tableName = "file")
public class FileEntry{
@PrimaryKey(autoGenerate = true)
private int id;
private String content; // member variables
public FileEntry(String content){ // constructor
this.id = id;
this.content = content;
}
public int getId(){ // getter methods
return id;
}
public void setId(int id){ // setter methods
this.id = id;
}
public String getContent(){
return content;
}
public void setContent(String content){
this.content = content;
}
}
方法二:使用远程数据源
public class FileEntry implements Serializable{
public String getContent(){
return content;
}
private String content;
}
组件 B - ViewModel(表示层)
方法一:使用房间
当您询问如何传递 android 上下文时,您可以通过如下方式扩展 AndroidViewModel 来包含应用程序引用。这是如果您的数据库需要应用程序上下文,但一般规则是 Activity 和 Fragments 不应存储在 ViewModel 中。
假设您将“文件”作为为对象列表定义的成员变量,例如在本例中为“文件条目”对象:
public class FileViewModel extends AndroidViewModel{
// Wrap your list of FileEntry objects in LiveData to observe data changes
private LiveData<List<FileEntry>> files;
public FileViewModel(Application application){
super(application);
FilesDatabase db = FilesDatabase.getInstance(this.getApplication());
方法二:使用远程数据源
public class FileViewModel extends ViewModel{
public FileViewModel(){}
public LiveData<List<FileEntry>> getFileEntries(String content){
Repository repository = new Repository();
return repository.getFileEntries(content);
}
}
在这种情况下,getFileEntries 方法包含 MutableLiveData:
final MutableLiveData<List<FileEntry>> mutableLiveData = new MutableLiveData<>();
如果您使用 Retrofit 客户端实现,您可以使用异步回调执行类似于以下代码的操作。代码取自Retrofit 2 Guide at Future Studio,对本讨论示例进行了一些修改。
// asynchronous
call.enqueue(new Callback<ApiData>() {
@Override
public void onResponse(Call<ApiData> call, Response<ApiData> response) {
if (response.isSuccessful()) {
mutableLiveData.setValue(response.body().getContent());
} else {
int statusCode = response.code();
// handle request errors yourself
ResponseBody errorBody = response.errorBody();
}
}
@Override
public void onFailure(Call<ApiData> call, Throwable t) {
// handle execution failures like no internet connectivity
}
return mutableLiveData;
组件 C - 视图(UI 控制器)
无论你是使用方法一还是二,你都可以这样做:
FileViewModel fileViewModel = ViewModelProviders.of(this).get(FileViewModel.class);
fileViewModel.getFileEntries(content).observe(this, fileObserver);
希望这有帮助。
对性能的影响
在我看来,决定是否使用哪种方法可能取决于您正在实现多少数据调用。如果有多个,Retrofit 可能是简化 API 调用的更好主意。如果您使用 Retrofit 客户端实现它,您可能会得到类似于以下代码的内容,这些代码来自此参考 article on Android Guide to app architecture:
public LiveData<User> getUser(int userId) {
LiveData<User> cached = userCache.get(userId);
if (cached != null) {
return cached;
}
final MutableLiveData<User> data = new MutableLiveData<>();
userCache.put(userId, data);
webservice.getUser(userId).enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
data.setValue(response.body());
}
});
return data;
}
上述实现可能具有线程性能优势,因为 Retrofit 允许您使用 enqueue 进行异步网络调用并在后台线程上返回 onResponse 方法。通过使用方法 2,您可以利用 Retrofit 的回调模式在并发后台线程上进行网络调用,而不会干扰主 UI 线程。
上述实现的另一个好处是,如果您正在进行多个 api 数据调用,您可以通过上面的接口webservice 干净地获取响应,用于您的 LiveData。这使我们能够调解不同数据源之间的响应。然后,调用data.setValue 设置 MutableLiveData 值,然后根据 Android 文档将其分派给主线程上的活动观察者。
如果您已经熟悉 SQL 并且只实现了 1 个数据库,那么选择 Room Persistence Library 可能是一个不错的选择。它还使用 ViewModel,由于减少了内存泄漏的可能性,因此带来了性能优势,因为 ViewModel 在 UI 和数据类之间维护的强引用更少。
可能需要关注的一点是,您的数据库存储库(例如,FilesDatabase 实现为单例,以提供单个全局访问点,使用公共静态方法创建类实例,以便只有 1 个相同的实例数据库的任何时候打开?如果是,单例可能会被限制在应用程序范围内,如果用户仍在运行应用程序,则 ViewModel 可能会泄漏。因此请确保您的 ViewModel 使用 LiveData 来引用视图。此外,使用延迟初始化可能会有所帮助,以便在尚未创建先前实例的情况下使用 getInstance 方法创建 FilesDatabase 单例类的新实例:
private static FilesDatabase dbInstance;
// Synchronized may be an expensive operation but ensures only 1 thread runs at a time
public static synchronized FilesDatabase getInstance(Context context) {
if (dbInstance == null) {
// Creates the Room persistent database
dbInstance = Room.databaseBuilder(context.getApplicationContext(), FilesDatabase.class, FilesDatabase.DATABASE_NAME)
另一件事是,无论您为 UI 选择 Activity 还是 Fragment,您都将使用 ViewModelProviders.of 来保留您的 ViewModel,而您的 Activity 或 Fragment 的范围仍然存在。如果您正在实现不同的活动/片段,您的应用程序中将有不同的 ViewModel 实例。
例如,如果您正在使用 Room 实现您的数据库,并且您希望允许您的用户在使用您的应用程序时更新您的数据库,那么您的应用程序现在可能需要在您的主要活动和更新活动中使用相同的 ViewModel 实例。尽管是一种反模式,但 ViewModel 提供了一个带有空构造函数的简单工厂。您可以使用public class UpdateFileViewModelFactory extends ViewModelProvider.NewInstanceFactory{ 在 Room 中实现它:
@Override
public <T extends ViewModel> T create(@NotNull Class<T> modelClass) {
return (T) new UpdateFileViewModel(sDb, sFileId);
上面,T是create的类型参数。在上面的工厂方法中,类 T 扩展了 ViewModel。成员变量 sDb 用于 FilesDatabase,sFileId 用于表示每个 FileEntry 的 int id。
如果您想了解更多关于性能成本的信息,Android 的 Persist Data 部分的 article 可能比我的 cmets 更有用。