mvp是一個(gè)老生常談的話題了,網(wǎng)上太多講MVP的文章了。但有的文章不是結(jié)合了rxjva,retrofit等開源項(xiàng)目,就是講的太過復(fù)雜。所以我會(huì)寫一個(gè)最簡單的mvp demo。來幫助大家理解mvp的本質(zhì)。
大多數(shù)時(shí)候,問題都可以拆解為,WHTA,WHY,HOW;什么是MVP,為什么使用MVP,如何使用MVP。
WHAT:先說什么是MVP,

上面這張圖是網(wǎng)上找的,其實(shí)這張圖已經(jīng)很明顯了,MVP和MVC的區(qū)別,在于以前的View層不僅要和model層交互,還要和controller層交互。而在mvp中,view層只和presenter層交互,而model層也和presenter交互,presenter構(gòu)成了view層和model層的橋梁,也解耦了view層和model層。這一點(diǎn)很關(guān)鍵。我認(rèn)為,這也是mvp的本質(zhì):
解耦view層和model層,讓view層和model層通過presenter層進(jìn)行通信。換個(gè)說法就是讓諸如網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)庫讀寫的邏輯,從activity中剝離出來。activit只負(fù)責(zé)頁面的展示,不關(guān)心model層的邏輯。
WHY:之后來談?wù)劄槭裁匆褂胢vp
如果大家閱讀過github上的一些開源的android項(xiàng)目,例如telegram,一款即時(shí)通訊軟件,如果你對(duì)它不了解,你可以看一下我之前寫的一篇文章在Android Studio上編譯自己的Telegram;或者TweetLanes,一個(gè)第三方的功能完整的Twitter客戶端。這些開源項(xiàng)目都存在一個(gè)問題,就是一個(gè)activity文件中一兩千行,甚至三四千行代碼。當(dāng)然,我想這個(gè)在android項(xiàng)目中并不是一個(gè)奇怪的現(xiàn)象,當(dāng)你的項(xiàng)目足夠復(fù)雜,沒有什么是不可能。在基于傳統(tǒng)android架構(gòu)的mvc模式中。model層很多時(shí)候只是一個(gè)bean類。而view層只是一個(gè)xml文件,controller層也就是activity層幾乎承擔(dān)了諸如網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)庫,更新UI等所有的工作,全在activity里完成。這也就導(dǎo)致了activity文件十分龐大臃腫。但是,問題接踵而至。多人維護(hù)這樣的一個(gè)項(xiàng)目是很痛苦的,一個(gè)幾千行的activity,假如某個(gè)人改動(dòng)了其中的一行,可能會(huì)導(dǎo)致其他修改他的人非常痛苦,因?yàn)闋砍短嗟倪壿嬃?。所以,人們開始嘗試把幾千行的代碼,分成很多模塊。網(wǎng)絡(luò)請(qǐng)求放在一個(gè)模塊,UI更新放在一個(gè)模塊,其他的東西放另一個(gè)模塊。之后就有了MVP。
HOW:在android中如何使用MVP模式
這里我新建了一個(gè)項(xiàng)目,項(xiàng)目里有3個(gè)class和一個(gè)interface,這就是最簡單的mvp模式了。

用到的第三方框架只有常用的okhttp,AndroidManifest里只申請(qǐng)了一個(gè)Internet的權(quán)限,這里就不放出來了。

MainActivity的xml文件也很簡單,最上面有個(gè)edittext,輸入你要請(qǐng)求的網(wǎng)址,中間有個(gè)button,點(diǎn)擊開始請(qǐng)求,下面有個(gè)textview,展示你請(qǐng)求到的內(nèi)容。大概的邏輯就是這樣。這樣的邏輯如果是按以前的寫法,很簡單,幾行代碼就能搞定,但我現(xiàn)在會(huì)用mvp來寫。
之前說要?jiǎng)冸x邏輯,那么就先從activity談起。activity在mvp模式中,只是負(fù)責(zé)界面的展示,其他邏輯放在其他類里,那么怎么進(jìn)行通信呢?通過接口就行了。所以我們這里先定義一個(gè)activity的ui邏輯接口。
public interface MainCallBack {
void getMessage(String message);
void error();
}
之后model層獲取的數(shù)據(jù),將會(huì)通過接口傳遞到activity中,進(jìn)行展示。從某種意義上來說,接口其實(shí)就是mvp中的view層。因?yàn)閍ctivity只是這個(gè)接口的實(shí)現(xiàn)而已。那么我們接下來看一下model層是怎么寫的。
public class MainModel {
public Call getData(String url) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
return client.newCall(request);
}
}
同樣很簡單,這個(gè)應(yīng)該只是okhttp的基礎(chǔ)用法。這里我們沒有調(diào)用enqueue方法傳入callback,是為了之后在presenter中使用,當(dāng)然你也可以在這里調(diào)用,不過這樣一來,你又要在model中聲明一個(gè)接口。好了,“view層”和model層都有了,那么最后只要再有presenter層,那么mvp就完成了。
public class MainPresenter {
private MainCallBack callBack;
private MainModel model;
public MainPresenter(MainCallBack callBack) {
this.callBack = callBack;
model=new MainModel();
}
public void getUrlData(String url){
model.getData(url).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callBack.error();
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
callBack.getMessage(response.body().string());
}
});
}
}
這是一個(gè)典型的mvp模式中的presenter層:接口通過構(gòu)造函數(shù)傳入,model直接在構(gòu)造函數(shù)中實(shí)例化。presenter中調(diào)用model的getdata方法,并傳入對(duì)應(yīng)的參數(shù),之后在okhttp的回調(diào)中調(diào)用對(duì)應(yīng)的ui接口,那么一次mvp模式下的網(wǎng)絡(luò)請(qǐng)求就完成了。
具體操作可以看一下activity中的代碼:
public class MainActivity extends AppCompatActivity implements MainCallBack {
private TextView resultTextView;
private MyHandler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.btn);
handler = new MyHandler(this);
final EditText editText = findViewById(R.id.et_url);
resultTextView = findViewById(R.id.tv_result);
final MainPresenter presenter = new MainPresenter(this);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String url = editText.getText().toString();
presenter.getUrlData(url);
}
});
}
@Override
public void getMessage(String message) {
Message msg = handler.obtainMessage(0, message);
handler.sendMessage(msg);
}
@Override
public void error() {
Message msg = handler.obtainMessage(1, "error");
handler.sendMessage(msg);
}
private static class MyHandler extends Handler {
private WeakReference<MainActivity> reference;
private MyHandler(MainActivity activity) {
reference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity = reference.get();
switch (msg.what) {
case 0:
activity.resultTextView.setText(msg.obj.toString());
break;
case 1:
activity.resultTextView.setText(msg.obj.toString());
break;
}
}
}
}
運(yùn)行結(jié)果如下兩圖:


當(dāng)網(wǎng)址錯(cuò)誤的時(shí)候我直接在textview中顯示“error”,當(dāng)然你也可以自由選擇顯示什么。因?yàn)閛khttp的回調(diào)是在子線程完成的,要刷新ui必須在主線程刷新,所以我這里寫了一個(gè)handler。當(dāng)然你也可以在model層進(jìn)行線程調(diào)度。比如一個(gè)基于okhttp的開源框架,okgo就是這么做的

他在初始化的時(shí)候聲明了一個(gè)主線程的handler,之后調(diào)用handler的post方法,完成從子線程到主線程的切換。當(dāng)然這都是后話了。
看完這些, 你應(yīng)該了解了mvp是怎么樣的一回事。萬變不離其宗,在此基礎(chǔ)上擴(kuò)展,你可以加入任何你喜歡的框架, rxjava,retrofit等。
或許看到這里還是會(huì)有人有疑問,我在一個(gè)activity中可以完成的事,寫成這么幾個(gè)類有必要嗎?雖然我在前面已經(jīng)解釋過了,但我還是會(huì)結(jié)合我工作中的例子再解釋一遍:我之前曾參與開發(fā)過一個(gè)和機(jī)票有關(guān)的app,如果有人接觸過就知道,機(jī)票類app十分的繁雜。流程繁多,規(guī)則復(fù)雜,而且不允許出錯(cuò)。訂票,退票,改簽,實(shí)名認(rèn)證,乘機(jī)人等,都有一系列的相當(dāng)復(fù)雜的流程。以提交訂單為例子,你可能需要提交很多東西,提交退票申請(qǐng)、提交訂票申請(qǐng)、提交改簽申請(qǐng)等等。邏輯都是先進(jìn)行一系列檢測和判斷,彈出dialog框,把數(shù)據(jù)發(fā)送到服務(wù)器端,之后視服務(wù)器返回結(jié)果,進(jìn)行跳轉(zhuǎn)或者再彈出dialog框。如果你和之前一樣,把所有邏輯寫在一個(gè)activity里面,那么不同的提交,是寫在不同的activity類里面的,為了省時(shí)間,大概很多人都會(huì)選擇復(fù)制黏貼重復(fù)的邏輯。但在這個(gè)過程中,可能會(huì)產(chǎn)生很多問題,比如你又復(fù)制漏了一段代碼之類的。如果使用mvp的話,只需要每個(gè)activity類都繼承相同的ui接口,或者使用相同的model和presenter,那么最后產(chǎn)生的效果其實(shí)是一樣的。但這樣可以讓代碼更容易維護(hù),修改一個(gè)地方的時(shí)候,所有地方就都變了。
那么,是否所有地方都需要mvp呢?答案是否定的。我認(rèn)為,設(shè)計(jì)模式的關(guān)鍵在于用在正確且合適的地方。我在一個(gè)項(xiàng)目中,可能會(huì)同時(shí)使用mvp,mvc,mvvm。因?yàn)槲抑肋@里應(yīng)該用什么,如果一個(gè)邏輯不具有復(fù)用的可能,同時(shí)也非常簡單,那么使用mvp就是多此一舉。如果一個(gè)頁面,帶有id的view非常多,那么使用mvvm也就是android的databinding特性可以節(jié)約你大量的時(shí)間。
最后給出幾點(diǎn)使用mvp的建議,經(jīng)供參考: