談談我對 MVP 的理解
說實話,MVP 這種模式或者說設計思想也已經出來很久了,現在最新的使用的是 MVVM 設計模式,不斷對於萌新來說,還是需要一步一步的向前走。畢竟,人不能一口吃成一個大胖子是吧。
本人大四,目前正在實習,其實接觸研究 MVP 也有很久了,期間找過網上的老哥們幫忙寫過 MVPDemo,也從 GitHub 上看過很多專案原始碼。也看過他們的設計思想等。不過一直對於 MVP 中的實現引用啊,資料怎麼傳遞過去的很迷。也看過 Google 的官方 Demo,看完後感覺還不如其他的簡單 Demo 容易明白。所以我這次結合自己寫的小 Demo 和理解,完整的講述下我眼中的 MVP。希望能幫到大家,如果有什麼錯誤還希望各位大佬多多指教。
文末附有原始碼

01.png
話不多說,這個圖是我盜來的,,,
其實對於 MVP 的好處我也不多和大家說了,說來說去也就那麼幾點:
1,鬆耦合
2,方便單元測試
3,程式碼整潔,容易修改
4,等等。。
先給大家展示下我的專案目錄結構:

02.png
ApiService 目錄:裡面放了請求介面的方法
package com.example.root.mvp_demo.ApiInterface; import com.example.root.mvp_demo.bean.ArticleData; import io.reactivex.Observable; import retrofit2.http.GET; import retrofit2.http.Path; /** * author:Jiwenjie * email:[email protected] * time:2018/10/21 * desc: 有關 Retrofit 和 RxJava 結合的功能介面 * version:1.0 */ public interface ApiService { @GET("/article/list/{page}/json") Observable<ArticleData> getArticle(@Path("page") int page); }
這裡大家應該都可以理解,因為我請求網路使用的是 RxJava + Retrofit,所以這裡的開頭是 Observable,如果只是單純的 Retrofit 的話那就改成 Call 就可以了,其他不必變化。
tips:這裡有一個注意點就是 @Path 和 @Query 的區別。前者是在 URL 中間新增引數,上面程式碼有表示,需要用 @Path 代替的引數在連結中使用 { } 包裹起來,注意名稱需要對應; 而後者是屬於拼接引數,即 URL 輸入完成後,在最後通過 ?和 & 連線起來的引數。相信我說的很明白了,不能明白的話就去看下你們經常寫的 URL 格式你也就會明白。
base 目錄:這裡存放了 BaseView 和 BasePresenter,這是大家的習慣啦,我正常會把 BaseActivity,BaseFragment 等都放在 Base 目錄下
package com.example.root.mvp_demo.base; /** * author:Jiwenjie * email:[email protected] * time:2018/10/21 * desc: * version:1.0 */ public interface BasePresenter { void start(); void destroy(); }
BaseView
package com.example.root.mvp_demo.base; /** * author:Jiwenjie * email:[email protected] * time:2018/10/21 * desc: * version:1.0 */ public interface BaseView { }
我這裡給 BaseView 定義了空實現,給 Presenter 定義了 onStart 和 onDestroy 方法。這些不用多說,相信大家都能夠看懂。
bean 目錄:存放實體類
contact 目錄:契約目錄,這裡存放契約類,一般 APP 的一個介面就是一個契約類,這也是 Google 的推薦寫法
package com.example.root.mvp_demo.contact; import com.example.root.mvp_demo.base.BasePresenter; import com.example.root.mvp_demo.base.BaseView; import com.example.root.mvp_demo.bean.ArticleData; /** * author:Jiwenjie * email:[email protected] * time:2018/10/21 * desc:首頁文章的契約類 * version:1.0 */ public class ArtContact { public interface ArtView extends BaseView { void setData(ArticleData data); } public interface ArtPresenter extends BasePresenter { void requestData(); } }
顧名思義,就像簽訂契約,哪一個 View 和 哪一個 Presenter 相對應起來。注意 View 很重要,它是紐帶。在具體使用的時候就需要 Activity 或者 Fragment 實現該 View,然後把引數傳遞給 Presenter。在Presenter 中做操作。
utils 目錄:再看 Model 目錄前先看 utils 目錄,這裡封裝了網路請求方法
package com.example.root.mvp_demo.utils; import com.example.root.mvp_demo.ApiInterface.ApiService; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.CallAdapter; import retrofit2.Converter; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; /** * author:Jiwenjie * email:[email protected] * time:2018/10/21 * desc: 有關網路請求的 Util * version:1.0 */ public class RetrofitManager { private static ApiService apiService; private static OkHttpClient okHttpClient; private static Converter.Factory gsonConverterFactory = GsonConverterFactory.create(); private static CallAdapter.Factory rxJaveCallAdapterFactory = RxJava2CallAdapterFactory.create(); private static HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); private static OkHttpClient getClient() { // 可以設定請求過濾的水平,body,basic,headers interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); return okHttpClient = new OkHttpClient.Builder() .addInterceptor(interceptor)// 日誌,所有請求響應都看到 .connectTimeout(60L, TimeUnit.SECONDS) .readTimeout(60L, TimeUnit.SECONDS) .writeTimeout(60L, TimeUnit.SECONDS) .build(); } public static ApiService getApiService() { if (apiService == null) { Retrofit retrofit = new Retrofit.Builder() .client(getClient()) .baseUrl("http://www.wanandroid.com/") .addConverterFactory(gsonConverterFactory) .addCallAdapterFactory(rxJaveCallAdapterFactory) .build(); apiService = retrofit.create(ApiService.class); } return apiService; } }
相信對於 Retrofit 有些瞭解的童鞋都知道其實 Retrofit 的實現就是 OkHttp 外面包裹了一層能夠和 RxJava 連線的部分而已,所以這裡的 Client 使用的是 okHttpClient。介面使用的是鴻洋大神的 玩 Android 介面。話說鴻洋老哥去頭條工作了,,,大寫的羨慕。都知道頭條待遇好要求高。
大家可以根據我寫的在做擴充套件。PS:其實這裡有關獲取 APiService 就可以修改擴充套件,使用 泛型和 Class 來處理代替。這樣在 Model 呼叫的時候直接傳入引數就可以。如果照我這樣寫,每個介面都要實現一個方法太多重複了。
Model 目錄:存放 Model 用來具體操作資料的部分
package com.example.root.mvp_demo.model; import com.example.root.mvp_demo.bean.ArticleData; import com.example.root.mvp_demo.utils.RetrofitManager; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; /** * author:Jiwenjie * email:[email protected] * time:2018/10/21 * desc: MVP 中的 model * version:1.0 */ public class ArticleModel { // 獲取首頁技術文章的 model public Observable<ArticleData> getArtData(int page) { return RetrofitManager.getApiService().getArticle(page) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } }
其實不論在 MVP 或者 MVC 模式中,Model 都是在操作資料的部分,這一點萬年不變。MVVM 我還沒看,不太瞭解,只知道好像是增加了一個 ViewModel,所以應該也是一樣。
這上面從 22 行到 24 行程式碼是 RxJava 的功能體現,鏈式呼叫,可以說是牛逼了。他的意思是被觀察者獲取資料請求網路的時候在 IO 執行緒,所以被觀察者解綁的時候也在 IO 執行緒。而觀察者執行在 UI 執行緒。這裡就體現了執行緒一行程式碼切換的強大。隨便切,且不過來算我輸,,,
如果對於這些不瞭解的童鞋可以去網上搜一搜,
這篇文章寫的很好。
Presenter 目錄:存放具體的 Prsenter 實現
大家都知道在 Activity 或者 Fragment 中需要持有 Presenter 的引用,在 Presenter 中對資料和 View 進行操作,所以我們還需要定義一個類來實現 Presenter 介面。
package com.example.root.mvp_demo.presenter; import android.content.Context; import android.util.Log; import android.widget.Toast; import com.example.root.mvp_demo.model.ArticleModel; import com.example.root.mvp_demo.bean.ArticleData; import com.example.root.mvp_demo.contact.ArtContact; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; /** * author:Jiwenjie * email:[email protected] * time:2018/10/21 * desc: 實際的 Presenter,在這裡需要持有 View 和 Model 的引用, * 通過 model 獲取資料,把獲取的資料顯示在 Model 中 * version:1.0 */ public class Presenter implements ArtContact.ArtPresenter { private Context mContext; private ArticleModel mModel; private ArtContact.ArtView mView; // 防止記憶體洩漏部分 protected Disposable disposable; // 初始化部分 public Presenter(Context context, ArtContact.ArtView view) { this.mContext = context; this.mView = view; mModel = new ArticleModel(); } @Override public void requestData() { destroy(); disposable = mModel.getArtData(0) .subscribe(new Consumer<ArticleData>() { @Override public void accept(ArticleData articleData) throws Exception { Toast.makeText(mContext, "資料獲取成功", Toast.LENGTH_SHORT).show(); mView.setData(articleData); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { Toast.makeText(mContext, "資料獲取失敗", Toast.LENGTH_SHORT).show(); } }); } @Override public void start() { Log.i("Presenter", "開始獲取資料"); } @Override public void destroy() { if (disposable != null && !disposable.isDisposed()) { disposable.dispose(); } } }
這裡我都寫了註釋,相信大家都能看懂,如果不明白的話可以私信或者 Google,如果實在不行的話,,,百度也行。注意這裡我傳遞引數是 0,固定寫死的,大家實際使用的時候需要修改一下,改成變數的形式就 OK 了。
MainActivity
重點來了,重點來了,前面說的再多都是鋪墊,還要具體使用才行。先看佈局,沒啥好說,一個 TextView 和一個 Button。
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout 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" tools:context=".MainActivity"> <TextView android:id="@+id/tv_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="20dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/btn_start" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="開始" android:textSize="18dp" app:layout_constraintTop_toBottomOf="@id/tv_content" /> </android.support.constraint.ConstraintLayout>
這裡給大家推薦下約束佈局,真的很好用,而且上網搜一搜就能會。真的是巢狀好幾層的頁面他就一層搞定。對於優化效能有要求的話強烈推薦。不過沒要求的話也就算了,因為寫起來還有有些費事的,起碼 Id 你就要想好多不重複的才行。
MainActivity 原始碼
package com.example.root.mvp_demo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import com.example.root.mvp_demo.bean.ArticleData; import com.example.root.mvp_demo.contact.ArtContact; import com.example.root.mvp_demo.presenter.Presenter; public class MainActivity extends AppCompatActivity implements ArtContact.ArtView { private Button btn_start; private TextView tv_content; private Presenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initEvent(); } private void initView() { btn_start = findViewById(R.id.btn_start); tv_content = findViewById(R.id.tv_content); presenter = new Presenter(getApplicationContext(), this); } private void initEvent() { btn_start.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.start(); presenter.requestData(); } }); } @Override public void setData(ArticleData data) { tv_content.setText(data.getData().getDatas().toString()); } @Override protected void onDestroy() { super.onDestroy(); presenter.destroy(); } }
實現了契約類中 View 的介面,重寫了方法,把持了 Presenter 的引用。初始化 Presenter 的時候把 View
作為引數傳遞了過去。大致流程就是這樣,也許真的是會了不難吧。我一開始總是不明白,現在怎麼想都明白。。。希望這篇文章能幫到大家:
原始碼敬上: ofollow,noindex">https://github.com/jiwenjie/MVP_Demo