前言:
前几天去面试,有一家公司问到会不会mvp,说说对他的理解,你的项目中是是如何实现的,然而我只是看了很多讲mvp的文章,并没有真正的在项目中用过,毕竟也都是小项目......然后的然后就让我现场写mvp.....
推荐一篇通俗易懂的文章
https://www.jianshu.com/p/389c9ae1a82c
之前也看了很多的文章,但是看得越多就越乱,看到最后也没有真正弄懂mvp到底是怎么个回事,结合google的todomvp和这篇文章,大致理清了mvp是咋回事了。
什么是mvp?
- mvp就是model/view/presenter三者
- 关系:presenter相当于是model和view之前的桥梁,他俩是不能直接交互的,得通过presenter来连接。
- 那这样的话,view只能和presenter交互,那怎么才能在view中操作presenter呢?只需要把presenter作为参数传给view就行了;那要在presenter中操作view怎么办呢,也是同样的方法,只需要把view传给view就行了。因为要在presenter中获取数据,所以还需要在Presenter中传入model。
既然说presenter是中间层,那在哪实现的presenter呢?
这个是当时问到我的一个题目。准确的说应该是在Activity中创建的presenter实例,将view和model作为参数传过去了。
什么是view?
根据google官方的例子,view其实是Fragment,Activity只是一个全局的控制者,负责创建view以及presenter实例。
view和presenter是怎么关联的?
在presenter的构造函数中调用view的setPresenter(this)就可以将二者绑定了。
前面做了这么多铺垫,接下来分析一下google官方demo(参考上面的链接分析的):
-
功能页面:
主页.jpg 详情页.jpg
-
项目结构
项目结构.png -
代码分析
- 在最底下有两个基类:
public interface BasePresenter {
//presenter开始获取数据并调用view中方法改变界面显示,其调用时机是在Fragment类的onResume方法中。
void start();
}
public interface BaseView<T> {
//view要持有presenter的引用,在将presenter实例传入view中,其调用时机是presenter实现类的构造函数中。
void setPresenter(T presenter);
}
- 定义了契约类(接口)
/**
* 契约类来统一管理view与presenter的所有的接口
*/
public interface TaskDetailContract {
interface View extends BaseView<Presenter> {
//定义了该界面(功能)中所有的UI状态情况
// 设置数据加载状态
void setLoadingIndicator(boolean active);
// 处理task加载失败的情况
void showMissingTask();
// 隐藏待办事项title
void hideTitle();
// 显示待办事项title
void showTitle(String title);
// 隐藏待办事项的描述
void hideDescription();
// 显示待办事项的描述
void showDescription(String description);
//显示待办事项的状态,是否已完成
void showCompletionStatus(boolean complete);
//显示要编辑的待办事项
void showEditTask(String taskId);
//显示已删除的待办事项
void showTaskDeleted();
//显示已完成的待办事项
void showTaskMarkedComplete();
//显示未完成的点半事项
void showTaskMarkedActive();
//是否未完成
boolean isActive();
}
interface Presenter extends BasePresenter {
//定义了该界面(功能)中所有的用户操作事件
// 修改待办事项
void editTask();
// 删除待办事项
void deleteTask();
// 标记完成
void completeTask();
// 标记未完成
void activateTask();
}
}
这样写了之后,这个功能有哪些功能操作,以及ui上会有哪些变化都很清楚
- View
public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {
private TaskDetailContract.Presenter mPresenter;
.....
@Override
public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {
//判空,在Presenter中会将view和presenter进行绑定,这里是检查传入的presenter是否为空
mPresenter = checkNotNull(presenter);
}
......
@Override
public void onResume() {
super.onResume();
//开始获取model中的数据
mPresenter.start();
}
......
@Override
public void hideDescription() {
//隐藏待办事件的描述
mDetailDescription.setVisibility(View.GONE);
}
......
}
TaskDetailFragment 作为view层,主要负责ui相关的状态更新,实现了契约类中的View接口后,在相关的代码中实现具体的内容。
- Presenter
Activity在mvp中的作用:
/**
* Activity在项目中是一个全局的控制者,负责创建view以及presenter实例,并将二者联系起来。
*/
public class TaskDetailActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState){
//创建view
TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()
.findFragmentById(R.id.contentFrame);
//创建presenter实例
new TaskDetailPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
taskDetailFragment);
}
}
创建Presenter
/**
* 作为Presenter层,实现了该接口,如此 TaskDetailPresenter 则只关注业务层的逻辑相关,UI的更新只需调用View的状态方法。
*/
public class TaskDetailPresenter implements TaskDetailContract.Presenter {
/**
* 构造函数,presenter作为中间层链接model和view,为了相互交互,则要持有他们的引用
* @param taskId : 待办事项id
* @param tasksRepository : model
* @param taskDetailView : view
*/
public TaskDetailPresenter(@Nullable String taskId,
@NonNull TasksRepository tasksRepository,
@NonNull TaskDetailContract.View taskDetailView) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
// 保持对View(TaskDetailFragment)的引用
mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");
// 使View(TaskDetailFragment)也保持对自身(TaskDetailPresenter)的引用
mTaskDetailView.setPresenter(this);
}
@Override
public void start() {
//开始获取数据
openTask();
}
}
- Model层:
model层中主要是数据的一些操作,数据的获取,数据的添加,数据的删除等
在Presenter的start()中获取数据:
/**
* 获取数据
*/
private void openTask() {
// 判空处理
if (Strings.isNullOrEmpty(mTaskId)) {
//加载数据的时候,如果taskId为空,则调用view的加载失败的情况
mTaskDetailView.showMissingTask();
return;
}
// 更新状态,加载成功的时候,设置加载的状态,
mTaskDetailView.setLoadingIndicator(true);
// modele,获取该条Task数据
mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
//数据加载成功
// View已经被用户回退(已销毁)
if (!mTaskDetailView.isActive()) {
return;
}
// 获取到task数据,并更新UI
mTaskDetailView.setLoadingIndicator(false);
if (null == task) {
//如果没有加载到task,则显示加载失败的ui
mTaskDetailView.showMissingTask();
} else {
//否则,显示这个待办事项
showTask(task);
}
}
@Override
public void onDataNotAvailable() {
// 显示数据获取失败时的状态
if (!mTaskDetailView.isActive()) {
return;
}
mTaskDetailView.showMissingTask();
}
});
}
再看一下TasksRepository的getTask():
@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.
// 从本地数据源(SQLite数据库)中获取,没有获取到,再从网络数据源获取
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();
}
});
}
});
}
- 分析Model(TasksRepositor)代码实现:
- TasksDataSource 接口分析:
里面包含了所有对数据的操作
- TasksDataSource 接口分析:
public interface TasksDataSource {
//加载数据的回调
interface LoadTasksCallback {
void onTasksLoaded(List<Task> tasks);
void onDataNotAvailable();
}
//获取数据的回调
interface GetTaskCallback {
void onTaskLoaded(Task task);
void onDataNotAvailable();
}
/**
* 获取所有task
* @param callback
*/
void getTasks(@NonNull LoadTasksCallback callback);
/**
* 根据id获取单个task
* @param taskId
* @param callback
*/
void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
/**
* 保存task
* @param task
*/
void saveTask(@NonNull Task task);
/**
* 标记为完成
* @param task
*/
void completeTask(@NonNull Task task);
/**
* 标记为完成
* @param taskId
*/
void completeTask(@NonNull String taskId);
/**
* 标记为未完成
* @param task
*/
void activateTask(@NonNull Task task);
/**
* 标记为未完成
* @param taskId
*/
void activateTask(@NonNull String taskId);
/**
* 删除所有已完成task
*/
void clearCompletedTasks();
/**
* 刷新task
*/
void refreshTasks();
/**
* 删除所有task
*/
void deleteAllTasks();
/**
* 删除单个task
* @param taskId
*/
void deleteTask(@NonNull String taskId);
}
- TasksRepository实现了TasksDataSource接口,维护了两个数据源。一个是本地(SQLite数据库),一个是远程(网络服务器),他们都是TasksDataSource类型。
可以把TasksRepository理解为一个大粮仓,里面又有2个小粮仓,有我的粮食库,也有你的粮食库,我们需要粮食的时候都要到对应的粮食库中取。所有的粮食库都实现TasksDataSource接口
//远程数据源
private final TasksDataSource mTasksRemoteDataSource;
//本地数据源
private final TasksDataSource mTasksLocalDataSource;
- TasksRepository是一个单例模式,在构造函数中要有两个数据源:
private static TasksRepository INSTANCE = null;
// Prevent direct instantiation.
private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
@NonNull TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
}
public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,
TasksDataSource tasksLocalDataSource) {
if (INSTANCE == null) {
INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);
}
return INSTANCE;
}
- 实现接口中的方法:
......
@Override
public void deleteTask(@NonNull String taskId) {
//根据id删除Task
① mTasksRemoteDataSource.deleteTask(checkNotNull(taskId));
② mTasksLocalDataSource.deleteTask(checkNotNull(taskId));
mCachedTasks.remove(taskId);
}
......
- ①和②中又执行了各自的操作
- 这时候会想到一个问题,这些数据到底是在哪里进行操作的呢?
继续看TasksLocalDataSource这个类,这是本地数据源
public class TasksLocalDataSource implements TasksDataSource {
private static volatile TasksLocalDataSource INSTANCE;
// Prevent direct instantiation.
private TasksLocalDataSource(@NonNull AppExecutors appExecutors,
@NonNull TasksDao tasksDao) {
mAppExecutors = appExecutors;
mTasksDao = tasksDao;
}
public static TasksLocalDataSource getInstance(@NonNull AppExecutors appExecutors,
@NonNull TasksDao tasksDao) {
if (INSTANCE == null) {
synchronized (TasksLocalDataSource.class) {
if (INSTANCE == null) {
INSTANCE = new TasksLocalDataSource(appExecutors, tasksDao);
}
}
}
return INSTANCE;
}
}
这个类是一个单例,实现了TasksDataSource ,刚刚在上面说过"本地数据源"就相当于一个小粮仓,要从里面取粮食就要实现TasksDataSource 这个接口才行。
然后实现接口后做具体的实现:
......
@Override
public void saveTask(@NonNull final Task task) {
checkNotNull(task);
Runnable saveRunnable = new Runnable() {
@Override
public void run() {
//在数据库中插入数据
mTasksDao.insertTask(task);
}
};
mAppExecutors.diskIO().execute(saveRunnable);
}
......
@Override
public void completeTask(@NonNull final Task task) {
Runnable completeRunnable = new Runnable() {
@Override
public void run() {
//在数据库中更新数据
mTasksDao.updateCompleted(task.getId(), true);
}
};
mAppExecutors.diskIO().execute(completeRunnable);
}
......
- 知道了这个是对本地数据库的具体实现后,那在哪里调用的它呢?
按住TasksLocalDataSource ,找到它在Injection这个类中用到了,接下来看一下Injection:
public class Injection {
/**
* 返回的是model
* @param context
* @return
*/
public static TasksRepository provideTasksRepository(@NonNull Context context) {
checkNotNull(context);
//创建数据库
ToDoDatabase database = ToDoDatabase.getInstance(context);
//创建TasksRepository :远程数据源、本地数据源
return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
TasksLocalDataSource.getInstance(new AppExecutors(), database.taskDao()));
}
}
它的代码不多,提供了一个静态方法供外部调用,而这个方法居然返回的是 TasksRepository ,根据上面的分析,知道他相当于是model,为什么要这么写呢?
- 接下来看 Injection 的这个方法在哪里使用了?
//创建presenter实例
new TaskDetailPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
taskDetailFragment);
原来在一开始创建Presenter的时候就使用了,Presenter要连接view和model,所有就要传入他们的引用。
现在整个逻辑就很清楚了。
- 本地数据源分析完了,还有一个远程数据源,接下来看一下 TasksRemoteDataSource这个类。和上面一样,他也要实现 TasksDataSource接口:
public class TasksRemoteDataSource implements TasksDataSource {
private static TasksRemoteDataSource INSTANCE;
// Prevent direct instantiation.
private TasksRemoteDataSource() {}
public static TasksRemoteDataSource getInstance() {
if (INSTANCE == null) {
INSTANCE = new TasksRemoteDataSource();
}
return INSTANCE;
}
}
其余的和本地数据源一样,只是它是从服务端读取数据
- 总结:
1.写一个契约类管理所有的view和presenter接口,这样所有的方法和ui状态都会很清楚了
2.Acitivity或Fragment相当于是View层,要实现契约类中的view接口,并在对应方法中写具体的实现,因为view层可以和Presenter层交互,所以得在View层拿到Presenter,在setPresenter()方法中可以将绑定的Presenter传进来,比如点击了删除按钮,就要调用Presnter中的删除事件。
3.Model层主要是对数据的一些操作,可以写一个接口,里面写上对数据的一些操作,model实现这个接口后做具体的实现(进行本地数据源和远程数据源的增删改查)
4.Presenter是中间层,在创建的时候要传入View和model的引用,在Presenter的构造方法中通过 view.setPresenter(this)传入当前Presenter进行绑定
over.....
网友评论