Android MVP 架构二 Presenter与Model

来源:互联网 发布:linux mount rw 编辑:程序博客网 时间:2024/06/05 01:15

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层的位置如下:
这里写图片描述

可以看到,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<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);            }            @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<Task> 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<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);            }            @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<Task> 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. * <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);    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. * <p> * 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<String, Task> 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.     * <p>     * 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<Task> 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<Map.Entry<String, Task>> it = mCachedTasks.entrySet().iterator();        while (it.hasNext()) {            Map.Entry<String, Task> 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.     * <p>     * 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<Task> tasks) {                refreshCache(tasks);                refreshLocalDataSource(tasks);                callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));            }            @Override            public void onDataNotAvailable() {                callback.onDataNotAvailable();            }        });    }    private void refreshCache(List<Task> tasks) {        if (mCachedTasks == null) {            mCachedTasks = new LinkedHashMap<>();        }        mCachedTasks.clear();        for (Task task : tasks) {            mCachedTasks.put(task.getId(), task);        }        mCacheIsDirty = false;    }    private void refreshLocalDataSource(List<Task> 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<String, Task> 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.     * <p>     * 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<Task> 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<Task> 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<String, Task> 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<Task> tasks = new ArrayList<Task>();        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得到数据,结构如下:
强调内容

使用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<TasksRepositoryObserver> mObservers = new ArrayList<TasksRepositoryObserver>();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:

@Overridepublic void onTasksChanged() {    if (isStarted()) {        forceLoad();    }}

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

欢迎访问我的github

代码下载

0 1
原创粉丝点击