Android MVP 架构二 Presenter与Model

1 前言

前一篇我们主要介绍了MVP中View与Presenter的概念及交互,View层持有Presenter的实例,将一切逻辑操作都转发给Presenter层来操作,Presenter层持有View层的实例,并且将有关界面的部分的操作转发给View层实例操作,而设计到数据的部分则和Model层一起来操作。根据google官方的MVP项目例子todoapp,我们还分别介绍了Activity的作用,View层的具体角色,Presenter的角色以及Contract契约类的作用。这一篇我们来介绍有关Model层的设计。

2 MVP中Presenter与Model要解决的问题


MVP中Presenter与Model层要解决的主要问题有以下几点:
1 数据存储,获取,以及合法性校验等问题
2 数据的状态变化,需要通知上层等问题
3 数据的管理,如存储本地还是内存,获取数据来源,是从本地还是网络等
4 数据的加工,组装,比如将一个接口的response到的数据加工成另一个接口需要的request参数问题等

一般来说,Model与Presenter需要处理的问题就这些,一般Model层的设计都需要分层进行处理,每一层规划好自己的职责,对外提供统一的接口。关于Model层的设计与Presenter的职责划分是很有大的文章可做的。例如官方的MVP架构就有几种不同的实现,我们先来看一种最简单的实现。

3 todoapp中Model层的设计

我们先来看todoapp的架构,如下:
这里写图片描述

项目中,Model层的位置如下:
Android MVP 架构二 Presenter与Model_第1张图片

可以看到,Model层主要由两部分构成,第一层就是Respository,第二层主要是输出存储层,包括local与remote,这样数据的同一由Respository管理,对于Presenter来说,我只与Respository交互,至于数据怎么来,我不关心,实际中和代码也是如此:
我们还是以Tasks模块TasksPresenter为例,详细讲一下Presenter与Model层的交互。TasksPresenter源代码如下:

package com.example.android.architecture.blueprints.todoapp.tasks;

import android.app.Activity;
import android.support.annotation.NonNull;

import com.example.android.architecture.blueprints.todoapp.addedittask.AddEditTaskActivity;
import com.example.android.architecture.blueprints.todoapp.data.Task;
import com.example.android.architecture.blueprints.todoapp.data.source.TasksDataSource;
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository;
import com.example.android.architecture.blueprints.todoapp.util.EspressoIdlingResource;

import java.util.ArrayList;
import java.util.List;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Listens to user actions from the UI ({@link TasksFragment}), retrieves the data and updates the
 * UI as required.
 */
public class TasksPresenter implements TasksContract.Presenter {

    private final TasksRepository mTasksRepository;

    private final TasksContract.View mTasksView;

    private TasksFilterType mCurrentFiltering = TasksFilterType.ALL_TASKS;

    private boolean mFirstLoad = true;

    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);
    }

    @Override
    public void start() {
        loadTasks(false);
    }

    @Override
    public void result(int requestCode, int resultCode) {
        // If a task was successfully added, show snackbar
        if (AddEditTaskActivity.REQUEST_ADD_TASK == requestCode && Activity.RESULT_OK == resultCode) {
            mTasksView.showSuccessfullySavedMessage();
        }
    }

    @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 tasks) {
                List tasksToShow = new ArrayList();

                // 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);
            }

            @Override
            public void onDataNotAvailable() {
                // The view may not be able to handle UI updates anymore
                if (!mTasksView.isActive()) {
                    return;
                }
                mTasksView.showLoadingTasksError();
            }
        });
    }

    private void processTasks(List tasks) {
        if (tasks.isEmpty()) {
            // Show a message indicating there are no tasks for that filter type.
            processEmptyTasks();
        } else {
            // Show the list of tasks
            mTasksView.showTasks(tasks);
            // Set the filter label's text.
            showFilterLabel();
        }
    }

    private void showFilterLabel() {
        switch (mCurrentFiltering) {
            case ACTIVE_TASKS:
                mTasksView.showActiveFilterLabel();
                break;
            case COMPLETED_TASKS:
                mTasksView.showCompletedFilterLabel();
                break;
            default:
                mTasksView.showAllFilterLabel();
                break;
        }
    }

    private void processEmptyTasks() {
        switch (mCurrentFiltering) {
            case ACTIVE_TASKS:
                mTasksView.showNoActiveTasks();
                break;
            case COMPLETED_TASKS:
                mTasksView.showNoCompletedTasks();
                break;
            default:
                mTasksView.showNoTasks();
                break;
        }
    }

    @Override
    public void addNewTask() {
        mTasksView.showAddTask();
    }

    @Override
    public void openTaskDetails(@NonNull Task requestedTask) {
        checkNotNull(requestedTask, "requestedTask cannot be null!");
        mTasksView.showTaskDetailsUi(requestedTask.getId());
    }

    @Override
    public void completeTask(@NonNull Task completedTask) {
        checkNotNull(completedTask, "completedTask cannot be null!");
        mTasksRepository.completeTask(completedTask);
        mTasksView.showTaskMarkedComplete();
        loadTasks(false, false);
    }

    @Override
    public void activateTask(@NonNull Task activeTask) {
        checkNotNull(activeTask, "activeTask cannot be null!");
        mTasksRepository.activateTask(activeTask);
        mTasksView.showTaskMarkedActive();
        loadTasks(false, false);
    }

    @Override
    public void clearCompletedTasks() {
        mTasksRepository.clearCompletedTasks();
        mTasksView.showCompletedTasksCleared();
        loadTasks(false, false);
    }

    /**
     * Sets the current task filtering type.
     *
     * @param requestType Can be {@link TasksFilterType#ALL_TASKS},
     *                    {@link TasksFilterType#COMPLETED_TASKS}, or
     *                    {@link TasksFilterType#ACTIVE_TASKS}
     */
    @Override
    public void setFiltering(TasksFilterType requestType) {
        mCurrentFiltering = requestType;
    }

    @Override
    public TasksFilterType getFiltering() {
        return mCurrentFiltering;
    }

}

