引言
在Android的架構(gòu)中Activity,fragment,布局的xml相當(dāng)于View。然而在實(shí)際的開發(fā)過程中,Android的View層任務(wù)繁重,將V和C都糅雜在Activity、Fragment中,這就導(dǎo)致了在實(shí)際開發(fā)中View層太過累贅。MVP把Activity中的UI邏輯抽象成View接口,把業(yè)務(wù)邏輯抽象成Presenter接口,Model類還是原來的Model
mvc1.png
mvp1.png
MPV的介紹
- 優(yōu)點(diǎn)
- 分離了視圖邏輯和業(yè)務(wù)邏輯,降低了耦合
- Activity只處理生命周期的任務(wù),代碼變得更加簡潔
使用MVP之后,Activity就能瘦身許多了,基本上只有FindView、SetListener以及Init的代碼。其他的就是對Presenter的調(diào)用,還有對View接口的實(shí)現(xiàn)。這種情形下閱讀代碼就容易多了,而且你只要看Presenter的接口,就能明白這個(gè)模塊都有哪些業(yè)務(wù),很快就能定位到具體代碼。Activity變得容易看懂,容易維護(hù),以后要調(diào)整業(yè)務(wù)、刪減功能也就變得簡單許多
- 視圖邏輯和業(yè)務(wù)邏輯分別抽象到了View和Presenter的接口中去,提高代碼的可閱讀性
- Presenter被抽象成接口,可以有多種具體的實(shí)現(xiàn),所以方便進(jìn)行單元測試
MVP中,由于業(yè)務(wù)邏輯都在Presenter里,我們完全可以寫一個(gè)PresenterTest的實(shí)現(xiàn)類繼承Presenter的接口,現(xiàn)在只要在Activity里把Presenter的創(chuàng)建換成PresenterTest,就能進(jìn)行單元測試了,測試完再換回來即可
- 把業(yè)務(wù)邏輯抽到Presenter中去,避免后臺線程引用著Activity導(dǎo)致Activity的資源無法被系統(tǒng)回收從而引起內(nèi)存泄露和OOM
Android APP 發(fā)生OOM的最大原因就是出現(xiàn)內(nèi)存泄露造成APP的內(nèi)存不夠用,而造成內(nèi)存泄露的兩大原因之一就是Activity泄露(Activity Leak)(另一個(gè)原因是Bitmap泄露(Bitmap Leak))Activity是有生命周期的,用戶隨時(shí)可能切換Activity,當(dāng)APP的內(nèi)存不夠用的時(shí)候,系統(tǒng)會回收處于后臺的Activity的資源以避免OOM。采用傳統(tǒng)的MV模式,一大堆異步任務(wù)和對UI的操作都放在Activity里面,比如你可能從網(wǎng)絡(luò)下載一張圖片,在下載成功的回調(diào)里把圖片加載到 Activity 的 ImageView 里面,所以異步任務(wù)保留著對Activity的引用。這樣一來,即使Activity已經(jīng)被切換到后臺(onDestroy已經(jīng)執(zhí)行),這些異步任務(wù)仍然保留著對Activity實(shí)例的引用,所以系統(tǒng)就無法回收這個(gè)Activity實(shí)例了,結(jié)果就是Activity Leak。Android的組件中,Activity對象往往是在堆(Java Heap)里占最多內(nèi)存的,所以系統(tǒng)會優(yōu)先回收Activity對象,如果有Activity Leak,APP很容易因?yàn)閮?nèi)存不夠而OOM。采用MVP模式,只要在當(dāng)前的Activity的onDestroy里,分離異步任務(wù)對Activity的引用,就能避免 Activity Leak
- 缺點(diǎn)
相對而言,MVP模式的代碼量就多了,類文件也多了,簡單的一個(gè)業(yè)務(wù)邏輯操作就要各方來配合協(xié)作(即是需要presenter 和 view的接口)。但是這個(gè)問題完全在可以接收的范圍。完全符合Java的抽象封裝設(shè)計(jì)原則(接口隔離,開閉原則)
通過實(shí)例介紹MPV的使用

