搭建屬於自己的Android MVP 框架
本文主要是結合自己對MVP的理解搭建了符合自身業務場景的MVP框架。
AndroidModuleDemo" target="_blank" rel="nofollow,noindex">先放一個Demo地址,文章末尾也有
關於MVP
- M(Model)負責資料的請求,解析,過濾等資料操作。
- V(View)負責處理UI,通常以
Activity
Fragment
的形式出現。 - P(Presenter)View Model中介軟體,互動的橋樑。
圖片.png
MVP的好處
- 分離了UI邏輯和業務邏輯,降低了耦合。
- Activity只處理UI相關操作,程式碼變得更加簡潔。
- UI邏輯和業務邏輯抽象到介面中,方便閱讀及維護。
- 把業務邏輯抽到Presenter中去,避免複雜業務邏輯造成的記憶體洩漏。
具體實現
1.對View進行封裝
一般情況下,做資料請求都有顯示載入框、請求成功、請求失敗等操作,我們把這些共有的功能封裝到BaseView中。
public interface IBaseView { /** * 顯示載入框 */ void showLoading(); /** * 隱藏載入框 */ void dismissLoading(); /** * 空資料 * * @param tag TAG */ void onEmpty(Object tag); /** * 錯誤資料 * * @param tagTAG * @param errorMsg 錯誤資訊 */ void onError(Object tag, String errorMsg); /** * 上下文 * * @return context */ Context getContext(); }
2.對Presenter封裝
為了避免持有View的Presenter做耗時操作而引起的記憶體洩漏,我們的Presenter應該和宿主 Activity/Fragment
同建立、同銷燬。
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); //建立present presenter = createPresenter(); if (presenter != null) { presenter.attachView(this); } } @Override protected void onDestroy() { super.onDestroy(); if (presenter != null) { presenter.detachView(); presenter = null; } } ... }
如上操作固然可以解決記憶體洩漏問題,但又會引發行的問題:
場景:使用者開啟商品列表頁,網路不好獲取資料比較慢,使用者離開該頁面,繼續瀏覽其他頁面,突然應用崩潰了。
分析問題:
在使用者開啟頁面的時候繫結P和V,離開頁面的時候解綁P和V,當耗時操作完成呼叫V更新介面,此時由於P和V已經解綁V處於null,呼叫V的更新頁面方法就會引起空指標異常。
解決問題:
使用動態代理對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(); } /** * 通過該方法建立Module */ protected abstract M createModule(); /** * 初始化方法 */ public abstract void start(); /** * View代理類防止 頁面關閉P非同步操作呼叫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層沒被銷燬, 執行V層的方法. if (isViewAttached()) { return method.invoke(mvpView, args); } //P層不需要關注V層的返回值 return null; } } }
3.契約類Contract的出現
通過契約類來管理Model、View、Presenter的所有介面,這樣使得Presenter和View有哪些功能一目瞭然,維護起來也方便,同時使得View與Presenter一一對應,並有效地減少類的數目。
public interfaceContract { 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); //建立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; } /** * 建立Presenter */ protected abstract P createPresenter(); }
通過泛型規定Presenter,並且暴露抽象方法createPresenter()給子類來建立Presenter,是在BaseView中的公共方法,減少子類程式碼的冗餘。
5.登入案例

類結構圖.jpg
契約類
public interface LoginContract { interface Model extends IBaseModel { /** * 登入 * * @param user使用者資訊 * @param callback 回撥 */ 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()); } }
總結
無論是MVP還是MCV或者MVVM,都是為把業務與UI分離,避免在一個Activity裡把所有的操作都塞進來,各自在各自的領域工作。每個人對於層級結構都有不同的理解和看法,封裝一個適合自己、適合當下業務場景的框架才是最重要的。
最後放上 Demo地址 ,共同學習,有什麼不好的地方,歡迎大家指出!