我们看到TasksPresenter引用了Respository实例对象mTasksRepository,并且在构造方法中初始化了该对象,我们以loadTasks()为例来分析一下task是如何加载的。首先loadTasks最终会调用到TasksPresenter的私有方法loadTasks(boolean forceUpdate, final boolean showLoadingUI),源码如下:

    /**
     * @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 tasks) {
                List tasksToShow = new ArrayList();

                // 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);
            }

            @Override
            public void onDataNotAvailable() {
                // The view may not be able to handle UI updates anymore
                if (!mTasksView.isActive()) {
                    return;
                }
                mTasksView.showLoadingTasksError();
            }
        });
    }

    private void processTasks(List tasks) {
        if (tasks.isEmpty()) {
            // Show a message indicating there are no tasks for that filter type.
            processEmptyTasks();
        } else {
            // Show the list of tasks
            mTasksView.showTasks(tasks);
            // Set the filter label's text.
            showFilterLabel();
        }
    }

loadTasks(boolean forceUpdate, final boolean showLoadingUI)会根据参数决定是否刷新,接着会调用mTasksRepository.getTasks()传入一个TasksDataSource.LoadTasksCallback回调来加载所有的task,这样Presenter层的TasksPresenter就拿到了mTasksRepository中所有的task数据。
可以看到TaskPresenter层中对Model层数据的操作是通过TasksRepository提供的接口getTasks()来实现的,不会直接去查询数据库或者发起网络请求等。

那么TasksRepository中getTasks又是如何实现的呢?由于TaskRespository实现了TasksDataSource接口,我们来看这两个类的实现

package com.example.android.architecture.blueprints.todoapp.data.source;

import android.support.annotation.NonNull;

import com.example.android.architecture.blueprints.todoapp.data.Task;

import java.util.List;

/**
 * Main entry point for accessing tasks data.
 * 

* 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 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); }

package com.example.android.architecture.blueprints.todoapp.data.source;

import static com.google.common.base.Preconditions.checkNotNull;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.example.android.architecture.blueprints.todoapp.data.Task;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Concrete implementation to load tasks from the data sources into a cache.
 * 

* For simplicity, this implements a dumb synchronisation between locally persisted data and data * obtained from the server, by using the remote data source only if the local database doesn't * exist or is empty. */ public class TasksRepository implements TasksDataSource { private static TasksRepository INSTANCE = null; private final TasksDataSource mTasksRemoteDataSource; private final TasksDataSource mTasksLocalDataSource; /** * This variable has package local visibility so it can be accessed from tests. */ Map mCachedTasks; /** * Marks the cache as invalid, to force an update the next time data is requested. This variable * has package local visibility so it can be accessed from tests. */ boolean mCacheIsDirty = false; // Prevent direct instantiation. private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource, @NonNull TasksDataSource tasksLocalDataSource) { mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource); mTasksLocalDataSource = checkNotNull(tasksLocalDataSource); } /** * Returns the single instance of this class, creating it if necessary. * * @param tasksRemoteDataSource the backend data source * @param tasksLocalDataSource the device storage data source * @return the {@link TasksRepository} instance */ public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource, TasksDataSource tasksLocalDataSource) { if (INSTANCE == null) { INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource); } return INSTANCE; } /** * Used to force {@link #getInstance(TasksDataSource, TasksDataSource)} to create a new instance * next time it's called. */ public static void destroyInstance() { INSTANCE = null; } /** * Gets tasks from cache, local data source (SQLite) or remote data source, whichever is * available first. *

* Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if all data sources fail to * get the data. */ @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 tasks) { refreshCache(tasks); callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values())); } @Override public void onDataNotAvailable() { getTasksFromRemoteDataSource(callback); } }); } } @Override public void saveTask(@NonNull Task task) { checkNotNull(task); mTasksRemoteDataSource.saveTask(task); mTasksLocalDataSource.saveTask(task); // Do in memory cache update to keep the app UI up to date if (mCachedTasks == null) { mCachedTasks = new LinkedHashMap<>(); } mCachedTasks.put(task.getId(), task); } @Override public void completeTask(@NonNull Task task) { checkNotNull(task); mTasksRemoteDataSource.completeTask(task); mTasksLocalDataSource.completeTask(task); Task completedTask = new Task(task.getTitle(), task.getDescription(), task.getId(), true); // Do in memory cache update to keep the app UI up to date if (mCachedTasks == null) { mCachedTasks = new LinkedHashMap<>(); } mCachedTasks.put(task.getId(), completedTask); } @Override public void completeTask(@NonNull String taskId) { checkNotNull(taskId); completeTask(getTaskWithId(taskId)); } @Override public void activateTask(@NonNull Task task) { checkNotNull(task); mTasksRemoteDataSource.activateTask(task); mTasksLocalDataSource.activateTask(task); Task activeTask = new Task(task.getTitle(), task.getDescription(), task.getId()); // Do in memory cache update to keep the app UI up to date if (mCachedTasks == null) { mCachedTasks = new LinkedHashMap<>(); } mCachedTasks.put(task.getId(), activeTask); } @Override public void activateTask(@NonNull String taskId) { checkNotNull(taskId); activateTask(getTaskWithId(taskId)); } @Override public void clearCompletedTasks() { mTasksRemoteDataSource.clearCompletedTasks(); mTasksLocalDataSource.clearCompletedTasks(); // Do in memory cache update to keep the app UI up to date if (mCachedTasks == null) { mCachedTasks = new LinkedHashMap<>(); } Iterator> it = mCachedTasks.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); if (entry.getValue().isCompleted()) { it.remove(); } } } /** * 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. *

* Note: {@link GetTaskCallback#onDataNotAvailable()} is fired if both data sources fail to * get the data. */ @Override public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) { checkNotNull(taskId); checkNotNull(callback); Task cachedTask = getTaskWithId(taskId); // Respond immediately with cache if available if (cachedTask != null) { callback.onTaskLoaded(cachedTask); return; } // Load from server/persisted if needed. // Is the task in the local data source? If not, query the network. mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() { @Override public void onTaskLoaded(Task task) { // Do in memory cache update to keep the app UI up to date if (mCachedTasks == null) { mCachedTasks = new LinkedHashMap<>(); } mCachedTasks.put(task.getId(), task); callback.onTaskLoaded(task); } @Override public void onDataNotAvailable() { mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() { @Override public void onTaskLoaded(Task task) { // Do in memory cache update to keep the app UI up to date if (mCachedTasks == null) { mCachedTasks = new LinkedHashMap<>(); } mCachedTasks.put(task.getId(), task); callback.onTaskLoaded(task); } @Override public void onDataNotAvailable() { callback.onDataNotAvailable(); } }); } }); } @Override public void refreshTasks() { mCacheIsDirty = true; } @Override public void deleteAllTasks() { mTasksRemoteDataSource.deleteAllTasks(); mTasksLocalDataSource.deleteAllTasks(); if (mCachedTasks == null) { mCachedTasks = new LinkedHashMap<>(); } mCachedTasks.clear(); } @Override public void deleteTask(@NonNull String taskId) { mTasksRemoteDataSource.deleteTask(checkNotNull(taskId)); mTasksLocalDataSource.deleteTask(checkNotNull(taskId)); mCachedTasks.remove(taskId); } private void getTasksFromRemoteDataSource(@NonNull final LoadTasksCallback callback) { mTasksRemoteDataSource.getTasks(new LoadTasksCallback() { @Override public void onTasksLoaded(List tasks) { refreshCache(tasks); refreshLocalDataSource(tasks); callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values())); } @Override public void onDataNotAvailable() { callback.onDataNotAvailable(); } }); } private void refreshCache(List tasks) { if (mCachedTasks == null) { mCachedTasks = new LinkedHashMap<>(); } mCachedTasks.clear(); for (Task task : tasks) { mCachedTasks.put(task.getId(), task); } mCacheIsDirty = false; } private void refreshLocalDataSource(List tasks) { mTasksLocalDataSource.deleteAllTasks(); for (Task task : tasks) { mTasksLocalDataSource.saveTask(task); } } @Nullable private Task getTaskWithId(@NonNull String id) { checkNotNull(id); if (mCachedTasks == null || mCachedTasks.isEmpty()) { return null; } else { return mCachedTasks.get(id); } } }

TasksDataSource定义了很多对task的操作,例如getTasks(),getTask(),saveTask()等,这些都是Model对数据的操作,而TaskRespository是对这些接口的实现,我们来看getTasks的实现。

    /**
     * This variable has package local visibility so it can be accessed from tests.
     */
    Map mCachedTasks;

    /**
     * Marks the cache as invalid, to force an update the next time data is requested. This variable
     * has package local visibility so it can be accessed from tests.
     */
    boolean mCacheIsDirty = false;

    /**
     * Gets tasks from cache, local data source (SQLite) or remote data source, whichever is
     * available first.
     * 

* Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if all data sources fail to * get the data. */ @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 tasks) { refreshCache(tasks); callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values())); } @Override public void onDataNotAvailable() { getTasksFromRemoteDataSource(callback); } }); } }

