現在的安卓開發已經很成熟,不像幾年前大部分人還處於技術摸索階段,當技術問題不再是安卓開發的難題時,更多人開始關注架構設計,代碼質量,更想易於測試,維護方便,邏輯清晰。大家試圖讓所有的代碼都高度解耦,各層分離,從而達到目的。MVP的架構模式,就這樣應運而生。這裏我希望以最簡單的例子,讓大家快速入門MVP模式。
認識MVP
MVP就是Model,View,Presenter的縮寫。
對比MVC,MVP模式的特點是V層和M層不可互相接觸,需要通過主導器Presenter進行關聯,P層進行所有的邏輯處理,V層僅僅負責視圖展示,M層負責獲取數據。
下面是MVC和MVP對比圖:
圖片出處
實例
依然是最簡單的登錄頁面,我通過一個登錄業務,來詳述MVP在項目中的使用,文後會附上代碼地址。
既然要解耦,必然是面向接口的編程,就按照寫代碼的順序從Model開始看:
LoginModel.java
/**
* Created by yuankundong on 2016/03/03.
*/
public interface LoginModel {
void toLogin(String username, String password, OnLoginListener onLoginListener);
}
對於Model層,沒有什麼多餘的方法,就是完成登錄,把結果返回給Presenter。這裏我又寫了一個接口OnLoginListener,爲了給Presenter回調,讓View和Model分離,當然,如果我們的框架中使用了otto之類的東東,那就不需要這個接口。接着看上面接口的實現:
LoginModelImpl.java
/**
* Created by yuankundong on 2016/03/03.
*/
public class LoginModelImpl implements LoginModel{
String url = "http://xxxxx";
public final static int TIME_OUT_CODE = 99;
public final static String TIME_OUT_MSG = "網絡連接不可用,請稍後重試";
@Override
public void toLogin(final String username, final String password, final OnLoginListener onLoginListener) {
StringRequest stringRequest = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Type type = new TypeToken<UserBean>() {
}.getType();
Gson gson = new Gson();
UserBean userBean = gson.fromJson(response, type);
if (userBean.isSuccess()) {
onLoginListener.OnSuccess(userBean);
} else {
onLoginListener.OnError(userBean.getCode(), userBean.getMsg());
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onLoginListener.OnError(TIME_OUT_CODE, TIME_OUT_MSG);
}
}) {
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> map = new HashMap<>();
map.put("phone", username);
map.put("password", MD5Utils.stringToMD5(password));
return map;
}
};
BaseApplication.requestQueue.add(stringRequest);
}
}
這裏使用Volley網絡框架,假設登錄成功後,返回的json數據是這個類型:
{
"code": 0,
"msg": "登錄成功"
}
那麼我的實體Bean可以這麼設計:
/**
* Created by yuankundong on 2016/03/03.
*/
public class UserBean {
private int code; // 返回碼,0爲成功
private String msg; // 返回信息
public UserBean(int code, String msg) {
this.code = code;
this.msg = msg;
}
public boolean isSuccess() {
return code == 0;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
上述代碼就完成了Model層,因爲Presenter是M和V的聯絡人,所以,我們再把View層寫了,同樣,需要先寫接口,這個接口裏面我們寫什麼呢,就是在頁面上,會發生的所有事,比如,獲取用戶名密碼,點擊登錄後有進度條提示,登錄反饋後取消提示等。
/**
* Created by yuankundong on 2016/03/03.
*/
public interface LoginView {
//取得用戶名
String getUsername();
//取得密碼
String getPassword();
//登錄成功後
void loginSuccess();
//登錄失敗後
void loginError(String msg);
//展示進度條
void showProgress();
//隱藏進度條
void hideProgress();
//當需要彈出提示信息時
void showMessage(String msg);
}
我們能想到的都寫進來了,接着我們寫這個接口的實現類,就是個Activity。
/**
* Created by yuankundong on 2016/03/03.
*/
public class LoginActivity extends AppCompatActivity implements LoginView{
TextView username;
TextView password;
Button login;
ProgressDialog dialog;
LoginPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter = new LoginPresenterImpl(this);
username = (TextView) findViewById(R.id.tv_username);
password = (TextView) findViewById(R.id.tv_password);
login = (Button) findViewById(R.id.btn_login);
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.actionLogin();
}
});
}
@Override
public String getUsername() {
return username.getText().toString();
}
@Override
public String getPassword() {
return password.getText().toString();
}
@Override
public void loginSuccess() {
Toast.makeText(this, "登錄成功", Toast.LENGTH_SHORT).show();
}
@Override
public void loginError(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void showProgress() {
dialog = ProgressDialog.show(this, " ", "加載中", true, true);
}
@Override
public void hideProgress() {
dialog.dismiss();
}
@Override
public void showMessage(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
看上面的代碼,很簡潔,沒有任何邏輯代碼,這就是我們想要的效果,接下來就開始寫Presenter層了,邏輯代碼也是寫到這一層。
/**
* Created by yuankundong on 2016/03/03.
*/
public interface LoginPresenter {
void actionLogin();
}
實現:
/**
* Created by yuankundong on 2016/03/03.
*/
public class LoginPresenterImpl implements LoginPresenter, OnLoginListener {
LoginView loginView;
LoginModel loginModel;
public LoginPresenterImpl(LoginView loginView) {
this.loginView = loginView;
this.loginModel = new LoginModelImpl();
}
@Override
public void actionLogin() {
String username = loginView.getUsername();
String password = loginView.getPassword();
if (TextUtils.isEmpty(username)) {
loginView.showMessage("用戶名爲空");
return;
}
if (TextUtils.isEmpty(password)) {
loginView.showMessage("密碼爲空");
return;
}
loginView.showProgress();
loginModel.toLogin(username,password,this);
}
@Override
public void OnSuccess(UserBean userBean) {
loginView.hideProgress();
loginView.loginSuccess();
}
@Override
public void OnError(int code, String msg) {
loginView.hideProgress();
loginView.loginError(msg);
}
}
在LoginPresenterImpl 類中,我們實例了LoginView 和LoginModel 。登錄的邏輯就是先判斷用戶名和密碼是否爲空,不爲空再調用Model裏的登錄方法,進行登錄。因爲Model層把登錄反饋傳給OnLoginListener 這個回調接口,所有我們在這裏實現這個接口,就能獲取登錄後的反饋。OnSuccess和OnError分別是登錄成功和失敗後,通知View層應該顯示什麼。
這樣,我們用MVP寫出來的登錄就完成了,總體思想就是,Model層獲取的數據交給Presenter處理,Presenter進行處理的過程中,會通知頁面View層進行相應變化,View層本身不進行邏輯處理,MVP架構及時各有分工。
整體工程裏面還有個別不涉及本文內容的我沒有詳述,源碼地址,點擊這裏下載