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的优势:
- 去掉了回调, 自动实现数据的异步加载;
- 当内容改变时回调出新数据;
- 当应用因为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
代码下载
- Android MVP 架构二 Presenter与Model
- Android MVP 架构一 View与Presenter
- Android MVP(Model View Presenter)
- 浅析MVP(Model-View-Presenter)架构及开发模式
- 介绍MVP Model-View-Presenter在Android中的应用
- Model-View-Presenter(MVP)概述
- MVP (Model View Presenter ) 简介
- MVP——Model-Viewer-Presenter
- MVP(model,view Presenter) Pattern(模式) 学习
- MVP之Android官方MVP架构学习—View层和Presenter层
- 在ASP.NET中实现Model-View-Presenter(MVP)
- NBearV3教程——MVP(Model/View/Presenter)
- NBearV3教程——MVP(Model/View/Presenter)篇
- 在ASP.NET中实现Model-View-Presenter(MVP)
- Model View Presenter (MVP) design pattern and data binding
- ASP.NET MVP( Model View Presenter )微软官方介绍
- .NET平台上的Model-View-Presenter(MVP)模式实践
- Android:聊聊 MVP 中 Presenter 的生命周期
- 洛谷日记2
- 指定控件的边框线
- MYSQL问题解决方案:Access denied for user 'root'@'localhost' (using password:YES)
- 修改vim配色
- 2017.4.15南海中学创新班
- Android MVP 架构二 Presenter与Model
- 人民币汇率市场化历程
- 2017第八届蓝桥杯B组省赛第六题:最大公共子串
- 自增两种方式区别
- 二叉树的最大节点
- HDFS学习总结
- 去除UITableView中空白Cell之间的横线
- 蓝桥杯 贪吃的大嘴
- python中所有可调用对象