getTasks()的设计很简单,首先判断缓存mCachedTasks 是否有数据,并且数据是否有效,这里用一个变量mCacheIsDirty来表示,如果满足条件就直接将缓存的数据传入回调。如果没有缓存,则如果缓存的数据无效,则该通过remote来更新数据了,否则就交给本地区获取数据。

我们分别来看remote与local获取数据。
先来看remote的获取数据:

private void getTasksFromRemoteDataSource(@NonNull final LoadTasksCallback callback) {
        mTasksRemoteDataSource.getTasks(new LoadTasksCallback() {
            @Override
            public void onTasksLoaded(List tasks) {
                refreshCache(tasks);
                refreshLocalDataSource(tasks);
                callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
            }

            @Override
            public void onDataNotAvailable() {
                callback.onDataNotAvailable();
            }
        });
    }

可以看到,直接调用了mTasksRemoteDataSource的getTasks来获取数据,并传入回调。而mTasksRemoteDataSource的初始化最终赋值是FakeTasksRemoteDataSource对象,因此最终调用到了FakeTasksRemoteDataSource的getTasks()。代码如下:

/**
 * Implementation of a remote data source with static access to the data for easy testing.
 */
public class FakeTasksRemoteDataSource implements TasksDataSource {

    private static FakeTasksRemoteDataSource INSTANCE;

