MVP与MVC之间的区别
MVC
- View:对应于布局文件
- Model:业务逻辑和实体模型
- Controllor:对应于Activity
MVP
- View 对应于Activity,负责View的绘制以及与用户交互
- Model 依然是业务逻辑和实体模型
- Presenter 负责完成View于Model间的交互
MVP模式的作用
MVP的好处
-
分离了视图逻辑和业务逻辑,降低了耦合
-
Activity只处理生命周期的任务,代码变得更加简洁
-
视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性
-
Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
-
把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM
其中最重要的有三点:
Activity 代码变得更加简洁
相信很多人阅读代码的时候,都是从Activity开始的,对着一个1000+行代码的Activity,看了都觉得难受。
使用MVP之后,Activity就能瘦身许多了,基本上只有FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。这种情形下阅读代码就容易多了,而且你只要看Presenter的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码。Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。
方便进行单元测试
一般单元测试都是用来测试某些新加的业务逻辑有没有问题,如果采用传统的代码风格(习惯性上叫做MV模式,少了P),我们可能要先在Activity里写一段测试代码,测试完了再把测试代码删掉换成正式代码,这时如果发现业务有问题又得换回测试代码,测试代码已经删掉了!双需要重新再写了……
MVP中,由于业务逻辑都在Presenter里,我们完全可以写一个PresenterTest的实现类继承Presenter的接口,现在只要在Activity里把Presenter的创建换成PresenterTest,就能进行单元测试了,测试完再换回来即可。万一发现还得进行测试,那就再换成PresenterTest。
避免 Activity 的内存泄露
Android APP 发生OOM的最大原因就是出现内存泄露造成APP的内存不够用,而造成内存泄露的两大原因之一就是Activity泄露(Activity Leak)(另一个原因是Bitmap泄露(Bitmap Leak))。
Java一个强大的功能就是其虚拟机的内存回收机制,这个功能使得Java用户在设计代码的时候,不用像C++用户那样考虑对象的回收问题。然而,Java用户总是喜欢随便写一大堆对象,然后幻想着虚拟机能帮他们处理好内存的回收工作。可是虚拟机在回收内存的时候,只会回收那些没有被引用的对象,被引用着的对象因为还可能会被调用,所以不能回收。
Activity是有生命周期的,用户随时可能切换Activity,当APP的内存不够用的时候,系统会回收处于后台的Activity的资源以避免OOM。
采用传统的MV模式,一大堆异步任务和对UI的操作都放在Activity里面,比如你可能从网络下载一张图片,在下载成功的回调里把图片加载到 Activity 的 ImageView 里面,所以异步任务保留着对Activity的引用。这样一来,即使Activity已经被切换到后台(onDestroy已经执行),这些异步任务仍然保留着对Activity实例的引用,所以系统就无法回收这个Activity实例了,结果就是Activity Leak。Android的组件中,Activity对象往往是在堆(Java Heap)里占最多内存的,所以系统会优先回收Activity对象,如果有Activity Leak,APP很容易因为内存不够而OOM。
采用MVP模式,只要在当前的Activity的onDestroy里,分离异步任务对Activity的引用,就能避免 Activity Leak。
MVC与MVP最明显的区别就是,MVC中是允许Model和View进行交互的,而MVP中很明显,Model与View之间的交互由Presenter完成。
还有一点就是Presenter与View之间的交互是通过接口的
实例代码说明
- HomeActivity.class
public class HomeActivity extends AppCompatActivity {
private HomePresenter mAddEditTaskPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addtask_act);
// Set up the toolbar.
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
HomeFragment addEditTaskFragment = (HomeFragment) getSupportFragmentManager()
.findFragmentById(R.id.contentFrame);
if (addEditTaskFragment == null) {
addEditTaskFragment = HomeFragment.newInstance();
ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
addEditTaskFragment, R.id.contentFrame);
}
// // Create the presenter
mAddEditTaskPresenter = new HomePresenter(addEditTaskFragment);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab_edit_task_done);
fab.setImageResource(R.drawable.ic_logs);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v, "跳转到LOG页面", Snackbar.LENGTH_LONG).show();
startActivity(new Intent(HomeActivity.this, LogActivity.class));
}
});
}
}
- HomeContract.class
public interface HomeContract {
interface View {
void showToast(String msg);
}
interface Presenter{
void showToast(String msg);
}
}
- HomePresenter.class
public class HomePresenter implements HomeContract.Presenter {
@NonNull
private final HomeContract.View mAddTaskView;
/**
* Creates a presenter for the add/edit view.
*
* @param addTaskView the add/edit view
*/
public HomePresenter(@NonNull HomeContract.View addTaskView) {
mAddTaskView = checkNotNull(addTaskView);
mAddTaskView.setPresenter(this);
}
@Override
public void share(String msg) {
mAddTaskView.showToast(msg);
}
}
- HomeFragment.class
public class HomeFragment extends Fragment implements HomeContract.View {
private HomeContract.Presenter mPresenter;
@BindView(R.id.button4)
public Button button4;
public static HomeFragment newInstance() {
return new HomeFragment();
}
public HomeFragment() {
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.addtask_frag, container, false);
unbinder = ButterKnife.bind(this, root);
return root;
}
@Override
public void setPresenter(@NonNull HomeContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@Override
public void showToast(@NonNull String msg) {
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
}
@OnClick({R.id.button4})
public void onClick(View view) {
switch (view.getId()) {
case R.id.button4:
mPresenter.showTost("MVP模式显示Tost");
break;
}
}
}
- addtask_act.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/Toolbar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/contentFrame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_edit_task_done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_dialog_email"
app:fabSize="normal"
app:layout_anchor="@id/contentFrame"
app:layout_anchorGravity="bottom|right|end" />
</android.support.design.widget.CoordinatorLayout>
</LinearLayout>
</android.support.v4.widget.DrawerLayout>
- addtask_frag.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<Button
android:id="@+id/button4"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1"
android:text="屏幕" />
</LinearLayout>
网友评论