本文主要是結(jié)合自己對MVP的理解搭建了符合自身業(yè)務(wù)場景的MVP框架。
關(guān)于MVP
- M(Model)負(fù)責(zé)數(shù)據(jù)的請求准谚,解析谦炒,過濾等數(shù)據(jù)操作贯莺。
- V(View)負(fù)責(zé)處理UI,通常以
Activity
Fragment
的形式出現(xiàn)宁改。 - P(Presenter)View Model中間件缕探,交互的橋梁。
上圖引用自此
MVP的好處
- 分離了UI邏輯和業(yè)務(wù)邏輯还蹲,降低了耦合爹耗。
- Activity只處理UI相關(guān)操作,代碼變得更加簡潔谜喊。
- UI邏輯和業(yè)務(wù)邏輯抽象到接口中鲸沮,方便閱讀及維護(hù)。
- 把業(yè)務(wù)邏輯抽到Presenter中去锅论,避免復(fù)雜業(yè)務(wù)邏輯造成的內(nèi)存泄漏讼溺。
具體實(shí)現(xiàn)
1.對View進(jìn)行封裝
一般情況下,做數(shù)據(jù)請求都有顯示加載框最易、請求成功怒坯、請求失敗等操作,我們把這些共有的功能封裝到BaseView中藻懒。
public interface IBaseView {
/**
* 顯示加載框
*/
void showLoading();
/**
* 隱藏加載框
*/
void dismissLoading();
/**
* 空數(shù)據(jù)
*
* @param tag TAG
*/
void onEmpty(Object tag);
/**
* 錯(cuò)誤數(shù)據(jù)
*
* @param tag TAG
* @param errorMsg 錯(cuò)誤信息
*/
void onError(Object tag, String errorMsg);
/**
* 上下文
*
* @return context
*/
Context getContext();
}
2.對Presenter封裝
為了避免持有View的Presenter做耗時(shí)操作而引起的內(nèi)存泄漏剔猿,我們的Presenter應(yīng)該和宿主Activity/Fragment
同創(chuàng)建、同銷毀嬉荆。
public abstract class BasePresenter{
...
/**
* 綁定View
*/
public void attachView(View view) {
this.view=view;
}
/**
* 解綁View
*/
public void detachView() {
this.view=null;
}
...
}
public abstract class MvpActivity extends BaseActivity implements View{
...
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//創(chuàng)建present
presenter = createPresenter();
if (presenter != null) {
presenter.attachView(this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (presenter != null) {
presenter.detachView();
presenter = null;
}
}
...
}
如上操作固然可以解決內(nèi)存泄漏問題归敬,但又會(huì)引發(fā)行的問題:
場景:用戶打開商品列表頁,網(wǎng)絡(luò)不好獲取數(shù)據(jù)比較慢,用戶離開該頁面汪茧,繼續(xù)瀏覽其他頁面椅亚,突然應(yīng)用崩潰了。
分析問題:
在用戶打開頁面的時(shí)候綁定P和V舱污,離開頁面的時(shí)候解綁P和V呀舔,當(dāng)耗時(shí)操作完成調(diào)用V更新界面,此時(shí)由于P和V已經(jīng)解綁V處于null扩灯,調(diào)用V的更新頁面方法就會(huì)引起空指針異常媚赖。
解決問題:
使用動(dòng)態(tài)代理對View做弱引用蔚万,完整的BasePresenter如下:
public abstract class BasePresenter<M extends IBaseModel, V extends IBaseView> {
private V mProxyView;
private M module;
private WeakReference<V> weakReference;
/**
* 綁定View
*/
@SuppressWarnings("unchecked")
public void attachView(V view) {
weakReference = new WeakReference<>(view);
mProxyView = (V) Proxy.newProxyInstance(
view.getClass().getClassLoader(),
view.getClass().getInterfaces(),
new MvpViewHandler(weakReference.get()));
if (this.module == null) {
this.module = createModule();
}
}
/**
* 解綁View
*/
public void detachView() {
this.module = null;
if (isViewAttached()) {
weakReference.clear();
weakReference = null;
}
}
/**
* 是否與View建立連接
*/
protected boolean isViewAttached() {
return weakReference != null && weakReference.get() != null;
}
protected V getView() {
return mProxyView;
}
protected M getModule() {
return module;
}
protected Context getContext() {
return getView().getContext();
}
protected void showLoading() {
getView().showLoading();
}
protected void dismissLoading() {
getView().dismissLoading();
}
/**
* 通過該方法創(chuàng)建Module
*/
protected abstract M createModule();
/**
* 初始化方法
*/
public abstract void start();
/**
* View代理類 防止 頁面關(guān)閉P異步操作調(diào)用V 方法 空指針問題
*/
private class MvpViewHandler implements InvocationHandler {
private IBaseView mvpView;
MvpViewHandler(IBaseView mvpView) {
this.mvpView = mvpView;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果V層沒被銷毀, 執(zhí)行V層的方法.
if (isViewAttached()) {
return method.invoke(mvpView, args);
} //P層不需要關(guān)注V層的返回值
return null;
}
}
}
3.契約類Contract的出現(xiàn)
通過契約類來管理Model鳞疲、View、Presenter的所有接口赏寇,這樣使得Presenter和View有哪些功能一目了然捻撑,維護(hù)起來也方便磨隘,同時(shí)使得View與Presenter一一對應(yīng),并有效地減少類的數(shù)目布讹。
public interface Contract {
interface Model extends IBaseModel {
void login(User user, ResponseCallback callback);
}
interface View extends IBaseView {
User getUserInfo();
void loginSuccess(User user);
}
interface Presenter {
void login();
}
}
4.對Activity
的封裝琳拭,Fragment
封裝同理
public abstract class BaseMvpActivity<P extends BasePresenter> extends Activity implements IBaseView {
protected P presenter;
@SuppressWarnings("unchecked")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//創(chuàng)建present
presenter = createPresenter();
if (presenter != null) {
presenter.attachView(this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (presenter != null) {
presenter.detachView();
presenter = null;
}
}
@Override
public void showLoading() {
if (loadingDialog != null && !loadingDialog.isShowing()) {
loadingDialog.show();
}
}
@Override
public void dismissLoading() {
if (loadingDialog != null && loadingDialog.isShowing()) {
loadingDialog.dismiss();
}
}
@Override
public void onEmpty(Object tag) {
}
@Override
public void onError(Object tag, String errorMsg) {
}
@Override
public Context getContext() {
return mContext;
}
/**
* 創(chuàng)建Presenter
*/
protected abstract P createPresenter();
}
通過泛型規(guī)定Presenter,并且暴露抽象方法createPresenter()給子類來創(chuàng)建Presenter描验,基類實(shí)現(xiàn)BaseView中的公共方法白嘁,減少子類代碼的冗余。
5.登錄案例
契約類
public interface LoginContract {
interface Model extends IBaseModel {
/**
* 登錄
*
* @param user 用戶信息
* @param callback 回調(diào)
*/
void login(User user, ResponseCallback callback);
}
interface View extends IBaseView {
/**
* 返回用戶信息
*/
User getUserInfo();
/**
* 登錄成功
*/
void loginSuccess(User user);
}
interface Presenter {
/**
* 登錄
*/
void login();
}
}
Model
public class LoginModel implements LoginContract.Model {
@Override
public void login(User user, ResponseCallback callback) {
if (user == null) {
callback.onError("", (Throwable) new Exception("用戶信息為空"));
}
RequestParam param = new RequestParam();
param.addParameter("username", user.getUsername());
param.addParameter("password", user.getPassword());
HttpUtils.getInstance()
.postRequest(Api.LOGIN, param, callback);
}
}
Presenter
public class LoginPresenter extends BasePresenter<LoginContract.Model, LoginContract.View>
implements LoginContract.Presenter {
@Override
public void login() {
if (isViewAttached()) {
getView().showLoading();
getModule().login(getView().getUserInfo(), new OnResultObjectCallBack<User>() {
@Override
public void onSuccess(boolean success, int code, String msg, Object tag, User response) {
if (code == 0 && response != null) {
getView().loginSuccess(response);
} else {
getView().onError(tag, msg);
}
}
@Override
public void onFailure(Object tag, Exception e) {
getView().onError(tag, msg);
}
@Override
public void onCompleted() {
getView().dismissLoading();
}
});
}
}
@Override
protected LoginModel createModule() {
return new LoginModel();
}
@Override
public void start() { }
}
登錄Activity
public class LoginActivity extends ActionBarActivity<LoginPresenter> implements LoginContract.View {
@BindView(R2.id.edt_name)
EditText edtName;
@BindView(R2.id.edt_pwd)
EditText edtPwd;
@BindView(R2.id.ob_login)
ObserverButton obLogin;
@BindView(R2.id.ob_register)
TextView obRegister;
@Override
protected int getLayoutId() {
return R.layout.user_activity_login;
}
@Override
protected void initView() {
setTitleText("登錄");
obLogin.observer(edtName, edtPwd);
}
@OnClick({R2.id.ob_login, R2.id.ob_register})
public void onViewClicked(View view) {
int i = view.getId();
if (i == R.id.ob_login) {
presenter.login();
} else if (i == R.id.ob_register) {
ActivityToActivity.toActivity(mContext, RegisterActivity.class);
}
}
@Override
public void loginSuccess(User user) {
UserInfoUtils.saveUser(user);
EventBusUtils.sendEvent(new Event(EventAction.EVENT_LOGIN_SUCCESS));
finish();
}
@Override
public void onError(Object tag, String errorMsg) {
super.onError(tag, errorMsg);
ToastUtils.showToast(mContext, errorMsg);
}
@Override
protected LoginPresenter createPresenter() {
return new LoginPresenter();
}
@Override
public void onEventBus(Event event) {
super.onEventBus(event);
if (TextUtils.equals(event.getAction(), EventAction.EVENT_REGISTER_SUCCESS)) {
finish();
}
}
@Override
protected boolean regEvent() {
return true;
}
@Override
public User getUserInfo() {
return new User(edtName.getText().toString().trim(), edtPwd.getText().toString().trim());
}
}
總結(jié)
無論是MVP還是MCV或者M(jìn)VVM膘流,都是為把業(yè)務(wù)與UI分離絮缅,避免在一個(gè)Activity里把所有的操作都塞進(jìn)來,各自在各自的領(lǐng)域工作呼股。每個(gè)人對于層級結(jié)構(gòu)都有不同的理解和看法耕魄,封裝一個(gè)適合自己、適合當(dāng)下業(yè)務(wù)場景的框架才是最重要的彭谁。
最后放上Demo地址吸奴,共同學(xué)習(xí),有什么不好的地方缠局,歡迎大家指出则奥!
參考文獻(xiàn)
Google爸爸的案例
JesseBraveMan的 Android MVP架構(gòu)搭建
淺談Android中的MVP架構(gòu)
深入講解Android MVP框架