一、扯淡
MVP是在MVC架構的基礎上演化來的一種架構,在MVC中Activity屬於C,因此業務邏輯也都是在Activity中實現,造成Activity十分臃腫,而且使用不當容易造成內存泄漏,比如在Activity中有線程在執行耗時操作,然後點back鍵,銷燬Activity,此時因爲有線程存在,線程依然持有Activity的引用,造成Activity無法正常銷燬,從而內存泄漏,MVP的主要優點是將業務層與Activity層解耦。
- Model 業務邏輯和實體模型
- View 對應於Activity,負責View的繪製以及與用戶交互
- Presenter 負責完成View於Model間的交互
- Contract 這個根據具體個人習慣,起一個契約作用
這裏以網絡請求登錄模塊做一個簡單的Demo,用戶點擊登錄按鈕,這一步是在View層做的,也就是Activity,然後我們去調用Presenter的登錄業務接口,Presenter不需要自己去完成網絡請求,而是繼續調用Model層去具體實現網絡請求,Model從網絡拿到數據後封裝成JavaBean,然後將請求結果(Javabean或者請求異常消息)告訴給Presenter,Presenter拿到結果後再次回調View層去更新UI,這就是一個簡單的MVP流程,這裏也很明顯,Model層和View層是不直接溝通的,而是通過Presenter這個橋樑。
二、基類
BaseModel
model層就是真正業務邏輯實現的地方,當業務邏輯實現完之後需要通知Presenter層,因此BaseModel需要拿到Presenter的引用,所以在BaseModel構造方法中需要將Presenter傳進來,然後還需要契約方法,讓子類去實現具體的業務邏輯。
//接受P層給他的需求(基類)
public abstract class BaseModel<P extends BasePresenter, CONTRACT> {
protected P p;
//業務結束通過,通過presenter調用契約、合同 void responseResult(T t);
public BaseModel(P p) {
this.p = p;
}
protected abstract CONTRACT getContract();
}
BasePresenter
Presenter層首先在其構造方法中創建Model,業務邏輯不同是不同的Model,因此我們生命抽象方法getModel交給子類去創建具體的Model,另外我們需要持有View層(Activity)的引用,但是直接在構造方法中把Activity傳進來這種強引用很容易因爲代碼不當而內存泄漏,因此採用弱引用是比較好的一種方式。
public abstract class BasePresenter<M extends BaseModel, V extends BaseView, CONTRACT> {
protected M m;
//綁定View層的弱引用
private WeakReference<V> vWeakReference;
public BasePresenter() {
m = getModel();
}
public void bindView(BaseView v) {
vWeakReference = (WeakReference<V>) new WeakReference<>(v);
}
public void unbindView() {
if (vWeakReference != null) {
vWeakReference.clear();
vWeakReference = null;
System.gc();
}
}
//獲取View ,P->V
public V getView() {
if (vWeakReference != null) {
return vWeakReference.get();
}
return null;
}
public abstract M getModel();
//獲取子類具體契約
public abstract CONTRACT getContract();
}
BaseView
View層就是Activity,View層需要和Presenter層進行通訊,我們在onCreate方法中創建Presenter,因爲但是具體的Presenter要根據業務邏輯不同而不同,因此我們讓子類去實現,我們定義抽象方法getPresenter,並在onCreate中調用,這樣View層就持有了Presenter的一個對象,同時我們調用presenter層的bindView將自身傳給Presenter層,Presenter通過弱引用就持有了View層的引用,最後別忘了在onDestroy中釋放Presenter層,解綁爲了避免內存泄漏。
public abstract class BaseView<P extends BasePresenter, CONTRACT> extends AppCompatActivity {
protected P p;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
p = getPresenter();
p.bindView(this);
}
//P層做什麼需求
public abstract CONTRACT getContract();
//從子類中獲取具體的契約
public abstract P getPresenter();
//如果Presenter層出現異常,需要告知View層
public void error(Exception e) {
}
@Override
protected void onDestroy() {
p.unbindView();
super.onDestroy();
}
}
三、具體實現類
契約類LoginContract
契約類就是將M、V、P層協商的共同業務封裝成接口,讓代碼邏輯更清晰。Model層就是一個請求登錄的最終實現。View層(Activity)則是處理登錄的具體結果,刷新UI。Presenter層既要去調用Model層的具體登錄,又要講回調結果通知給View層。
public interface LoginContract {
interface Model {
//Model層子類完成方法的具體實現
void executeLogin(String name, String psw) throws Exception;
}
interface View<T extends BaseEntity> {
//項目中往往是以Javabean
void handleResult(T t);
}
interface Presenter<T extends BaseEntity> {
//登錄請求(接受View層的指令,可以自己去做,也可以讓model層去做)
void requestLogin(String name, String psw);
//結果響應(接受Model層處理的結果,通知View層去刷新)
void responseResult(T t);
}
}
LoginModel
Model類需要實現具體具體登錄的契約。
public class LoginModel extends BaseModel<LoginPresenter, LoginContract.Model> {
public LoginModel(LoginPresenter loginPresenter) {
super(loginPresenter);
}
@Override
protected LoginContract.Model getContract() {
return new LoginContract.Model() {
@Override
public void executeLogin(String name, String psw) throws Exception {
if ("123".equals(name) && "123".equals(psw)) {
p.getContract().responseResult(new UserInfo("123", "123"));
} else {
p.getContract().responseResult(null);
}
}
};
}
}
LoginPresenter
首先實現基類的getModel方法,創建LoginModel,並將自己傳給LoginModel。然後實現自己的契約,在請求登錄的實現中調用Model層的登錄方法,並在拿到Model層的結果後調用View層的處理方法。
public class LoginPresenter extends BasePresenter<LoginModel, LoginActivity, LoginContract.Presenter> {
@Override
public LoginModel getModel() {
return new LoginModel(this);
}
@Override
public LoginContract.Presenter getContract() {
return new LoginContract.Presenter<UserInfo>() {
@Override
public void requestLogin(String name, String psw) {
try {
//3種風格,P層很極端,要不不做事製作轉發,要不拼命一個人幹活,Google的MVPDemo中在P端做了所有的業務邏輯
m.getContract().executeLogin(name, psw);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void responseResult(UserInfo userInfo) {
getView().getContract().handleResult(userInfo);
}
};
}
}
LoginActivity
LoginActivity層首先實現父類的抽象方法getPresenter創建LoginPresenter,然後在按鈕點擊後調用P層的登錄接口,並在請求結果返回的回調中刷新UI。
public class LoginActivity extends BaseView<LoginPresenter, LoginContract.View> {
private EditText etCount;
private EditText etPwd;
private Button btnLongin;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
initListener();
}
private void initView() {
etCount = findViewById(R.id.et_count);
etPwd = findViewById(R.id.et_password);
btnLongin = findViewById(R.id.btn_login);
}
private void initListener() {
btnLongin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = etCount.getText().toString();
String psw = etPwd.getText().toString();
p.getContract().requestLogin(name, psw);
}
});
}
@Override
public LoginContract.View getContract() {
return new LoginContract.View<UserInfo>() {
@Override
public void handleResult(UserInfo userInfo) {
if (userInfo != null) {
Toast.makeText(LoginActivity.this, userInfo.toString(), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(LoginActivity.this, "登錄失敗", Toast.LENGTH_SHORT).show();
}
}
};
}
@Override
public LoginPresenter getPresenter() {
return new LoginPresenter();
}
}
最後補充上兩個JavaBean的代碼,比較簡單
public class BaseEntity {
private int code;
private boolean success;
private String error;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
public class UserInfo extends BaseEntity {
private String name;
private String company;
public UserInfo() {
}
public UserInfo(String name, String company) {
this.name = name;
this.company = company;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", company='" + company + '\'' +
'}';
}
}
一個簡易版的MVP Demo就完成了。