对于MVP架构,最近一段时间谷歌推出了官方的示例,包含多种不同的方式,先调了一个最基础的用来学习一下。Google官方示例的Git-Hub地址:Google官方示例地址,大家可以去参观学习。
基础的MVP框架:
Google提供的最基础的MVP架构只是单纯的通过MVP的思想来组织代码,所有的注入都是通过手动注入的。
首先来看一下Google对于这个项目的简介:
可以在图中很直观的看到,MVP的分层还是很明确的,通过Presenter来完成View和Model的交互。
总览:
现在来看一下项目结构:
项目主要是根据功能模块来分包的,从上到下依次是:
- 添加待完成任务模块
- model模块:封装了一些数据的增删改查操作
- 任务完成数据统计模块
- 任务详情模块
- 任务列表模块
- 工具类模块
- 两个最基础的Presenter和View接口
最基础的接口:
首先是最基础的两个接口,BasePresenter和BaseView:
BasePresenter:
定义了Presenter最基础的方法,调用该方法可以通知Presenter开始工作
,具体的方法会在具体的Presenter中定义
BaseView:
定义了View的最基础的方法,提供了设置Presenter的方法,具体的方法会在具体的View中定义
Model层实现:
因为Model层是和View层和Presenter层是分离开的,而且是最底层的一层,所以先看这层怎么实现:
这是Model层的结构,依次是:
- 本地数据存取
- 远程数据存取
- 定义的数据源接口,主要定义了的数据操作的方法
- 数据仓库,用于测试
- 待做任务的实体类Task
首先是TaskDataSource这个接口:
在TaskDataSource中定义了数据存取的一些操作方法,对于数据存取的类都要实现这个接口中的方法。
/**
* Main entry point for accessing tasks data.
* <p>
* For simplicity, only getTasks() and getTask() have callbacks. Consider adding callbacks to other
* methods to inform the user of network/database errors or successful operations.
* For example, when a new task is created, it's synchronously stored in cache but usually every
* operation on database or network should be executed in a different thread.
*
* 封装了一些对于任务的数据操作方法,包括数据保存,查询,删除等方法
*/
public interface TasksDataSource {
/**
* 获取任务列表的回调接口,对应于获取多个任务的回调
*/
interface LoadTasksCallback {
void onTasksLoaded(List<Task> tasks);
void onDataNotAvailable();
}
/**
* 获取指定任务的回调接口,对应于获取单个任务的回调
*/
interface GetTaskCallback {
void onTaskLoaded(Task task);
void onDataNotAvailable();
}
//获取任务列表
void getTasks(@NonNull LoadTasksCallback callback);
//根据任务的Id获取指定任务
void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
//保存任务
void saveTask(@NonNull Task task);
//已完成的任务,根据实体来查询
void completeTask(@NonNull Task task);
//已完成的任务,根据任务Id来查询
void completeTask(@NonNull String taskId);
//正在活动的任务
void activateTask(@NonNull Task task);
//正在活动的任务,根据id来查询
void activateTask(@NonNull String taskId);
//清除所有的已完成的任务
void clearCompletedTasks();
//刷新任务列表
void refreshTasks();
//删除所有的任务
void deleteAllTasks();
//根据任务id删除指定任务
void deleteTask(@NonNull String taskId);
}
上边代码中添加的注释已经很好的解释了该接口中定义的方法,基本已经满足了这个项目的需求。
具体的实现:
然后对于本地数据的存取和远程数据的存取,分别封装了以下的类:
local代表本地数据存取:
TaskDbHelper继承自SQLiteOpenHelper,用于封装了一些数据库的操作
TaskLocalDataSource是具体实现了TasksDataSource接口的实现类,里边通过Sqlite数据库完成了数据的存取
TasksPersistenceContract是数据库的契约类,里边主要是包含一些数据库中用到的常量信息
remote代表远程数据存取:
- TaskRemoteDataSource也是实现了TaskDataSource接口的实现类,主要是进行远程数据的加载。
以TaskLocalDataSource为例来看一下里边的实现:
//这是TaskLocalDataSource中保存Task的方法,其实就是通过DBHelper将数据持久化,其他的方法也类似
@Override
public void saveTask(@NonNull Task task) {
checkNotNull(task);
SQLiteDatabase db = mDbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(TaskEntry.COLUMN_NAME_ENTRY_ID, task.getId());
values.put(TaskEntry.COLUMN_NAME_TITLE, task.getTitle());
values.put(TaskEntry.COLUMN_NAME_DESCRIPTION, task.getDescription());
values.put(TaskEntry.COLUMN_NAME_COMPLETED, task.isCompleted());
db.insert(TaskEntry.TABLE_NAME, null, values);
db.close();
}
以上这些就是Model层的主要知识点,梳理一遍可以很清楚的明白流程:
首先定义关于数据操作的接口(TaskDataSource)
然后根据需要实现的数据存储方式来分别实现对应的数据存取实现类(TaskLocalDataSource和TaskRemoteDataSource),均继承自之前定义好的数据操作接口。
以上这些就是关于Model层的实现步骤和原理,更多细节的实现的可以阅读源码了解。
View层和Presenter层的实现:
以添加新的待做任务这个模块为例来看一下Google在推荐的MVP实现实例中是怎么处理View层和Presenter层的:
MVP中的对应关系:
Model:TaskLocalDataSource
View:AddEditTaskFragment
Presenter:AddEditTaskPresenter
其中两个之间都是通过契约类来进行连接的,也就是图中的AddEditTaskContract。在这里Google引入了一个新的叫做契约类的中间部分,包括对于某一具体模块中对于View和Presenter的方法的定义。我们可以以AddEditTaskContract为例来看一下这个契约类具体是用来干什么的。
package com.example.android.architecture.blueprints.todoapp.addedittask;
import com.example.android.architecture.blueprints.todoapp.BasePresenter;
import com.example.android.architecture.blueprints.todoapp.BaseView;
/**
* This specifies the contract between the view and the presenter.
* 添加任务的契约类
*
*/
public interface AddEditTaskContract {
interface View extends BaseView<Presenter> {
//显示没有任务的错误信息提示
void showEmptyTaskError();
//显示任务列表
void showTasksList();
//设置标题
void setTitle(String title);
//设置描述信息
void setDescription(String description);
//是否处于活动
boolean isActive();
}
interface Presenter extends BasePresenter {
//保存任务
void saveTask(String title, String description);
//填充任务
void populateTask();
//是否数据缺失
boolean isDataMissing();
}
}
这里边主要内容是根据当前的模块来派生一个适合于当前模块的View和Presenter,在这里集中的显示了View和Presenter的功能,也是当前模块中具体View和具体Presenter定义的。
具体的View和Presenter的实现:
AddEditTaskFragment.java:
package com.example.android.architecture.blueprints.todoapp.addedittask;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.example.android.architecture.blueprints.todoapp.R;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Main UI for the add task screen. Users can enter a task title and description.
* 主要添加任务的界面,用户可以输入任务的标题和描述
*/
public class AddEditTaskFragment extends Fragment implements AddEditTaskContract.View {
public static final String ARGUMENT_EDIT_TASK_ID = "EDIT_TASK_ID";
//在这个View中含有一个Presenter的引用
private AddEditTaskContract.Presenter mPresenter;
private TextView mTitle;
private TextView mDescription;
public static AddEditTaskFragment newInstance() {
return new AddEditTaskFragment();
}
public AddEditTaskFragment() {
// Required empty public constructor
}
@Override
public void onResume() {
super.onResume();
//开启Presenter
mPresenter.start();
}
@Override
public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FloatingActionButton fab =
(FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task_done);
fab.setImageResource(R.drawable.ic_done);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//在View中通过Presenter间接的和Model进行交互,
// 在这里就体现出所有的业务逻辑都通过Presenter来完成
mPresenter.saveTask(mTitle.getText().toString(), mDescription.getText().toString());
}
});
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.addtask_frag, container, false);
mTitle = (TextView) root.findViewById(R.id.add_task_title);
mDescription = (TextView) root.findViewById(R.id.add_task_description);
setHasOptionsMenu(true);
return root;
}
@Override
public void showEmptyTaskError() {
Snackbar.make(mTitle, getString(R.string.empty_task_message), Snackbar.LENGTH_LONG).show();
}
@Override
public void showTasksList() {
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish();
}
@Override
public void setTitle(String title) {
mTitle.setText(title);
}
@Override
public void setDescription(String description) {
mDescription.setText(description);
}
@Override
public boolean isActive() {
return isAdded();
}
}
上边就是示例项目中给出的View层的实现,可以体会一下其“只是进行数据的显示,并不进行业务逻辑的处理”这种想法。
接下来看一下Presenter层的实现示例:
AddEditTaskPresenter.java:
public class AddEditTaskPresenter implements AddEditTaskContract.Presenter,
TasksDataSource.GetTaskCallback {
//有TasksDataSource的引用,连接 Model层
@NonNull
private final TasksDataSource mTasksRepository;
//有AddEditTaskContract.View的应用,连接View层
@NonNull
private final AddEditTaskContract.View mAddTaskView;
@Nullable
private String mTaskId;
private boolean mIsDataMissing;
/**
* Creates a presenter for the add/edit view.
*
* @param taskId ID of the task to edit or null for a new task
* @param tasksRepository a repository of data for tasks
* @param addTaskView the add/edit view
* @param shouldLoadDataFromRepo whether data needs to be loaded or not (for config changes)
*/
public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
@NonNull AddEditTaskContract.View addTaskView, boolean shouldLoadDataFromRepo) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository);
mAddTaskView = checkNotNull(addTaskView);
mIsDataMissing = shouldLoadDataFromRepo;
}
@Override
public void start() {
if (!isNewTask() && mIsDataMissing) {
populateTask();
}
}
@Override
public void saveTask(String title, String description) {
if (isNewTask()) {
createTask(title, description);
} else {
updateTask(title, description);
}
}
@Override
public void populateTask() {
if (isNewTask()) {
throw new RuntimeException("populateTask() was called but task is new.");
}
mTasksRepository.getTask(mTaskId, this);
}
/**
* TasksDataSource.GetTaskCallback接口的实现方法,用于加载完数据之后的回调
*/
@Override
public void onTaskLoaded(Task task) {
// The view may not be able to handle UI updates anymore
if (mAddTaskView.isActive()) {
mAddTaskView.setTitle(task.getTitle());
mAddTaskView.setDescription(task.getDescription());
}
mIsDataMissing = false;
}
/**
* TasksDataSource.GetTaskCallback接口的实现方法,用于数据无法获取到时候回调
*/
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
if (mAddTaskView.isActive()) {
mAddTaskView.showEmptyTaskError();
}
}
@Override
public boolean isDataMissing() {
return mIsDataMissing;
}
private boolean isNewTask() {
return mTaskId == null;
}
/**
* 创建新的Task任务
* @param title
* @param description
*/
private void createTask(String title, String description) {
Task newTask = new Task(title, description);
if (newTask.isEmpty()) {
mAddTaskView.showEmptyTaskError();
} else {
mTasksRepository.saveTask(newTask);
mAddTaskView.showTasksList();
}
}
/**
* 更新Task任务
* @param title
* @param description
*/
private void updateTask(String title, String description) {
if (isNewTask()) {
throw new RuntimeException("updateTask() was called but task is new.");
}
//在Presenter中与
mTasksRepository.saveTask(new Task(title, description, mTaskId));
// After an edit, go back to the list.
mAddTaskView.showTasksList();
}
}
Presenter中主要是实现View和Model的交互,并且将两者隔离开来,实现了接口隔离。
最后就剩下AddEditTaskActivity这个类,其实其主要是做一个承载的作用,类似于一个容器:
AddEditTaskActivity.java:
/**
* Displays an add or edit task screen.
* 展示添加任务或者是修改任务的界面
*/
public class AddEditTaskActivity extends AppCompatActivity {
public static final int REQUEST_ADD_TASK = 1;
public static final String SHOULD_LOAD_DATA_FROM_REPO_KEY = "SHOULD_LOAD_DATA_FROM_REPO_KEY";
private AddEditTaskPresenter mAddEditTaskPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addtask_act);
// Set up the toolbar.
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
//初始化View
AddEditTaskFragment addEditTaskFragment =
(AddEditTaskFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
String taskId = getIntent().getStringExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID);
if (addEditTaskFragment == null) {
addEditTaskFragment = AddEditTaskFragment.newInstance();
if (getIntent().hasExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID)) {
actionBar.setTitle(R.string.edit_task);
Bundle bundle = new Bundle();
bundle.putString(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId);
addEditTaskFragment.setArguments(bundle);
} else {
actionBar.setTitle(R.string.add_task);
}
//将Fragmment添加到Activity中
ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
addEditTaskFragment, R.id.contentFrame);
}
boolean shouldLoadDataFromRepo = true;
// Prevent the presenter from loading data from the repository if this is a config change.
if (savedInstanceState != null) {
// Data might not have loaded when the config change happen, so we saved the state.
shouldLoadDataFromRepo = savedInstanceState.getBoolean(SHOULD_LOAD_DATA_FROM_REPO_KEY);
}
// Create the presenter
//实例化一个Presenter
mAddEditTaskPresenter = new AddEditTaskPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
addEditTaskFragment,
shouldLoadDataFromRepo);
//为View设置Presenter
addEditTaskFragment.setPresenter(mAddEditTaskPresenter);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// Save the state so that next time we know if we need to refresh data.
outState.putBoolean(SHOULD_LOAD_DATA_FROM_REPO_KEY, mAddEditTaskPresenter.isDataMissing());
super.onSaveInstanceState(outState);
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
@VisibleForTesting
public IdlingResource getCountingIdlingResource() {
return EspressoIdlingResource.getIdlingResource();
}
}
总结下来,感觉就是Activity像是一个舞台,承载着上边所有的元素进行表演,而Presenter这是提线木偶中提线的那个人,操控着任务的交互,View则是木偶的动作表演,而Model这是推动故事发展的剧情。
因为剧情的发展(Model的改变)导致提线人知道剧情发生变化(Presenter得知Model发生了变化)从而操作木偶(View发生变化)产生动作。
同样的,当木偶产生动作时(View发生变化),Presenter知道木偶发生了这个动作(Presenter知道View发生了变化)之后下一步剧情该怎么发展(Model怎么改变)。
链接
有兴趣的可以Git-Hub上看看Google官方给出的MVP实例的源码。
Git-Hub地址:https://github.com/googlesamples/android-architecture