从Google的todo-mvp源码中学习MVP模式
Posted zero9988
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从Google的todo-mvp源码中学习MVP模式相关的知识,希望对你有一定的参考价值。
从Google的todo-mvp源码中学习MVP模式
MVP : Model-View-Presenter
MVP模式可以说是现在开发模式中的mvp了。MVP模式也是mvvm模式的一个基础。
Google在Github上面公布了一组官方的mvp samples:Google todo-mvp
通过对以下几个例子的源码分析,来深入学习MVP模式。
Sample | Description |
---|---|
todo‑mvp | 最基础的MVP模式用例 |
todo‑mvp‑clean | 使用了 Clean Architecture 的概念 |
todo‑mvp‑dagger | 使用Dagger 2进行依赖注入 |
todo‑mvp‑rxjava | 使用Rxjava 2进行并发操作 |
todo‑mvvm‑databinding | 通过databinding来转变成mvvm框架 |
上述的几个例子都是基于第一个todo-mvp这个例子的基础改变而成的。
首先来对比一下MVP模式和MVC模式。
MVC模式:在安卓中,Activity即作为Controller又作为View的一部分,这就导致了代码耦合。
MVP模式:就是解决上述的问题,把Activity和XML页面都仅作为View,Controller的功能以及操作Model都交给了Presenter。通过Presenter把View和Model彻底分开。
todo‑mvp
先看看官方项目介绍中的结构图:
其中Fragment作为View的部分,Presenter是一个自定义的类
而左边的一块,Repository就是Model部分了。
我们的分析先从整体结构入手,然后局部源码分析:
首先看一下项目结构:
这里主要看main目录下的文件结构:
其中tasks,addedittask,statistics,taskdetail这4个目录都是独立且相似的。
因此我们就分析tasks目录下的4个主要文件(这4个文件组成了MVP模式中一个页面实现的标准结构):
TasksActivity: 初始化View(TasksFragment)和Presenter(TasksPresenter)
TasksContract: 接口,里面有两个内部接口View接口和Presenter接口。
TaskFragment: MVP模式,View的部分(implements TasksContract.View)
TaskPresenter: MVP模式,Presenter的部分(implements TasksContract.Presenter)
我们在看看data目录下的文件:
TasksDataSource:Model操作的接口,定义了Model的相关操作
TasksRepository:操作Model的一个实例,实现了TasksDataSource接口,里面还包含了两个成员:mTasksRemoteDataSource,mTasksLocalDataSource,分别代表本地Model操作和远程Model操作(从网络获取数据)
Task:一个具体的Model
我们再从一个简单的UML图中看看结构:
我们可以清晰的看到View和Presenter分别持有对方的接口对象,这样子就能互相调用。但是View对于Model层是完全被隔断的。
Presenter同时持有Model和View的对象。
而Model对Presenter和View都是没有感知的。
看完了宏观的结构,我们从代码层次微观的更细致的分析一下:
1,首先从TasksActivity的onCreate()方法开始:
//TasksActivity.java
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.tasks_act);
// Set up the toolbar.
……………………………………………………
// Set up the navigation drawer.
……………………………………………………
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null)
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
// Create the presenter
mTasksPresenter = new TasksPresenter(
Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
// Load previously saved state, if available.
if (savedInstanceState != null)
TasksFilterType currentFiltering =
(TasksFilterType) savedInstanceState.getSerializable(CURRENT_FILTERING_KEY);
mTasksPresenter.setFiltering(currentFiltering);
在onCreate()方法中,主要创建了TasksFragment和TasksPresenter,并对保存在savedInstanceState的数据进行处理。其中以下这行代码是最重要的:
mTasksPresenter = new TasksPresenter(
Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
通过TasksPresenter的构造方法,把Model View Presenter联系在了一起。
其中Injection是通过依赖注入的方式构造了一个TasksRepository对象,具体如何构造先不管他。只要知道TasksPresenter通过这个TasksRepository对象来操作Model的。
//TasksPresenter.java
public class TasksPresenter implements TasksContract.Presenter
private final TasksRepository mTasksRepository;
private final TasksContract.View mTasksView;
…………………………
public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView)
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");
mTasksView.setPresenter(this);
…………………………
在TasksPresenter的构造函数中,把TasksRepository和TasksFragment赋值给自己的成员变量。并调用TasksFragment的setPresenter方法,把自己的引用交给他。这样子,MVP三者就互相关联了。
然后就开始正式的运作了:
2,在刚才的onCreate方法中提交了Fragment自然会运行到TasksFragment的生命周期onCreateView方法:
//TasksFragment.java
public class TasksFragment extends Fragment implements TasksContract.View
private TasksContract.Presenter mPresenter;
private TasksAdapter mListAdapter;
private View mNoTasksView;
private ImageView mNoTaskIcon;
……………………
@Override
public void onResume()
super.onResume();
mPresenter.start();
…………………………
这里就捡去了一个关键代码onResume()方法,在该生命周期中,调用了mPresenter的start()方法,该方法时实现的BasePresenter接口的一个方法。
在TasksFragment.java的代码中,我们可以发现所有和数据相关的操作都是交给了mPresenter成员变量去实现。在TasksFragment.java中只剩下了获取view对象,view对象的一些基本设置。
3,我们知道这个mPresenter对象的实例是TasksPresenter对象,因此,我们查看TasksPresenter中的start()方法:
最终调用了loadTasks方法,
在loadTasks方法中,TasksPresenter 通过mTasksRepository成员变量,调用mTasksRepository.getTasks()方法去获取Model中的数据。其中的参数是一个callback,就是告诉Model仓库,获取到了Model以后,需要使用TasksPresenter的processTasks方法去处理获取到的数据。
ublic class TasksPresenter implements TasksContract.Presenter
private final TasksRepository mTasksRepository;
private final TasksContract.View mTasksView;
……………………
@Override
public void start()
loadTasks(false);
@Override
public void loadTasks(boolean forceUpdate)
// Simplification for sample: a network reload will be forced on first load.
loadTasks(forceUpdate || mFirstLoad, true);
mFirstLoad = false;
/**
* @param forceUpdate Pass in true to refresh the data in the @link TasksDataSource
* @param showLoadingUI Pass in true to display a loading icon in the UI
*/
private void loadTasks(boolean forceUpdate, final boolean showLoadingUI)
if (showLoadingUI)
mTasksView.setLoadingIndicator(true);
if (forceUpdate)
mTasksRepository.refreshTasks();
// The network request might be handled in a different thread so make sure Espresso knows
// that the app is busy until the response is handled.
EspressoIdlingResource.increment(); // App is busy until further notice
mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback()
@Override
public void onTasksLoaded(List<Task> tasks)
List<Task> tasksToShow = new ArrayList<Task>();
// This callback may be called twice, once for the cache and once for loading
// the data from the server API, so we check before decrementing, otherwise
// it throws "Counter has been corrupted!" exception.
if (!EspressoIdlingResource.getIdlingResource().isIdleNow())
EspressoIdlingResource.decrement(); // Set app as idle.
// We filter the tasks based on the requestType
for (Task task : tasks)
switch (mCurrentFiltering)
case ALL_TASKS:
tasksToShow.add(task);
break;
case ACTIVE_TASKS:
if (task.isActive())
tasksToShow.add(task);
break;
case COMPLETED_TASKS:
if (task.isCompleted())
tasksToShow.add(task);
break;
default:
tasksToShow.add(task);
break;
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive())
return;
if (showLoadingUI)
mTasksView.setLoadingIndicator(false);
processTasks(tasksToShow);
……………………
4,首先判断缓存中是否有数据且缓存数据不脏(没被修改过),如果有数据则直接通过callback提交回Presenter中,
如果数据已经不是最新数据了(被修改过了),通过从远程获取最新的数据,如果缓存中没数据,但数据是最新的,那么直接从本地读取数据
//TasksRepository.java
public class TasksRepository implements TasksDataSource
private static TasksRepository INSTANCE = null;
private final TasksDataSource mTasksRemoteDataSource;
private final TasksDataSource mTasksLocalDataSource;
…………………………………………………………
@Override
public void getTasks(@NonNull final LoadTasksCallback callback)
checkNotNull(callback);
// Respond immediately with cache if available and not dirty
if (mCachedTasks != null && !mCacheIsDirty)
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);
);
………………………………………………………………
总结:
1,创建presenter和view的接口
2,在activity中创建fragment作为view
3,在activity中创建presenter,并与之前的view互相绑定
4,在fragment的onresume生命周期中通过presenter初始化数据。即从model中获取数据。
todo‑mvp-clean
同样先看看官方的配图:
我们发现就在Presenter和Model的中间,多了一块Domain。
该项目是根据Clean Architecture的原则来实现的一个MVP模式的项目。
Clean Architecture简单的说就是一个分层模型,外层拥有内层对象的引用,因此外层可以通过内层暴露的方法进行需求的操作。而内层对外层是一无所知的。
这篇文章http://www.jianshu.com/p/cba6663435c7有个不错的解说。
我们主要来看看 这个多出来的Domain到底是什么。
Domian其实就是所有数据操作的集合。简单的说,就是增删改查这些操作。
每一个Use case就是一种操作。
来看一下项目结构目录:
对比例一的项目结构,我们可以发现 多出了一个domain文件目录,多出了UseCase抽象类,UseCaseHandler类,UseCaseScheduler接口和UseCaseThreadPoolScheduler类。
再来对比一下一个简单的UML图:
我们可以发现Domain部分,就是由UseCase(具体任务),UseCaseHandler(任务分发处理器),UseCaseThreadPoolScheduler(任务调度执行器)三个部分构成的。
UseCase抽象类:每一个具体任务(操作)都继承自这个抽象类,要实现executeUseCase抽象方法,通过泛型约定了这个具体任务的请求参数类型(request)和响应数据类型(response)。
UseCaseHandler类:Usecase任务的处理器,分发和处理每一个不同的UseCase,把UseCase交给调度执行器(UseCaseScheduler)去执行。
UseCaseThreadPoolScheduler类:调度执行器,实现了UseCaseScheduler接口,里面主要包含一个线程池,通过线程池去执行UseCase任务。
domain文件目录:里面主要有一个useCase文件夹。里面每一个XxxTask都是继承自UseCase抽象类。
接着看一下源代码是怎么执行的:
1,同样从TasksActivity的onCreate方法开始,其中不同的就是创建TasksPresenter的时候,参数变多了,把所有UseCase和UseCaseHandler都通过参数的方式,传递给TaskPresenter,这样TaskPresenter就可以用UseCaseHandler来处理UseCase任务。
//TaskActivity.java
// Create the presenter
mTasksPresenter = new TasksPresenter(
Injection.provideUseCaseHandler(),
tasksFragment,
Injection.provideGetTasks(getApplicationContext()),
Injection.provideCompleteTasks(getApplicationContext()),
Injection.provideActivateTask(getApplicationContext()),
Injection.provideClearCompleteTasks(getApplicationContext())
);
2,同样在TasksFragment的onResume生命周期中调用刚才的TasksPresenter的start()接口方法,最终将调用TasksFragment的loadTasks方法。
//TasksPresenter.java
/**
* @param forceUpdate Pass in true to refresh the data in the @link TasksDataSource
* @param showLoadingUI Pass in true to display a loading icon in the UI
*/
private void loadTasks(boolean forceUpdate, final boolean showLoadingUI)
if (showLoadingUI)
mTasksView.setLoadingIndicator(true);
GetTasks.RequestValues requestValue
= new GetTasks.RequestValues(forceUpdate,
mCurrentFiltering);
mUseCaseHandler.execute(mGetTasks, requestValue,
new UseCase.UseCaseCallback<GetTasks.ResponseValue>()
@Override
public void onSuccess(GetTasks.ResponseValue response)
List<Task> tasks = response.getTasks();
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive())
return;
if (showLoadingUI)
mTasksView.setLoadingIndicator(false);
processTasks(tasks);
@Override
public void onError()
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive())
return;
mTasksView.showLoadingTasksError();
);
该方法中主要是通过UseCaseHandler对象来处理UseCase任务。mUseCaseHandler的execute方法的参数分别是指定的任务GetTasks(UseCase),该任务涉及的请求参数,及其执行完任务以后的回调函数。
3,mUseCaseHandler的execute方法会调用mUseCaseScheduler(UseCaseScheduler)的execute接口方法。execute方法的参数是一个Runable对象,是为了能在接下来的线程中进行调用。这个Runable里面主要就是调用了UseCase的run方法。然后,mUseCaseScheduler的具体实例是UseCaseThreadPoolScheduler对象。在UseCaseThreadPoolScheduler中会创建一个ThreadPoolExecutor,最后的执行就是交给这个ThreadPoolExecutor线程池对象来执行的。
//UseCaseHandler.java
public class UseCaseHandler
private static UseCaseHandler INSTANCE;
private final UseCaseScheduler mUseCaseScheduler;
public UseCaseHandler(UseCaseScheduler useCaseScheduler)
mUseCaseScheduler = useCaseScheduler;
public <T extends UseCase.RequestValues, R extends UseCase.ResponseValue> void execute(
final UseCase<T, R> useCase, T values, UseCase.UseCaseCallback<R> callback)
useCase.setRequestValues(values);
useCase.setUseCaseCallback(new UiCallbackWrapper(callback, this));
// The network request might be handled in a different thread so make sure
// Espresso knows
// that the app is busy until the response is handled.
EspressoIdlingResource.increment(); // App is busy until further notice
mUseCaseScheduler.execute(new Runnable()
@Override
public void run()
useCase.run();
// This callback may be called twice, once for the cache and once for loading
// the data from the server API, so we check before decrementing, otherwise
// it throws "Counter has been corrupted!" exception.
if (!EspressoIdlingResource.getIdlingResource().isIdleNow())
EspressoIdlingResource.decrement(); // Set app as idle.
);
………………
//UseCaseThreadPoolScheduler.java
public class UseCaseThreadPoolScheduler implements UseCaseScheduler
ThreadPoolExecutor mThreadPoolExecutor;
@Override
public void execute(Runnable runnable)
mThreadPoolExecutor.execute(runnable);
4,因此,最终会在一个新线程中调用UseCase的run方法。run方法中会执行真正实现类的executeUseCase方法,在executeUseCase方法中我们可以看到与例一中相似的代码,去请求Model中的数据。
//UseCase.java
public abstract class UseCase<………………>
void run()
executeUseCase(mRequestValues);
protected abstract void executeUseCase(Q requestValues);
//GetTasks.java
public class GetTasks extends UseCase<GetTasks.RequestValues, GetTasks.ResponseValue>
private final TasksRepository mTasksRepository;
……………………
@Override
protected void executeUseCase(final RequestValues values)
if (values.isForceUpdate())
mTasksRepository.refreshTasks();
mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback()
@Override
public void onTasksLoaded(List<Task> tasks)
TasksFilterType currentFiltering = values.getCurrentFiltering();
TaskFilter taskFilter = mFilterFactory.create(currentFiltering);
List<Task> tasksFiltered = taskFilter.filter(tasks);
ResponseValue responseValue = new ResponseValue(tasksFiltered);
getUseCaseCallback().onSuccess(responseValue);
@Override
public void onDataNotAvailable()
getUseCaseCallback().onError();
);
因此,实际上,就多封装了一层Domain层,Domain层进行多种任务的调度和管理。
todo‑mvp‑dagger
以上是关于从Google的todo-mvp源码中学习MVP模式的主要内容,如果未能解决你的问题,请参考以下文章
Android官方TODO-MVP项目分析(上)---View 层 Presenter 层以及 Contract 分析
Android官方TODO-MVP项目分析(上)---View 层 Presenter 层以及 Contract 分析