Google Sample MVP
Android架构蓝图
目前Android主流的开发架构:原生开发(MVC),MVP,MVVM等
今天简单的说一下我对于Android架构的了解和对Google MVP的认识分析。
说Android就不得不提到Java,Android的应用层和Java有着不解之缘,Android应用层参考Java的实现并且进行了很多的优化,比如大家都熟悉的JVM与Android虚拟机。其实Android开发也是对于Java GUI图形界面开发的优化,完成了MVC的分层,用布局文件(xml文件)完成了对于视图布局的抽象和优化。
以下是自己结合自己实际开发中的经验对MVP的一些感悟。
先看一下对于MVP架构的目录结构图
Note: in a MVP context, the term "view" is overloaded:
· The class android.view.View will be referred to as "Android View"
· The view that receives commands from a presenter in MVP, will be simply called "view".
注意:在MVP的上下文里,“view”一词有多重含义:
· android.view.View被称为“Android View”
· 在MVP中,从presenter接收命令的view将被简单地称为“view”。
It uses fragments for two reasons:
· The separation between Activity and Fragment fits nicely with this implementation of MVP: the Activity is the overall controller that creates and connects views and presenters.
· Tablet layout or screens with multiple views take advantage of the Fragments framework.
(MVP中的View实现)使用Fragment有两个原因:
· Activity与Fragment之间的分离很好的符合了MVP的实现:Activity作为整体控制器来创建和连接views与presenters。
· 平板布局或者屏幕上有多个views的布局可以很好的利用Fragments框架。
/**
* This specifies the contract between the view and the presenter.
*/
public interface AddEditTaskContract {
interface View extends BaseView {
void showEmptyTaskError();
void showTasksList();
void setTitle(String title);
void setDescription(String description);
boolean isActive();
}
interface Presenter extends BasePresenter {
void saveTask(String title, String description);
void populateTask();
}
}
**
* Listens to user actions from the UI ({@link AddEditTaskFragment}), retrieves the data and updates
* the UI as required.
*/
public class AddEditTaskPresenter implements AddEditTaskContract.Presenter,
TasksDataSource.GetTaskCallback {
……
/**
* Creates a presenter for the add/edit view.
*
* @param taskId ID of the task to edit or null for a new task
* @param tasksRepository a repository of data for tasks
* @param addTaskView the add/edit view
*/
public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
@NonNull AddEditTaskContract.View addTaskView) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository);
mAddTaskView = checkNotNull(addTaskView);
mAddTaskView.setPresenter(this);
}
@Override
public void start() {
if (!isNewTask()) {
populateTask();
}
}
@Override
public void saveTask(String title, String description) {
if (isNewTask()) {
createTask(title, description);
} else {
updateTask(title, description);
}
}
……
}
public class AddEditTaskFragment extends Fragment implements AddEditTaskContract.View {
public static final String ARGUMENT_EDIT_TASK_ID = "EDIT_TASK_ID";
private AddEditTaskContract.Presenter mPresenter;
private TextView mTitle;
private TextView mDescription;
public static AddEditTaskFragment newInstance() {
return new AddEditTaskFragment();
}
public AddEditTaskFragment() {
// Required empty public constructor
}
@Override
public void onResume() {
super.onResume();
//BasePresenter接口的方法,主要完成数据的初始化
mPresenter.start();
}
//在构造函数中调用完成presenter和View的关联
@Override
public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FloatingActionButton fab =
(FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task_done);
fab.setImageResource(R.drawable.ic_done);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.saveTask(mTitle.getText().toString(), mDescription.getText().toString());
}
});
}
……
}
public class AddEditTaskActivity extends AppCompatActivity {
……
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addtask_act);
……
// Create the presenter
new AddEditTaskPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
addEditTaskFragment);
}
……
}
在构造方法中调用View的setPresenter方法与View建立联系
Activity 在项目中是一个全局控制着,负责创建View以及Presenter,并将两者联系起来
Contract这个类是首次出现于google的mvp示例中,以前的MVP模式并未见到,这个类定义了View接口和Presenter接口为对方的实例提供的方法。容易的可以看出View和Presenter之间的操作。
这个契约类的好处是方便接口统一管理、修改,同时,内容清晰,一目了然,维护起来也方便。
先来看看官方的代码目录(这里只是功能模块的目录,不包括测试模块,毕竟这里分析的是官方的实现代码)
tasks 包可以显示任务列表
taskdetail包显示任务详情
addedittask包添加和编辑任务
statistics包用来显示任务的完成情况
data包数据模块对应mvp的M
Util包就是通用的方法。
我们的项目中也运用到了mvp的思想,其实mvp并没有固定的写法,正确的去理解架构的思想,都可以有自己独特的mvp写法。
简单说一下我们项目中MVP架构的实现
我们项目中的实现与Google实现不同只有Presenter接口和接口的实现类,Presenter与View直接的调用关联关系同过泛型参数实现,这使得我们的具有类文件更少的优势,同时不可避免带来了相互调用比较模糊;(接口相互之间持有引用)
在我们的实现之中,Fragment或者Activity相当于作为了默认的View层(接口以及实现类)
我们的项目实现里面没有明显的Repository层,上层(activity/fragment/presenter)不需要知道数据的细节(或者说 - 数据源),来自于网络、数据库,亦或是内存等等。如此,一来上层可以不用关心细节,二来底层可以根据需求修改,不会影响上层,两者的分离用可以帮助协同开发
还有一个不同的是我们的Presenter加入了生命周期,我认为这一定程度上增加了Presenter层的复杂程度(以及Onclick事件的处理AZ)
有时候我们还面临一个问题,接口数据模型不一致,View不能方便的复用
在MVVM的模式中ViewModel的存在很好的解决了这个问题;
MVVM的实现类似于观察者模式,采用了数据模型与xml布局文件绑定的形式;
很多MVVM的框架都是实现了布局文件与代码的双向绑定,xml既可以调用java的代码,改变数据模型的同时UI也能自己改变。
Google 也给出了MVP与DataBinding
t is based on the todo-mvp sample and uses the Data Binding library to display data and bind UI elements to actions.
It doesn't follow a strict Model-View-ViewModel or a Model-View-Presenter pattern, as it uses both View Models and Presenters.
The Data Binding Library saves on boilerplate code allowing UI elements to be bound to a property in a data model.
Layout files are used to bind data to UI elements
Events are also bound with an action handler
Data can be observed and set up to be updated automatically when needed
Diagram
MVVM对比与MVP的最大优势就是更加解耦UI逻辑与业务逻辑. View与ViewModel的耦合, 要弱于View与Presenter的耦合. View是ViewModel的消费者, 当修改UI时, 导致较少地修改ViewModel. 根据业务关注点(Concern), 设置更加多的高内聚View与ViewModel, 在多个页面中共享与替换.
MVVM使业务逻辑更加彻底地分离, 使用DataBinding分离UI显示与UI逻辑, 实现View与ViewModel的一对多, ViewModel与Model的多对多, 模块复用更加完备, 进一步提高可测试性.
MVP 的优缺点
任何事务都存在两面性,MVP当然也不列外,我们来看看MVP的优缺点。
优点:
模块职责划分明显,层次清晰
Presenter可以复用,一个Presenter可以用于多个View,而不需要更改Presenter的逻辑(当然是在View的改动不影响业务逻辑的前提下)
利于测试驱动开发。以前的Android开发是难以进行单元测试的(虽然很多Android开发者都没有写过测试用例,但是随着项目变得越来越复杂,没有测试是很难保证软件质量的;而且近几年来Android上的测试框架已经有了长足的发展——开始写测试用例吧),在使用MVP的项目中Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。
View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。
增加了Contract接口,便于模块功能的管理和扩展。
缺点:
Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。
由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。 UI复杂的界面Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了。
额外的代码复杂度及使用、学习成本。
MVP架构更好地分离项目职责, 解除业务逻辑与UI逻辑之间的耦合. 对于小型项目而言, 与设计模式类似, 使用接口会导致过度设计, 增加代码量. 当处理复杂页面时, Presenter层会包含大量UI逻辑与业务逻辑, 显得非常冗余, 违反单一职责原理.
关于presenter一直持有Activity对象导致的内存泄漏问题
只要用过mvp这个问题可能很多人都知道。写mvp的时候,presenter会持有view,如果presenter有后台异步的长时间的动作,比如网络请求,这时如果返回退出了Activity,后台异步的动作不会立即停止,这里就会有内存泄漏的隐患,所以会在presenter中加入一个销毁view的方法。