原文链接:Anthony的博客
推荐链接:安卓架构文章合集
1 前言
在本系列文章从零开始搭建android框架系列之前我多次提到了官方mvp项目的构建。并应用到了项目MVPCommon中。但是细心的你肯定都会发现,之前的文章都在整体上对MVP 的使用进行了说明,却对其中的model层一言带过。包括数据也是大多采用假数据。
使用了MVP,我们肯定不会再像以前网络访问数据,SharedPreference保存数据,本地数据库保存,缓存数据等的处理分散于每个activity或者fragment之间。数据的获取、存储、数据状态变化都将是Model层的任务。RxJava,Retrofit,EventBus,SqlBrite等技术都会在后续得到分析和使用。
下面的文章将会从一些优秀的模板代码分析出发,研究MVP中model层的设计,后续的文章将会在这些基础上继续分析和使用。
这里整理网络上也有不少关于这方面优秀文章,将会在参考资料中给出。
2 google官方mvp中model层设计
之前两篇文章分别对官方google官方架构MVP解析与实战和Google官方MVP+Dagger2架构详解项目进行了解析,并没有对其中的model层进行分析,这里单独抽取出来和大家共同学习一下。其实该项目中Model层最大的特点是被赋予了数据获取的职责,与我们平常Model层只定义javabean,实体对象截然不同。实例中,数据的获取、存储、数据状态变化都是Model层的任务,Presenter会根据需要调用该层的数据处理逻辑并在需要时将回调传入。
我们来看TaskDetailPresenter 的 start() 方法:
@Override
public void start() {
openTask();
}
private void openTask() {
// 判空处理
if (null == mTaskId || mTaskId.isEmpty()) {
mTaskDetailView.showMissingTask();
return;
}
// 更新状态
mTaskDetailView.setLoadingIndicator(true);
// 获取该条Task数据
mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
// The view may not be able to handle UI updates anymore
// View已经被用户回退
if (!mTaskDetailView.isActive()) {
return;
}
// 获取到task数据,并更新UI
mTaskDetailView.setLoadingIndicator(false);
if (null == task) {
mTaskDetailView.showMissingTask();
} else {
showTask(task);
}
}
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
// 显示数据获取失败时的状态
if (!mTaskDetailView.isActive()) {
return;
}
mTaskDetailView.showMissingTask();
}
});
}
可以看到的是presenter中调用了mTaskDetailView.setLoadingIndicator(true);
中更新view层的状态之后,接着调用mTasksRepository.getTask......
获取数据,并且在数据获取成功和失败后,分别处理回调方法onTaskLoaded
,onDataNotAvailable
,并在其中更新view。将数据的操作完全交给TasksRepository
处理。
我们接着看 TasksRepository 中的getTask() 方法,
@Singleton
public class TasksRepository implements TasksDataSource {
......
private final TasksDataSource mTasksRemoteDataSource;
private final TasksDataSource mTasksLocalDataSource;
@Inject
TasksRepository(@Remote TasksDataSource tasksRemoteDataSource,
@Local TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = tasksRemoteDataSource;
mTasksLocalDataSource = tasksLocalDataSource;
}
......
/**
* Gets tasks from local data source (sqlite) unless the table is new or empty. In that case it
* uses the network data source. This is done to simplify the sample.
*/
@Override
public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
checkNotNull(taskId);
checkNotNull(callback);
Task cachedTask = getTaskWithId(taskId);
// 缓存获取数据,并回调
if (cachedTask != null) {
callback.onTaskLoaded(cachedTask);
return;
}
// 本地获取数据
mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
callback.onTaskLoaded(task);
}
@Override
public void onDataNotAvailable() {
//本地获取数据失败,使用网络数据
mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
callback.onTaskLoaded(task);
}
@Override
public void onDataNotAvailable() {
callback.onDataNotAvailable();
}
});
}
});
}
}
我们发现 TasksRepository 维护了两个数据源,一个是本地mTasksLocalDataSource
,一个是远程mTasksRemoteDataSource
。从getTask
方法中可以看到这里首先从缓存获取数据,如果获取成功则直接回调·callback.onTaskLoaded(cachedTask);`,接着从本地获取,获取失败后再从网络获取 ,这也和处理图片的三级缓存策略一样。
我们发现TasksRepository类都实现了 TasksDataSource 接口:
public interface TasksDataSource {
interface LoadTasksCallback {
void onTasksLoaded(List tasks);
void onDataNotAvailable();
}
interface GetTaskCallback {
void onTaskLoaded(Task task);
void onDataNotAvailable();
}
void getTasks(@NonNull LoadTasksCallback callback);
void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
void saveTask(@NonNull Task task);
void completeTask(@NonNull Task task);
void completeTask(@NonNull String taskId);
void activateTask(@NonNull Task task);
void activateTask(@NonNull String taskId);
void clearCompletedTasks();
void refreshTasks();
void deleteAllTasks();
void deleteTask(@NonNull String taskId);
}
可以发现我们正是在这里TasksDataSource
中定义了内部接口GetTaskCallback
。从而实现TasksRepository
中数据获取的时候的回调。
为了简化整个模板代码的操作,这里只在
getTasks()
和getTask()
方法中添加了回调并不是说其他地方不需要数据的回调。
还记得之前的Google官方MVP+Dagger2架构详解文章讲解的整个实例app的TasksRepositoryComponent
在整个应用的Application中初始化。
@Singleton
@Component(modules = {TasksRepositoryModule.class, ApplicationModule.class})
public interface TasksRepositoryComponent {
TasksRepository getTasksRepository();
}
我们可以看到我们将 TasksRepositoryModule
放在mock下,做mock测试,看下图:
我们也在
TasksRepositoryModule
中提供了数据的实例,也正是在mock中提供了假数据
FakeTasksRemoteDataSource
替换了
TasksRemoteDataSource
.
@Module
public class TasksRepositoryModule {
@Singleton
@Provides
@Local
TasksDataSource provideTasksLocalDataSource(Context context) {
return new TasksLocalDataSource(context);
}
@Singleton
@Provides
@Remote
TasksDataSource provideTasksRemoteDataSource() {
return new FakeTasksRemoteDataSource();
}
}
到这里也就完成了整个数据model层的提供,我们也可以看到Dagger2再次显示了它的威力。
总结:
最后,我们再来看这张图。Fragment作为View,View和Presenter通过Activity来进行关联,Presenter对数据的调用是通过TasksRepository来完成的,而TasksRepository维护着它自己的数据源和实现。
到这里你肯定明白了为什么我们需要Repository层了---->屏蔽底层细节。
上层(activity/fragment/presenter)不需要知道数据的细节(或者说 - 数据源),来自于网络、数据库,亦或是内存等等。如此,一来上层可以不用关心细节,二来底层可以根据需求修改,不会影响上层,两者的分离用可以帮助协同开发。
3 android-boilerplate 中model层的设计
如果你关注安卓架构,之前肯定关注过Android Application Architecture(翻译文章 Android应用架构- 小鄧子的)这篇文章。其中的示例项目android-boilerplate也是一个基于MVP架构的框架,加入EventBus(Otto),RxJava,Retrofit,SqlBrite等。其中的model层有很多值得借鉴的地方。这里再从这整个架构图来学习一下相应的思路。
View(视图)层:Activities,Fragment以及ViewGroup等在这一层。处理用户的交互和输入事件,并且触发Presenter中的相应操作。
Presenter层 :Presenters 订阅(subscibe) RxJava的
Observables
,负责处理订阅周期,处理由DataManager
提供的数据,并调用View层中的相应方法展示数据。
Model (数据)层: 负责获取,保存,缓存以及修改数据。负责与本地数据库,其他数据存储,
restful APIs
,以及第三方SDKs 交互。在此架构中,Model层被划分为两个部分:许多helpers类和一个DataManager
.helpers类的数量在不同的工程中不尽相同,但是每个都有自己的功能。比如:通过SharedPreferences
与数据进行交互的PreferHelper
,通过SqlBrite提供与数据库交互的DatabaseHelper
,DataManager
结合并且转化不同的Helpers类为Rx操作符,向Presenter层提供Observables
类型的数据(provide meaningful data to the Presenter),并且同时处理数据的并发操作(group actions that will always happen together.)。这一层也包含实际的model类,用于定义当前数据架构。
因此,我们可以看到的优点是:
1 Activity和Fragment变得非常轻量。他们唯一的职责就是建立/更新UI和处理用户事件。因此,他们变得更容易维护。
2 RxJava的Observable和操作符避免了嵌套回调的出现,同时如果数据model层出现数据错误,我们也会在presenter层得到处理,在presenter层调用相应的view层的方法显示错误信息。
3 现在我们通过模拟View Layer可以很容易的编写出单元测试。之前这些代码是View Layer的一部分,所以我们很难对它进行单元测试。整个架构变得测试友好。
4 如果DataManager变得臃肿,我们可以通过转移一些代码到Presenter来缓解这个问题。
5 通过引入Event Bus (事件总线,这个项目使用的是otto)。它允许我们在Data Layer中发送事件,以便View Layer中的多个组件都能够订阅到这些事件。比如DataManager
中的退出登录方法可以发送一个事件,订阅这个事件的多个Activity在接收到该事件后就能够更改它们的UI视图,从而显示一个登出状态。
6 这里的DataManager
也扮演了官方示例项目中Respository
的作用,屏蔽底层细节。
7 引入Dagger2 ,添加依赖注入,实现组件的重用,也就使得测试变得更加容易.
未完待续......
参考链接:
1 从零开始的Android新项目5 - Repository层(上) Retrofit、Repository组装
2 从零开始的Android新项目6 - Repository层(下) Realm、缓存、异常处理
3 android architecture architecture guidelines
4 Android应用架构- 小鄧子的
5 Android官方MVP架构项目解析
6 从零开始搭建android框架系列
7 google官方架构MVP解析与实战
8 Google官方MVP+Dagger2架构详解
9 Android Application Architecture
10 完美的安卓 model 层架构(上)
11 完美的安卓 model 层架构(下)