1. 程式人生 > >這一定是最簡單的MVP+Retrofit

這一定是最簡單的MVP+Retrofit

說明:不講原理,不講優化,就是幹

目標:學會如何搭建最最基本的mvp架構

簡介

我承認畫圖不是我的強項

MVP是MVC衍生出來的架構,現在也比較成熟了,用的人也多了,面試也會考了,所以你必須要知道了

M:資料層(資料庫,檔案,網路等)

V:UI層(Activity,Fragment,View及其子類,Adapter及其子類)

P:中介(作用:關聯V和M)

經過思想鬥爭,我覺得理論上的東西解釋再多不如程式碼敲一遍,下面用一個使用者登陸功能講解如何搭建MVP架構,順便簡單用下Retrofit網路請求庫(別怕都有註釋)總之,MVP記住一個核心思想:V層就只做UI的操作,所有的業務處理和資料處理都交給P層(不然要你何用)就相當於把Activity裡你寫的眾多網路請求和資料處理程式碼統統提取封裝到P裡了

MVP結構

api:Retrofit專用的,就兩行程式碼,和mvp無關,待會給你看

bean:登陸的實體類,和mvp無關

login:可以看出是按功能分包。他比平時見到的多出來兩個類,一個是 LoginPresenter(P層),一個是 LoginContract(契約介面類,用於將P和V介面封裝到一起)他們是mvp重要的組成部分

架構圖如下:

思路

mvp結構實現分三步:

一,搞一個介面契約類Contract,內含V介面和P介面

二,搞一個實現V介面的view類

三,搞一個實現P介面的presenter類

第一步:

搞一個介面契約類Contract,內含V介面和P介面

。V接口裡面放的是UI更新方法(V介面的實現類用到的方法);P接口裡面放的是網路請求,資料讀取,檔案讀取等具體的業務操作方法(P介面的實現類用到的方法)

從動態圖可以看到,一共有三個地方有UI介面變化

①點選登陸展示等待載入遮罩

②登陸成功或者失敗後取消等待載入遮罩

③取消等待載入遮罩的同時吐司

所以V接口裡會有三個UI更新方法,那麼就在V介面寫三個方法唄,值得注意的是setPresenter方法只是用來把V層和P層關聯的,本身與UI更新無關,但是必須要有的

P介面在本例中只需要實現一個登陸按鈕觸發的網路請求就可以啦,所以只有一個方法

由此可見,契約類Contract把V和P介面封裝在了一塊,而V和P介面又把具體的view和presenter用到的方法封裝在了一塊

/**
* 包含View和Presenter的契約介面
* Created by wangjiong on 2017/12/7.
*/

public interface LoginContract {
   /**
    * 與UI相關,與view相關操作
    */

   interface View {
       // 定義Presenter
       void setPresenter();
       // 展示等待載入頁面
       void showLoading();
       // 隱藏等待載入頁面
       void hideLoading();
       // 顯示登陸資訊
       void showLoginInfo(String msg);
   }
   /**
    * 與業務相關
    */

   interface Presenter {
       /**
        * 登陸
        * @param userId       使用者id
        * @param userPassword 密碼
        */

       void login(String userId, String userPassword);
   }
}

第二步:

搞一個實現V介面的view類。本例中的view類就是LoginActivity。首先實現介面所有的方法是必須的,然後這裡有兩個地方需要注意

一,我們是在實現V層介面setPresenter方法裡通過LoginPresenter的構造方法將LoginActivity傳遞過去的(LoginPresenter是P的實現類),這裡要記得主動呼叫一下setPresenter方法觸發初始化presenter這個事

二,就是所謂的MVP中V層和P層分隔開,UI和業務分隔開。比如本例登陸按鈕的點選事件,他並不是我們平時看到的直接擼網路請求程式碼,而是呼叫了P層裡的的方法,說人話就是把網路請求一大堆程式碼封裝了一個方法放到了P層那個類裡頭了,我們在呼叫的時候傳需要用到的引數就行了。你說坑不坑,這點玩意說的那麼高大尚

/**
* MVP層中的View層
*/

public class LoginActivity extends AppCompatActivity implements LoginContract.View {
   private LoginPresenter mPresenter;
   private EditText mEtName, mEtPwd;
   private LinearLayout mLayoutLoading;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_login);
       setPresenter();// 初始化presenter(很重要!不能說重寫就不管了,一定要在view初始化呼叫此方法)
       mEtName = findViewById(R.id.et_name);// 使用者名稱
       mEtPwd = findViewById(R.id.et_pwd);// 密碼
       mLayoutLoading = findViewById(R.id.layout_loading);// 遮罩層
       findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {// 登陸按鈕
           @Override
           public void onClick(View view) {// 點選登陸不是直接請求網路,而是通過presenter請求網路,然後將請求回來的資料交給view來更新
               mPresenter.login(mEtName.getText().toString().trim(), mEtPwd.getText().toString().trim());
           }
       });
   }
   @Override
   public void setPresenter() {
       mPresenter = new LoginPresenter(this);// 一是為了例項化presenter,二是通過構造方法將view例項傳遞給presenter
   }
   @Override
   public void showLoading() {// 展示遮罩層
       mLayoutLoading.setVisibility(View.VISIBLE);
   }
   @Override
   public void hideLoading() {// 隱藏遮罩層
       mLayoutLoading.setVisibility(View.GONE);
   }
   @Override
   public void showLoginInfo(String msg) {// 吐司登陸資訊
       Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
   }
}

第三步:

搞一個實現P介面的presenter類。本例中presenter類就是LoginPresenter,可以看到他就一個構造方法(用來關聯V層並獲取view例項化物件)和一個請求網路資料的方法。高能預警:網路請求前UI頁面需要展示一個遮罩層,所以呼叫了view的showLoading方法,請求結束後UI頁面需要取消遮罩並吐司,所以呼叫了view的hideLoading方法和showLoginInfo方法

/**
* MVP中的P層
* Created by wangjiong on 2017/12/7
*/

public class LoginPresenter implements LoginContract.Presenter {
   private LoginContract.View mView;
   public LoginPresenter(LoginContract.View view) {// 獲取到view的例項化物件
       this.mView = view;
   }
   @Override
   public void login(String userId, String password) {
       mView.showLoading();// 呼叫view的展示遮罩方法(view用來更新具體的UI)
       // 網路請求(可以自己封裝一個網路庫)
       Retrofit retrofit = new Retrofit.Builder()
               .baseUrl("http://192.168.1.101:8080/merclienttest/")// 請求的url
               .addConverterFactory(GsonConverterFactory.create())// 設定Gson為實體類解析工具
               .build();
       LoginApi loginApi = retrofit.create(LoginApi.class);// 傳入一個介面類並返回此類的例項物件
       Call<LoginBean> call = loginApi.login(userId, password);// 呼叫類中定義的login方法
       call.enqueue(new Callback<LoginBean>() {// retrofit非同步請求
           @Override
           public void onResponse(Call<LoginBean> call, Response<LoginBean> response) {
               mView.hideLoading();// 呼叫view的隱藏遮罩方法(view用來更新具體的UI)
               mView.showLoginInfo(response.body().getMsg());// 呼叫view的吐司方法(view用來更新具體的UI)
           }
           @Override
           public void onFailure(Call<LoginBean> call, Throwable t) {
               mView.hideLoading();
           }
       });
   }
}

全劇終

媽個雞這是啥玩意?完了?哈哈哈,沒錯這就是MVP。就是這麼簡單。只需要記住一個核心思想V層僅僅是處理UI頁面的(只管化妝接客,拉皮條找老鴇子P),業務邏輯放在P層去處理(網路請求,資料庫,檔案等),P處理完之後再呼叫一下V層已經寫好的對應更新UI的方法即可

擴充套件

沒忘記Retrofit哦,簡單介紹下使用方法

分三步:

一,搞一個介面類Api

二,搞一個對應的實體類bean

三,呼叫Retrofit方法請求網路

第一步:搞一個Api介面。本例中是LoginApi

第一行程式碼 @GET("login") 這個Get就代表get請求,如果換成Post那就代表post請求。login代表介面名(介面名需要後臺提供)

第二行程式碼 Call<LoginBean> login(@Query("userId") String userId, @Query("password") String password); 其中 login 代表自己定義的一個方法,名字隨便起(小駝峰式命名),@Query("userId") 代表介面中有個名為 userId 的引數, String userId 代表給這個引數傳值,值為userId(名字隨便起)

/**
* 登陸介面
* Created by wangjiong on 2017/12/7.
*/

public interface LoginApi {
   @GET("login")//get請求login介面(介面名需要後臺提供)
   Call<LoginBean> login(@Query("userId") String userId, @Query("password") String password);  // 宣告一個login的方法(隨便寫),兩個string形參,並返回一個實體類LoginBean
}

沒反應過來?

jsonObject.put("userId",userId) 這樣寫懂了吧,第一個userId是接口裡的引數名,第二個userId是你傳的字串值,名字隨便搞的,叫啥都行

你也可以@Query("userId") String myUserId 這就等價於

jsonObject.put("userId",myUserId  )  這裡只是為了說明問題,所以不必太在意細節

第二步,搞一個對應的實體類bean。這個對應本例中的LoginBean。推薦用GsonFormat外掛自動生成,啥?沒聽過,你又不是妹子,百度吧

public class LoginBean {
   /**
    * code : 200
    * msg : 登陸成功
    */

   private String code;
   private String msg;
   public String getCode() {
       return code;
   }
   public void setCode(String code) {
       this.code = code;
   }
   public String getMsg() {
       return msg;
   }
   public void setMsg(String msg) {
       this.msg = msg;
   }
}

第三步,呼叫Retrofit方法請求網路。做完上面兩步,就可以正常呼叫了,咋用?你又不是妹子,參考LoginPresenter吧

/**
* MVP中的P層
* Created by wangjiong on 2017/12/7
*/

public class LoginPresenter implements LoginContract.Presenter {
   private LoginContract.View mView;
   public LoginPresenter(LoginContract.View view) {// 獲取到view的例項化物件
       this.mView = view;
   }
   @Override
   public void login(String userId, String password) {
       mView.showLoading();// 呼叫view的展示遮罩方法(view用來更新具體的UI)
       // 網路請求(可以自己封裝一個網路庫)
       Retrofit retrofit = new Retrofit.Builder()
               .baseUrl("http://192.168.1.101:8080/merclienttest/")// 請求的url
               .addConverterFactory(GsonConverterFactory.create())// 設定Gson為實體類解析工具
               .build();
       LoginApi loginApi = retrofit.create(LoginApi.class);// 傳入一個介面類並返回此類的例項物件
       Call<LoginBean> call = loginApi.login(userId, password);// 呼叫類中定義的login方法
       call.enqueue(new Callback<LoginBean>() {// retrofit非同步請求
           @Override
           public void onResponse(Call<LoginBean> call, Response<LoginBean> response) {
               mView.hideLoading();// 呼叫view的隱藏遮罩方法(view用來更新具體的UI)
               mView.showLoginInfo(response.body().getMsg());// 呼叫view的吐司方法(view用來更新具體的UI)
           }
           @Override
           public void onFailure(Call<LoginBean> call, Throwable t) {
               mView.hideLoading();
           }
       });
   }
}

原始碼地址:https://github.com/GodJiong/mvp