    private static final Map TASKS_SERVICE_DATA = new LinkedHashMap<>();

    // Prevent direct instantiation.
    private FakeTasksRemoteDataSource() {}

    public static FakeTasksRemoteDataSource getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new FakeTasksRemoteDataSource();
        }
        return INSTANCE;
    }

    @Override
    public void getTasks(@NonNull LoadTasksCallback callback) {
        callback.onTasksLoaded(Lists.newArrayList(TASKS_SERVICE_DATA.values()));
    }

    ......

    @Override
    public void saveTask(@NonNull Task task) {
        TASKS_SERVICE_DATA.put(task.getId(), task);
    }
    ......
}

可以看到,直接是FakeTasksRemoteDataSource 的TASKS_SERVICE_DATA 中的数据。而在saveTask中时,又将数据存储进去了。

接着我们分析local端数据的获取
mTasksLocalDataSource的getTask调用最终会调用到TasksLocalDataSource中的getTasks(),代码如下:

/**
 * Concrete implementation of a data source as a db.
 */
public class TasksLocalDataSource implements TasksDataSource {

    private static TasksLocalDataSource INSTANCE;

    private TasksDbHelper mDbHelper;

    // Prevent direct instantiation.
    private TasksLocalDataSource(@NonNull Context context) {
        checkNotNull(context);
        mDbHelper = new TasksDbHelper(context);
    }

    public static TasksLocalDataSource getInstance(@NonNull Context context) {
        if (INSTANCE == null) {
            INSTANCE = new TasksLocalDataSource(context);
        }
        return INSTANCE;
    }

