从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模式。

SampleDescription
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

Github地址

先看看官方项目介绍中的结构图:

其中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

Github地址

同样先看看官方的配图:

我们发现就在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

Github地址

以上是关于从Google的todo-mvp源码中学习MVP模式的主要内容,如果未能解决你的问题,请参考以下文章

Google官方MVP模式示例项目解析 todo-mvp

Android官方TODO-MVP项目分析(上)---View 层 Presenter 层以及 Contract 分析

Android官方TODO-MVP项目分析(上)---View 层 Presenter 层以及 Contract 分析

MVP+Kotlin源码体验

Android 官方示例:android-architecture 学习笔记之todo-mvp

从Spring源码中学习如何查找自定义注解