MVP 全稱:Model-View-Presenter
MVP 是從經典的模式MVC演變而來,它們的基本思想有相通的地方:Controller/Presenter負責邏輯的處理,Model提供數據,View負責顯示。
模式描述
Model-view-presenter (MVP) 是使用者界面 設計模式的一種,被廣范用于便捷自動化單元測試和在呈現邏輯中改良分離關注點(separation of concerns)。
- Model 定義使用者界面所需要被顯示的資料模型,一個模型包含著相關的業務邏輯。
- View 視圖為呈現使用者界面的終端,用以表現來自 Model 的資料,和使用者命令路由再經過 Presenter 對事件處理后的資料。
- Presenter 包含著元件的事件處理,負責檢索 Model 取得資料,和將取得的資料經過格式轉換與 View 進行溝通。
下面一個圖就能很好展示它們之間的關系
那我們開始實例講解
現在我們做一個小Demo。輸入一個qq號,能查到QQ號碼吉兇
請求示例:
http://api.jisuapi.com/qqluck/query?qq=22222&appkey=yourappkey
Json返回示例
{
"status": "0",
"msg": "ok",
"result": {
"qq": "22222",
"score": "62",
"luck": "兇",
"content": "煩悶懊惱,事事難展,自防災禍,始免困境",
"character": "要面包不要愛情",
"characterdetail": "責任心重,尤其對工作充滿熱誠,是個徹頭徹尾工作狂。但往往因為過分專注職務,而忽略身邊的家人及朋友,是個寧要面包不需要愛情的理性主義者。"
}
}
項目結構
其中api 是接口。bean 是javabean 用來作數據模型。model來進行網絡請求獲取數據。presenter來進行事件處理。把處理好的數據通過一定方式傳給view ,也就是我們的activity,然后展示給用戶看。
所以這就結構就看著非常簡單。model 只做網絡請求,presenter把請求的數據處理好 傳給view,然后view在展示給用戶看。這中間view和model是沒有任何的關聯,只是有個中間著presenter來進行相應的處理
我們接下來先把api 和bean 填上,代碼如下
網絡請求用的retrofit框架
compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
MyService類,簡單封裝網絡請求
public class MyService {
public static String baseUrl="http://api.jisuapi.com/qqluck/";
public static Retrofit mRetrofit=new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
public static <T> T getApi(Class<T> mClass){
return mRetrofit.create(mClass);
}
}
DemoApi類, api接口類
public interface DemoApi {
@POST("query")
Call<QqluckData> loadqQuery(@Query("qq") int qq, @Query("appkey") String appkey);
}
接下來開始寫view 。因為我們要做一個qq兇吉,那么就要把展示結果給用戶看。所以先寫一個接口.只有一個方法。用來接收presenter傳過了的數據,展示給用戶看
public interface IMainView {
void showInfo(String response);
}
然后用Maintivivty來繼承IMainView接口,實現其中的方法,獲得數據,展示給用戶看,代碼如下
public class MainActivity extends AppCompatActivity implements IMainView {
private TextView mluck;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mluck= (TextView) findViewById(R.id.luck);
}
public void onClick(View v){
switch (v.getId()){
case R.id.send:
break;
}
}
@Override
public void showInfo(String response) {
mluck.setText(response);
}
}
現在基本完成了一半,下面接著寫model 的網絡請求
根據上面的請求示例,我們需要傳一個qq號,和一個appkey。appkey去注冊申請一個就好,在這就不多說,另外需要一個Callback回調函數。需要把請求到的數據傳給presenter。model也到這差不多了。
public class MainModel {
public void getData( int qq, String appkey, Callback<QqluckData> mCallback) {
DemoApi api = MyService.getApi(DemoApi.class);
Call<QqluckData> qqluckDataCall = api.loadqQuery(qq, appkey);
qqluckDataCall.enqueue(mCallback);
}
}
現在就開始寫presenter,代碼如下
public class MainPresenter {
public IMainView mIMainView;//view 的引用
public MainModel mMainModel;//model的引用
public MainPresenter(IMainView IMainView) {
mIMainView = IMainView;
mMainModel=new MainModel();
}
public void load(int qq){//加載數據。通過model 來獲取
String appkey="34865d1e2ff7170f";
mMainModel.getData(qq, appkey, new Callback<QqluckData>() {
@Override
public void onResponse(Call<QqluckData> call, Response<QqluckData> response) {
//獲取到數據
QqluckData body = response.body();
//調用showInfo方法,傳遞數據。展示給用戶看
mIMainView.showInfo("luck:"+body.getResult().getLuck()+"\ncontent:"+body.getResult().getContent());
}
@Override
public void onFailure(Call<QqluckData> call, Throwable t) {
}
});
}
}
presenter 代碼沒多少。持有view 和midel的引用。 當view初始化presenter時會把view 傳過來。presenter內部在持有Model的引用。當用戶發起qq測吉兇功能時,由view 調用presenter的load方法。presenter在由model去發送網絡請求。把數據結果在傳回給view。下面我在把完整的Maintivit 貼出來
public class MainActivity extends AppCompatActivity implements IMainView {
private TextView mluck;
private MainPresenter mMainPresenter;
private EditText mEdqq;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mluck= (TextView) findViewById(R.id.luck);
mEdqq= (EditText) findViewById(R.id.ed_qq);
//onCreate時初始化MainPresenter,因為當前已繼承IMainView,直接傳this即可。這個時候MainPresenter就持有IMainView的引用了
mMainPresenter=new MainPresenter(this);
}
public void onClick(View v){
switch (v.getId()){
case R.id.send:
if(TextUtils.isEmpty(mEdqq.getText().toString())){
return;
}
//發送請求。調取MainPresenter.load的方法
mMainPresenter.load(Integer.parseInt(mEdqq.getText().toString()));
break;
}
}
@Override
public void showInfo(String response) {
//展示結果
mluck.setText(response);
}
}
好了。到此一個簡單的mvp demo已經寫完了 。。或許有很多人在此覺得mvp 太麻煩了。一個簡單的網絡請求,寫的這么多類,這么多代碼。但是我要說的是,你看我們的activity類。代碼是不是非常少,是不是很簡潔,其他的也都分工很明確。耦合性也很低。便于后期維護,這個時候mvp 的優點便體現出來了。
對了。展示一下結果
MVP的優點
- 1、模型與視圖完全分離,我們可以修改視圖而不影響模型
- 2、可以更高效地使用模型,因為所有的交互都發生在一個地方——Presenter內部
- 3、我們可以將一個Presenter用于多個視圖,而不需要改變Presenter的邏輯。這個特性非常的有用,因為視圖的變化總是比模型的變化頻繁。
- 4、如果我們把邏輯放在Presenter中,那么我們就可以脫離用戶接口來測試這些邏輯(單元測試)
MVP的缺點
- 由于對視圖的渲染放在了Presenter中,所以視圖和Presenter的交互會過于頻繁。還有一點需要明白,如果Presenter過多地渲染了視圖,往往會使得它與特定的視圖的聯系過于緊密。一旦視圖需要變更,那么Presenter也需要變更了。比如說,原本用來呈現Html的Presenter現在也需要用于呈現Pdf了,那么視圖很有可能也需要變更。
如果有什么問題不懂。或者我那個地方寫的有問題。歡迎小伙伴提出。最后說一下。MVP只是個設計模式。不一定非要按部就班。我們要活學活用。適合自己的才是最好的。
源代碼
https://github.com/ccicec/AndroidMvpDemo/tree/master
A New Day