說明:不講原理,不講優化,就是幹
目標:學會如何搭建最最基本的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