Android MVP模式介绍和讲解
作者:谭东
先给个MVP的出处链接:https://github.com/googlesamples/android-architecture/,出处就是google在github上的一个架构的开源例子,里面有个todo-mvp的sample项目,大概的展示了下它们的架构设计,也就是这个todo-mvp例子项目的架构:https://github.com/googlesamples/android-architecture/tree/todo-mvp/。
这只是他们认为比较好的架构方式,被传开了了,也就出现了MVP模式。但是并不一定这个模式就非常好,只不过有它的好处的初衷:想让结构更加的清晰,逻辑耦合度降低。但相对来说也使开发变得逻辑复杂了些,一般大型的项目推荐使用,看你的需求和权衡了。
之前也看了一些MVP的文章,没有太认真看,也是云里雾里。后来索性抽空一上午的时间,直接看官方的todo-mvp的例子源码,一点点分析整理google的mvp的设计的思路和流程。这个Demo演示了谷歌github例子的Model-View-Presenter(MVP)的架构设计,不过说实话,官方的demo写的有点乱,类命名、方法命名等很不清晰。总体来说还好,下午的时间就按照这个mvp的架构规范重新写了一个版本的todo-mvp。原版的todo-mvp用的是sqlite,涉及到了RoomDatabase。让注重mvp阅读人看起来很乱,容易分散重点。所以我这里使用sharedpreferences替换了sqlite的数据增删改查。 整体说来,官方的例子选择的还算容易理解,实现的功能就是列表展示本地存储的任务Task数据。有增加一条、删除一条、获取一条、获取全部任务的功能。
先看原版的实现设计图:
淡绿色部分就是View和Presenter部分的双向交互,左侧白底部分的就是Model部分的逻辑,可以看出整个MVP就是Model和Presenter的交互及View和Presenter的双向交互,而View和Model之间并没有直接的交互,这也就避免了耦合吧。
下面这个是我改过重写的demo的实现,把SQLite换成了SharedPreference,去除了本地和网络的任务的判断。
我这里以todo-mvp的我精心改版整理后的项目进行分析讲解,MVP的整体架构分析:
官方的demo基本上是按照功能分类的,把很多类都混在一起了,我这里的结构整理,相对清晰一些。
先说MVP的Model:
Model里先有实体,这里是Task:
/**
* Created by Tandong on 2018/4/2.
* MVP中某个M的基础实体类
*/
public class Task {
private String id;
private String date;
private String title;
private String content;
private boolean completed;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public boolean isCompleted() {
return completed;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Task task = (Task) o;
return id.equals(task.id);
}
}
以及操作Task实体的定义的方法,TaskDao,它的作用就是预先定义对外暴漏操作Task的接口方法:
/**
* Created by Tandong on 2018/4/2.
* 对应的Task的Model的主要方法接口定义类
*/
public interface TaskDao {
void getTasks(@NonNull LoadTasksCallback callback);
void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
void saveTask(@NonNull Task task);
void refreshTasks();
void deleteAllTasks();
void deleteTask(@NonNull Task task);
interface LoadTasksCallback {
void onTasksLoaded(List<Task> tasks);
void onDataNotAvailable();
}
interface GetTaskCallback {
void onTaskLoaded(Task task);
void onDataNotAvailable();
}
}
之后,再实现TaskDao里的定义的接口方法,也就是要有TaskImpl实现类:
/**
* Created by Tandong on 2018/4/2.
* Task的Model的接口方法的具体实现,采用单例模式
*/
public class TaskDaoImpl implements TaskDao {
private static TaskDaoImpl INSTANCE = null;
private static final int SERVICE_LATENCY_IN_MILLIS = 500;
private static List<Task> TASKS_SERVICE_DATA = null;
// 防止直接实例化
private TaskDaoImpl() {
}
/**
* 单例模式创建TaskDaoImpl
*
* @return
*/
public static TaskDaoImpl getInstance() {
if (INSTANCE == null) {
INSTANCE = new TaskDaoImpl();
}
return INSTANCE;
}
/**
* 销毁回收实例对象
*/
public static void destroyInstance() {
INSTANCE = null;
}
@Override
public void getTasks(@NonNull final LoadTasksCallback callback) {
//逻辑的具体实现,这里获取数据
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
TASKS_SERVICE_DATA = Utils.getTasksFromSp();
callback.onTasksLoaded(TASKS_SERVICE_DATA);
}
}, SERVICE_LATENCY_IN_MILLIS);
}
@Override
public void getTask(@NonNull String taskId, @NonNull final GetTaskCallback callback) {
int position = 0;
TASKS_SERVICE_DATA = Utils.getTasksFromSp();
for (int i = 0; i < TASKS_SERVICE_DATA.size(); i++) {
if (TASKS_SERVICE_DATA.get(i).getId().equals(taskId)) {
position = i;
}
}
//逻辑的具体实现
final Task task = TASKS_SERVICE_DATA.get(position);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
callback.onTaskLoaded(task);
}
}, SERVICE_LATENCY_IN_MILLIS);
}
@Override
public void saveTask(@NonNull Task task) {
//逻辑的具体实现
TASKS_SERVICE_DATA.add(task);
Utils.addTaskToSp(task);
}
@Override
public void refreshTasks() {
//逻辑的具体实现
TASKS_SERVICE_DATA = Utils.getTasksFromSp();
}
@Override
public void deleteAllTasks() {
//逻辑的具体实现
TASKS_SERVICE_DATA.clear();
Utils.deleteAllTaskToSp();
}
@Override
public void deleteTask(@NonNull Task task) {
//逻辑的具体实现
Utils.deleteTaskToSp(task);
}
}
那么可以看出,Model里一般有3个类,以这个todo-mvp例子来说,有实体类Task,接口方法类TaskDao,实现类TaskImpl。
接着定义我们的View:
根据情况,我们可以定义一个父类的View叫BaseView,当然直接写一个View也没问题。
这个View的类里我们定义好我们的Activity或者Fragment界面需要交互操作的一些方法。
interface View extends BaseView<Presenter> {
/**
* 这里定义操作View界面相关的回调方法
*/
void setLoadingIndicator(boolean show);
void showTasks(List<Task> tasks);
void showAddTaskOk(Task task);
void showTaskDetailsUi(String taskId);
void showLoadingTasksError();
void showNoTasks();
boolean isActive();
void showClearedAllTaskOk();
void showClearedTaskOk(Task task);
}
然后定义Presenter,Presenter的中文意思是主持人,顾名思义也就是整个Model和View的调度和指挥者。Presenter就是把Model的TaskImpl类和View的View类关联结合起来。里面定义的方法也是对应View和Model相连接调用的中间件接口方法,类似于一个中间转接接口。
interface Presenter extends BasePresenter {
/**
* 这里定义操作加载处理数据的相关回调方法
*/
void loadTasks(boolean showLoading);
void addNewTask(@NonNull Task task);
void openTaskDetails(@NonNull Task task);
void clearAllTasks();
void clearTask(@NonNull Task task);
}
google官方的todo-mvp的例子中间又加了一层,Contract类,就是一个连接关联类Contract,把View和Presenter关联起来。
/**
* Created by Tandong on 2018/4/2.
* 一个连接关联类Contract,把View和Presenter关联起来
*/
public interface TasksContract {
interface View extends BaseView<Presenter> {
/**
* 这里定义操作View界面相关的回调方法
*/
void setLoadingIndicator(boolean show);
void showTasks(List<Task> tasks);
void showAddTaskOk(Task task);
void showTaskDetailsUi(String taskId);
void showLoadingTasksError();
void showNoTasks();
boolean isActive();
void showClearedAllTaskOk();
void showClearedTaskOk(Task task);
}
interface Presenter extends BasePresenter {
/**
* 这里定义操作加载处理数据的相关回调方法
*/
void loadTasks(boolean showLoading);
void addNewTask(@NonNull Task task);
void openTaskDetails(@NonNull Task task);
void clearAllTasks();
void clearTask(@NonNull Task task);
}
}
这一层我觉的可以调整,可有可无。把这个写成一个View就可以。
然后定义编写我们的真正的Presenter实现类,TasksPresenter类。
/**
* Created by Tandong on 2018/4/2.
* Presenter的业务真正实现,监听用户UI操作,回调和更新数据给UI
*/
public class TasksPresenter implements TasksContract.Presenter {
private final TaskDaoImpl taskDaoImpl;
private final TasksContract.View tasksView;
public TasksPresenter(@NonNull TaskDaoImpl taskDaoImpl, @NonNull TasksContract.View tasksView) {
this.taskDaoImpl = Utils.checkNotNull(taskDaoImpl, "tasksRepository cannot be null");
this.tasksView = Utils.checkNotNull(tasksView, "tasksView cannot be null!");
this.tasksView.setPresenter(this);
}
@Override
public void start() {
loadTasks(true);
}
@Override
public void loadTasks(boolean showLoading) {
toLoadTasks(showLoading);
}
/**
* 添加数据操作
*/
@Override
public void addNewTask(@NonNull Task task) {
//操作Model
taskDaoImpl.saveTask(task);
//操作View
tasksView.showAddTaskOk(task);
}
@Override
public void openTaskDetails(@NonNull Task requestedTask) {
Utils.checkNotNull(requestedTask, "requestedTask cannot be null!");
tasksView.showTaskDetailsUi(requestedTask.getId());
}
/**
* 删除全部数据操作
*/
@Override
public void clearAllTasks() {
//操作Model
taskDaoImpl.deleteAllTasks();
//操作View
tasksView.showClearedAllTaskOk();
}
/**
* 删除单条数据操作
*
* @param task
*/
@Override
public void clearTask(@NonNull Task task) {
//操作Model
taskDaoImpl.deleteTask(task);
//操作View
tasksView.showClearedTaskOk(task);
}
private void toLoadTasks(final boolean showLoadingUI) {
if (showLoadingUI) {//显示加载中进度条布局
tasksView.setLoadingIndicator(true);
}
taskDaoImpl.getTasks(new TaskDao.LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
// The view may not be able to handle UI updates anymore
if (!tasksView.isActive()) {
return;
}
if (showLoadingUI) {//隐藏加载中进度条
tasksView.setLoadingIndicator(false);
}
processTasks(tasks);
}
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
if (!tasksView.isActive()) {
return;
}
tasksView.showLoadingTasksError();
}
});
}
private void processTasks(List<Task> tasks) {
if (tasks.isEmpty()) {
// Show a message indicating there are no tasks for that filter type.
processEmptyTasks();
} else {
//加载完毕后回调给View进行显示逻辑处理
tasksView.showTasks(tasks);
}
}
private void processEmptyTasks() {
tasksView.showNoTasks();
}
}
可以看出,这个Presenter实现类,实现了之前定义的Presenter接口里的方法,监听用户UI操作,回调和更新数据给UI。里面含有两个大类:TaskDaoImpl这个Model实现类和TasksContract.View这个View类,也就是把Model和View进行了关联和调度。从每个方法里也可以看出,每个方法基本上都有Model方法的调用和View的相应的操作调用。
好了,我们的Model、View、Presenter都写好了。接下来就是Activity或Fragment界面的调用了。
todo-mvp的demo里是Activity里调用了TaskFragment进行View的操作。那看下TaskFragment里的具体逻辑,TaskFragment先实现之前View里调用的接口TasksContract.View:
/**
* Created by Tandong on 2018/4/2.
* Fragment实现View的接口方法回调
*/
public class TaskFragment extends BaseFragment implements TasksContract.View, OnItemCallBack {
@Bind(R.id.rv)
RecyclerView recyclerView;
@Bind(R.id.tv_tips)
TextView tv_tips;
private TaskAdapter taskAdapter;
private LinearLayoutManager linearLayoutManager;
private TasksContract.Presenter presenter;
public TaskFragment() {
// Requires empty public constructor
}
public static TaskFragment newInstance() {
return new TaskFragment();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_tasks, container, false);
ButterKnife.bind(this, root);
return root;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
linearLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
taskAdapter = new TaskAdapter(getActivity(), Utils.getTasksFromSp());
taskAdapter.setOnItemCallBack(this);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(taskAdapter);
}
@Override
public void onResume() {
super.onResume();
presenter.start();
}
@Override
public void setPresenter(TasksContract.Presenter presenter) {
this.presenter = Utils.checkNotNull(presenter);
}
@Override
public void setLoadingIndicator(boolean show) {
if (show) {
tv_tips.setVisibility(View.VISIBLE);
tv_tips.setText("加载中...");
} else {
tv_tips.setVisibility(View.GONE);
}
}
@Override
public void showTasks(List<Task> tasks) {
//view的操作具体逻辑实现
taskAdapter.setDatas(tasks);
}
@Override
public void showAddTaskOk(Task task) {
//view的操作具体逻辑实现
showToast("添加任务成功~");
taskAdapter.addData(task);
}
@Override
public void showTaskDetailsUi(String taskId) {
//view的操作具体逻辑实现
}
@Override
public void showLoadingTasksError() {
//view的操作具体逻辑实现
tv_tips.setVisibility(View.VISIBLE);
tv_tips.setText("加载失败~");
}
@Override
public void showNoTasks() {
//view的操作具体逻辑实现
tv_tips.setVisibility(View.VISIBLE);
tv_tips.setText("没有数据~");
}
@Override
public boolean isActive() {
//view的操作具体逻辑实现
return isAdded();
}
@Override
public void showClearedAllTaskOk() {
//view的操作具体逻辑实现
tv_tips.setVisibility(View.VISIBLE);
tv_tips.setText("没有数据~");
}
@Override
public void showClearedTaskOk(Task task) {
taskAdapter.removeData(task);
}
@Override
public void onItemLongClick(View v, int posiotion) {
showToast("长按移除 " + posiotion);
presenter.clearTask(taskAdapter.getTasks().get(posiotion));
}
@Override
public void onItemClick(View v, int position) {
Task task = taskAdapter.getTasks().get(position);
showToast(position + " " + task.getTitle() + " " + task.getContent());
}
}
然后在实现的回调方法里,把返回的数据或者状态进行相应的操作和UI上的响应。也可以操作presenter里的一些方法。
那么整合MVP的todo-mvp的官方例子基本上这样,官方的todo-mvp里的功能多一点,有展示、添加、详情等分类。不过流程和操作都类似,MVP的官方例子就给大家讲解这么多。
整理后的Demo的github地址:https://github.com/jaychou2012/MVPDemo-todo-mvp
上一篇: css/html如何设置readonly
下一篇: js编译语言与解释型语言详解
推荐阅读
-
详解Android中的MVP架构分解和实现
-
详解Android MVP开发模式
-
Android编程开发之TextView文字显示和修改方法(附TextView属性介绍)
-
详解Android中的MVP架构分解和实现
-
Android UI设计与开发之ViewPager介绍和简单实现引导界面
-
Android软键盘显示模式及打开和关闭方式(推荐)
-
Android编程开发之TextView文字显示和修改方法(附TextView属性介绍)
-
android LinearLayout和RelativeLayout组合实现精确布局方法介绍
-
Android源码学习之单例模式应用及优点介绍
-
Android源码学习之工厂方法模式应用及优势介绍