-
一個(gè)簡單的訂單查詢 點(diǎn)擊查詢顯示 點(diǎn)擊清除取消顯示
項(xiàng)目結(jié)構(gòu).png - 首先來看看MainActivity
package com.rrcc.mvp.mvpdemo.view;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import com.rrcc.mvp.mvpdemo.R;
import com.rrcc.mvp.mvpdemo.adapter.DailyPagerAdapter;
import com.rrcc.mvp.mvpdemo.model.OrderListBean;
import com.rrcc.mvp.mvpdemo.model.OrdersModelImpl;
import com.rrcc.mvp.mvpdemo.presenter.IPresenterImpl;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity implements ISearchView, View.OnClickListener{
private RecyclerView mRecyclerView;
private DailyPagerAdapter mDailyPagerAdapter = null;
private List<OrderListBean.DataBean> mList = new ArrayList<OrderListBean.DataBean>();
private Button mSearchBtn = null;
private Button mCancelBtn = null;
private IPresenterImpl mIPresenterImpl = null;
private OrdersModelImpl mOrdersModelImpl = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mOrdersModelImpl = new OrdersModelImpl();
mIPresenterImpl = new IPresenterImpl(mOrdersModelImpl,this);
initView();
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_search:
mIPresenterImpl.doSearch();
break;
case R.id.btn_search_cancel:
mIPresenterImpl.cancelSearch();
break;
}
}
@Override
public void showResult(List<OrderListBean.DataBean> datas) {
if(null!=datas)
setView(datas);
}
@Override
public void cancelShow(List<OrderListBean.DataBean> datas) {
if(null!=datas)
setView(datas);
}
private void initView(){
mRecyclerView = findViewById(R.id.pager_shopping_recycler);
mSearchBtn = findViewById(R.id.btn_search);
mCancelBtn = findViewById(R.id.btn_search_cancel);
mSearchBtn.setOnClickListener(this);
mCancelBtn.setOnClickListener(this);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(layoutManager);
}
private void setView(List<OrderListBean.DataBean> mList){
mDailyPagerAdapter = new DailyPagerAdapter(this,mList);
mRecyclerView.setAdapter(mDailyPagerAdapter);
mDailyPagerAdapter.notifyDataSetChanged();
}
@Override
protected void onDestroy() {
super.onDestroy();
mIPresenterImpl = null;
}
}
- 再來看看IPresenter
public interface IPresenter {
void doSearch();
void cancelSearch();
}
package com.rrcc.mvp.mvpdemo.presenter;
import com.rrcc.mvp.mvpdemo.model.OrderListBean;
import com.rrcc.mvp.mvpdemo.model.OrdersModelImpl;
import com.rrcc.mvp.mvpdemo.view.ISearchView;
import java.util.ArrayList;
import java.util.List;
public class IPresenterImpl implements IPresenter {
private OrdersModelImpl mOrdersModelImpl = null;
private ISearchView mSearchView = null;
private List<OrderListBean.DataBean> mList = null;
public IPresenterImpl(final OrdersModelImpl ordersModelImpl,final ISearchView searchView) {
this.mOrdersModelImpl = ordersModelImpl;
this.mSearchView = searchView;
mList = new ArrayList<OrderListBean.DataBean>();
}
@Override
public void doSearch() {
mList = mOrdersModelImpl.getOrderList();
mSearchView.showResult(mList);
}
@Override
public void cancelSearch() {
if(mList!=null)
mList.clear();
mSearchView.cancelShow(mList);
}
}
從代碼可以看出,IPresenterImpl保留了ISearchView的引用,因此在IPresenterImpl里就可以直接進(jìn)行UI操作了,而不用在Activity里完成。這里使用了ISearchView引用,而不是直接使用Activity,這樣一來,如果在別的Activity里也需要用到相同的業(yè)務(wù)邏輯,就可以直接復(fù)用IPresenterImpl類了。
- 再看看ISearchView 其實(shí)現(xiàn)類為 MainActivity
package com.rrcc.mvp.mvpdemo.view;
import com.rrcc.mvp.mvpdemo.model.OrderListBean;
import java.util.List;
public interface ISearchView {
void showResult(List<OrderListBean.DataBean> data);
void cancelShow(List<OrderListBean.DataBean> data);
}
項(xiàng)目代碼奉上https://github.com/JLhaoran/mvpdemo.git