    /**
     * Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if the database doesn't exist
     * or the table is empty.
     */
    @Override
    public void getTasks(@NonNull LoadTasksCallback callback) {
        List tasks = new ArrayList();
        SQLiteDatabase db = mDbHelper.getReadableDatabase();

        String[] projection = {
                TaskEntry.COLUMN_NAME_ENTRY_ID,
                TaskEntry.COLUMN_NAME_TITLE,
                TaskEntry.COLUMN_NAME_DESCRIPTION,
                TaskEntry.COLUMN_NAME_COMPLETED
        };

        Cursor c = db.query(
                TaskEntry.TABLE_NAME, projection, null, null, null, null, null);

        if (c != null && c.getCount() > 0) {
            while (c.moveToNext()) {
                String itemId = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_ENTRY_ID));
                String title = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_TITLE));
                String description =
                        c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_DESCRIPTION));
                boolean completed =
                        c.getInt(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_COMPLETED)) == 1;
                Task task = new Task(title, description, itemId, completed);
                tasks.add(task);
            }
        }
        if (c != null) {
            c.close();
        }

        db.close();

        if (tasks.isEmpty()) {
            // This will be called if the table is new or just empty.
            callback.onDataNotAvailable();
        } else {
            callback.onTasksLoaded(tasks);
        }

    }

    ......

    @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();
    }

   ......
}

可以看到,local端数据的获取就是直接查询的数据库,关于数据库的操作这里就不再分析了。

总结一下,todoapp的Presenter层的loadTasks()最终会将获取数据的操作转发给TaskRespository,而Taskrespository会根据实际情况去获取缓存,remote或者local 数据库中的数据,最终将获取的tasks传递给回调。从而将数据获取到。

以上就是关于从Presenter获取Model层的数据的操作,暂时还没发现Model层变化如下通知Presenter层的情况,后面再做分析

4 todo-mvp-loaders 层Presenter与Model设计

todo-mvp-loaders 用Loader取数据的MVP。基于上一个例子todo-mvp, 只不过这里改为用Loader来从Repository得到数据,结构如下:
Android MVP 架构二 Presenter与Model_第2张图片

使用Loader的优势:

  1. 去掉了回调, 自动实现数据的异步加载;
  2. 当内容改变时回调出新数据;
  3. 当应用因为configuration变化而重建loader时, 自动重连到上一个loader.

Diff with todo-mvp
既然是基于todo-mvp, 那么之前说过的那些就不再重复, 我们来看一下都有什么改动:

git difftool -d todo-mvp

添加了两个类:

TaskLoader和TasksLoader.

在Activity中new Loader类, 然后传入Presenter的构造方法.

Contract中View接口删掉了isActive()方法, Presenter删掉了populateTask()方法.

数据获取

添加的两个新类是TaskLoader和TasksLoader, 都继承于AsyncTaskLoader, 只不过数据的类型一个是单数, 一个是复数.

AsyncTaskLoader是基于ModernAsyncTask, 类似于AsyncTask,
把load数据的操作放在loadInBackground()里即可, deliverResult()方法会将结果返回到主线程, 我们在listener的onLoadFinished()里面就可以接到返回的数据了, (在这个例子中是几个Presenter实现了这个接口).

TasksDataSource接口的这两个方法:

List<Task> getTasks();
Task getTask(@NonNull String taskId);

都变成了同步方法, 因为它们是在loadInBackground()方法里被调用.

Presenter中保存了Loader和LoaderManager, 在start()方法里initLoader, 然后onCreateLoader返回构造传入的那个loader.
onLoadFinished()里面调用View的方法. 此时Presenter实现LoaderManager.LoaderCallbacks.

数据改变监听

TasksRepository类中定义了observer的接口, 保存了一个listener的list:

private List mObservers = new ArrayList();

public interface TasksRepositoryObserver {
    void onTasksChanged();
}

每次有数据改动需要刷新UI时就调用:

private void notifyContentObserver() {
    for (TasksRepositoryObserver observer : mObservers) {
        observer.onTasksChanged();
    }
}

在两个Loader里注册和注销自己为TasksRepository的listener: 在onStartLoading()里add, onReset()里面remove方法.
这样每次TasksRepository有数据变化, 作为listener的两个Loader都会收到通知, 然后force load:

@Override
public void onTasksChanged() {
    if (isStarted()) {
        forceLoad();
    }
}

这样onLoadFinished()方法就会被调用.

欢迎访问我的github

代码下载

你可能感兴趣的:(android,架构,android,应用开发)