Google TODO-MVP详解
简单介绍
在日常的开发当中,经常会遇到需求的变动。这个东西是真的难以避免的,所以对于产品的基础框架就比较重要了。Google大大在I/O大会上提出来了Android开发方式是属于MVP的,即Model+View+Presenter。
另外在GitHub上面放出来了一个样例,供开发者学习,这里可以看到整个项目的简介。
Google通过一个TODO类的APP讲解了如何使用一些第三方库,已经如何基于他们构建自己的APP的框架。
目前的状态
Stable samples
Sample | Description |
---|---|
todo‑mvp | Demonstrates a basic Model‑View‑Presenter (MVP) architecture and provides a foundation on which the other samples are built. This sample also acts as a reference point for comparing and contrasting the other samples in this project. |
todo‑mvp‑loaders | Fetches data using the Loaders API. |
todo‑databinding | Uses the Data Binding Library. |
todo‑mvp‑clean | Uses concepts from Clean Architecture. |
todo‑mvp‑dagger | Uses Dagger2 to add support for dependency injection. |
todo‑mvp‑contentproviders | Based on the todo-mvp-loaders sample, this version fetches data using the Loaders API, and also makes use of content providers. |
todo‑mvp‑rxjava | Uses RxJava to implement concurrency, and abstract the data layer. |
todo‑mvvm‑databinding | Based on the todo-databinding sample, this version incorporates the Model‑View‑ViewModel pattern. |
Samples in progress
Sample | Description |
---|---|
dev‑todo‑mvp‑tablet | Adds a master and detail view for tablets. |
dev‑todo‑mvvm‑rxjava | Based on the todo-rxjava sample, this version incorporates the Model‑View‑ViewModel pattern. |
可以看到上面的一些库在Android中都比较常用,这一次我们先从最基础的todo-mvp来看。
可以在上面的地址中获取,也可以直接在命令行中采用
git clone https://github.com/googlesamples/android-architecture.git
的方法获取。
需要切换到todo-mvp分支中
git checkout todo-mvp
在一般阅读代码的时候,我会采取新建一个read-code的分支,便于自己看到自己的改动。
git checkout -b read-code
准备工作已经OK,我们来看看该项目的模块结构。
模块结构
整体来看Google大法是通过实际的业务模块来划分包的,当然你要是通过UI、Util、Data来划分也一点没有问题,个人习惯而已。如下:
模块结构- BasePresenter & BaseView是所有的P和V的基础接口,BasePresenter中有一个start()方法,V可以通过改方法调用P中的业务逻辑。BaseView中有一个setPresenter()方法,通过该方法,在P的构造函数中将V关联起来。
- data中的数据源分为了远程TasksRemoteDataSource和本地TasksLocalDataSource
- addedittask顾名思义,这个模块有两种状态(添加和修改)在这个包里分了四块
- AddEditTaskContract即合约类,在其中有两个内部接口,分别定义了V所要控制的UI逻辑和P所控制的业务逻辑。
- AddEditTaskPresenter则为实现了Contract内部接口的P,在其中有业务的逻辑。
- AddEditTaskFragment则为实现了Contract内部接口的V,
在其中有UI的部分操作。 - 肯定有人就会有疑问了,为何上面MVP都已经有了,那AddEditTaskActivity的作用又是什么呢?我所理解的Activity应该属于一个容器,在这个容器中做了一部分简单的初始化UI操作,如控制ToolBar的样式、加载Fragment等。
代码分析
AddEditTaskActivity分析
先来看看AddEditTaskActivity,顾名思义有「添加任务」和「编辑任务」的功能。这个Activity不是我们所认为的标准意义上的View类,上面提到了,它所做的工作。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addtask_act);
// Set up the toolbar.
...
// Add Fragment
...
// Create the presenter
mAddEditTaskPresenter = new AddEditTaskPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
addEditTaskFragment,
shouldLoadDataFromRepo);
}
这个Activity处理了部分UI初始化工作,同时,创建了Presenter和载入了Fragment,即通过构造函数将V和P做了联系。
这里需要注意的是,在构造函数的第二个参数tasksPepository是通过Injection注入的。很多新手看到这个,就会有疑问,怎么找不到这个类。其实是这样的,在文件目录中,我们看到了和app同级的目录,存在mock和prod。其次在app.gradle里面设置了productFlavors来控制发行的版本。因此我们去mock文件夹下就能找到Injection类了。
public class Injection {
public static TasksRepository provideTasksRepository(@NonNull Context context) {
checkNotNull(context);
return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
TasksLocalDataSource.getInstance(context));
}
}
这里通过注入,写入了一个假的远程Task数据源,用来做测试。
AddEditTaskContract分析
这是一个合约类,内部有两个内部接口,分别继承了V和P的基类,同时拓展了部分方法。
public interface AddEditTaskContract {
//View中实现了UI的调用逻辑
interface View extends BaseView<Presenter> {
void showEmptyTaskError();
...
}
interface Presenter extends BasePresenter {
//Presenter中实现了业务逻辑
void saveTask(String title, String description);
...
}
}
AddEditTaskFragment分析
这个Fragment实现了合约类中的View,所以通过覆写BaseView中的setPresenter(),将Presenter和View关联起来。
public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
其次覆写了合约类中的UI逻辑,在此我就不详说了,比较简单,大家可以自己看看。
AddEditTaskPresenter分析
在这个Presenter中,通过构造函数将Presenter和View关联在一起。构造函数的调用发生在上面的Activity中。
public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
@NonNull AddEditTaskContract.View addTaskView, boolean shouldLoadDataFromRepo) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository);
mAddTaskView = checkNotNull(addTaskView);
mIsDataMissing = shouldLoadDataFromRepo;
//将Presenter和View关联在一起
mAddTaskView.setPresenter(this);
}
这里将数据源tasksRepository传入到Presenter的构造函数中,可以使得Presenter在处理业务逻辑的时候,将数据教由给Model层处理。
这样的好处是显而易见的,彻底的将M-V-P分离开来,实现了解耦,也让整个程序的框架变得清楚明了起来。
TasksRepository分析
TasksRepository继承自TaskDataSource,TaskDataSource这个类中定义了两个回调接口,以及部分实现方法。
interface LoadTasksCallback {
void onTasksLoaded(List<Task> tasks);
void onDataNotAvailable();
}
interface GetTaskCallback {
void onTaskLoaded(Task task);
void onDataNotAvailable();
}
而TaskRepository中首先定义了两个数据源,一个是本地数据源mTasksLocalDataSource,另一个是远程数据源mTasksRemoteDataSoure。
下来我们重点看看获取任务的方法getTasks(LoadTasksCallbac callback)
public void getTasks(@NonNull final LoadTasksCallback callback) {
checkNotNull(callback);
// Respond immediately with cache if available and not dirty
if (mCachedTasks != null && !mCacheIsDirty) {
//TODO: What is Map's function values()
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
return;
}
if (mCacheIsDirty) {
// If the cache is dirty we need to fetch new data from the network.
getTasksFromRemoteDataSource(callback);
} else {
// Query the local storage if available. If not, query the network.
mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
refreshCache(tasks);
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
}
@Override
public void onDataNotAvailable() {
getTasksFromRemoteDataSource(callback);
}
});
}
}
我们来看看上面的代码:
- 从Cache里面读并且Cache中数据没有被污染,则直接返回
- 若Cache数据已经被污染,则获取远程的数据源。否则,直接从本地数据库查询,若查询不到,则获取远程数据源
以上,可以看出来,TasksRepository很好的将两个数据源的调度策略对外隐藏了起来,使得调用者不用关心如何选择数据源调度。
总结
再往下面就是一些业务实现逻辑上面的事情了,大家可以自己去看看。
总的来说,Google给了我们一个很好的范例,通过一个简单的APP,把MVP结构摆在了我们面前。当然你可以不认可它,而要将Activity去掉,那也是可以的。我到觉得现有的这种结构,更加方便的能看出来MVP各个角色之间的关系。
正如Google在项目介绍中说为何使用Fragment的那样:
Notice also in the following illustration that this version of the app uses fragments, and this is for two reasons:
• The use of both activities and fragments allows for a better separation of concerns which compliments this implementation of MVP. In this version of the app, the Activity is the overall controller which creates and connects views and presenters.
• The use of fragments supports tablet layouts or UI screens with multiple views.
使用activities和framgents使得MVP更好的分离开,Activity更多的是相当于一个全局的控制器,将Views和Presenters进行联系起来。
最后,让我们结合这张图片来回顾一下MVP在这个APP中的应用:
- Activity作为一个控制类,将View和Presenter连接在一起
- Presenter直接调用Model层的Repository获取数据
- Repository中则对外封装了数据源的获取逻辑,通过回调返回给上层
我的小站欢迎过来逛逛。