android项目重构之mvp

  1. 一直想用mvp架构写个项目,但是一直没有机会,最近项目上事情比较少,就看了看自己之前写的代码,发现项目虽然小,但是代码看起来很混乱,耦合性太高,于是产生了重构项目的念头。  
在网上找了一些资料,再看了google官方的mvp架构demo之后,我开始了重构之旅。
众所周知,mvp分别就是model、view 、presenter ,model就是数据模型,view就是界面显示,而presenter就是view和model交互的中介。
google官方的demo通过contract接口把view和presenter的接口写到了一起,这样更方便项目的维护。
不多说,上代码,由于mvp的架构每个页面都差不多,这里就以项目里的login页面为例吧。
原本的代码是这样的:
[java] view plain copy
 
  1. public class LoginActivity extends BaseActivity   
  2. implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {  
  3. 。。。省略一部分绑定view代码  
  4.   
  5.     private boolean isAgree = true;  
  6.   
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.activity_login);  
  11.         initView();  
  12.         initEvent();  
  13.     }  
  14.   
  15.     /** 
  16.      * 事件处理 
  17.      */  
  18.     private void initEvent() {  
  19.        设置点击事件的逻辑,省略。。。  
  20.     }  
  21.   
  22.     private void initView() {  
  23.         ButterKnife.bind(this);  
  24.        。。。。这里主要是一些页面初始化的逻辑  
  25.         Intent intent = getIntent();  
  26.         boolean haveUpdate = intent.getBooleanExtra(Constants.HAVE_UPDATE, false);  
  27.         // 判断是否需要更新  
  28.         if (haveUpdate) {  
  29.             String versionName = SharePreferenceUtils.getString(UIUtils.getContext(), UserConfig.versionName);  
  30.             if (!TextUtils.isEmpty(versionName)) {  
  31.                 // 显示更新对话框  
  32.                 showUpdateDialog();  
  33.             }  
  34.         }  
  35.   
  36.     }  
  37.   
  38.     @Override  
  39.     public void onClick(View v) {  
  40.         switch (v.getId()) {  
  41.             case R.id.tv_agree_protocol:  
  42.                 // 跳转到webview  
  43.                 Intent intent = new Intent(this, WebViewActivity.class);  
  44.                 intent.putExtra(Constants.TITLE, getString(R.string.qtz_protocol));  
  45.                 startActivity(intent);  
  46.                 break;  
  47.             case R.id.btn_login:  
  48.                 login();  
  49.                 break;  
  50.             case R.id.tv_login_bottom:  
  51.                 registerFromPc();  
  52.                 break;  
  53.         }  
  54.     }  
  55.   
  56.     /** 
  57.      * 访问网络注册帐号 
  58.      */  
  59.     private void registerFromPc() {  
  60.         Intent intent = new Intent();  
  61.         intent.setAction("android.intent.action.VIEW");  
  62.         Uri content_url = Uri.parse(getString(R.string.register_from_net));  
  63.         intent.setData(content_url);  
  64.         startActivity(intent);  
  65.     }  
  66.   
  67.     /** 
  68.      * 登录的逻辑 
  69.      */  
  70.     private void login() {</span><span style="font-size: 18px;">  
  71. </span><span style="font-size:14px;">//     <span style="font-family: Arial, Helvetica, sans-serif;">1.</span><span style="font-family: Arial, Helvetica, sans-serif;">前端校验输入符合格式,才能请求后台验证,</span></span><pre><span style="font-size:14px;">        // 手机号限制为11个数字  
  72.         final String phone = login_phone.getText().toString().trim();  
  73.         if (phone.length() != 11) {  
  74.             UIUtils.showToastShort(getString(R.string.plese_enter_acc));  
  75.             return;  
  76.         }  
  77. //        6-15位字母或数字,区分大小写; 校验提示:“请输入密码”  
  78.         String reg = "^[a-zA-Z0-9]{6,15}$";  
  79.   
  80.         final String password = login_password.getText().toString().trim();  
  81.         if (!password.matches(reg)) {  
  82.             UIUtils.showToastShort(getString(R.string.plece_enter_pwd));  
  83.             return;  
  84.         }  
  85.   
  86. //        2.必须勾选同意服务协议  
  87.         if (!isAgree) {  
  88.             UIUtils.showToastShort(getString(R.string.read_and_agree_protocol));  
  89.             return;  
  90.         }  
[java] view plain copy
 
  1. // 联网校验 new LoginProtocol(new LoginRequestBean(phone, password, 3)) .getDataByPost(new BaseProtocol.OnSuccessCallBack<UserBean>() { @Override public void onSuccess(UserBean userBean) { if (userBean.resultCode == Constants.SUCCESS_CODE) { UIUtils.showToastShort(getString(R.string.login_success)); if (userBean.object != null) { SharePreferenceUtils.putLong(UIUtils.getContext(), UserConfig.businessId, userBean.object.id); SharePreferenceUtils.putString(UIUtils.getContext(), UserConfig.miAlias, userBean.object.alias); SharePreferenceUtils.putString(UIUtils.getContext(), UserConfig.shopImg, userBean.object.shopInfo.shopImg); SharePreferenceUtils.putDouble(UIUtils.getContext(),UserConfig.businessLat,userBean.object.shopInfo.addrLat); SharePreferenceUtils.putDouble(UIUtils.getContext(),UserConfig.businessLng,userBean.object.shopInfo.addrLng); if (userBean.object.shopInfo != null) { SharePreferenceUtils.putLong(UIUtils.getContext(),UserConfig.shopId,userBean.object.shopInfo.id); } Logger.e(TAG, "alias:" + userBean.object.alias); // 小米推送设置别名 MiPushClient.setAlias(LoginActivity.this, userBean.object.alias, null); } // 校验成功之后保存密码 SharePreferenceUtils.saveAccAndPwd(phone, password); // 下载店铺头像 new ImageProtocol(LoginActivity.this).downloadImg(userBean.object.shopInfo.shopImg, SHOP_ICON); // 跳转到主界面 goToMain(); } else { // 3.失败提示:“账号或密码不正确” UIUtils.showToastShort(userBean.resultDesc); } } }, new BaseProtocol.OnErrorCallBack() { @Override public void onError() { UIUtils.showToastShort(getString(R.string.fail_to_connect)); } }); } /** * 跳转到主界面的方法 */ private void goToMain() { Intent intent = new Intent(UIUtils.getContext(), MainActivity.class); startActivity(intent); finish(); } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { switch (buttonView.getId()) { case R.id.cb_agree_protocol: // 是否同意协议 cb_agree_protocol.setChecked(!isAgree); isAgree = !isAgree; break; } } public static String SHOP_ICON = "shop_icon.png";}  




       看到这个login方法里面都逻辑估计都吐了吧,我也快吐了,这tm是谁写的代码,这么烂,好吧,就是我写的。。。
没关系,人是需要进步的,代码也需要一点点进步。接下来,就让他们重生吧!
首先定义一个logincontract接口
[java] view plain copy
 
  1. public interface LoginContract {  
  2.   
  3.     interface View {  
  4.         /** 
  5.          * 跳转到主页的方法 
  6.          */  
  7.         void goToMain();  
  8.     }  
  9.   
  10.     interface Presenter {  
  11.     }  
  12. }  
这里的presenter没有任何抽象方法,是由于项目中登录的入口不止一个,所以把这段逻辑抽取到了basepresenter里面去,以便代码的复用
下面看看basepresenter里面的逻辑
[java] view plain copy
 
  1. public abstract class BasePresenter<T> {  
  2.   
  3.     private static final String TAG = "updateDialog";  
  4.     protected Reference<T> mViewRef; // view 的弱引用  
  5.   
  6.     /** 
  7.      * 将presenter与view建立关联 
  8.      * 
  9.      * @param view 
  10.      */  
  11.     public void attachView(T view) {  
  12.         mViewRef = new WeakReference<>(view);  
  13.     }  
  14.   
  15.     /** 
  16.      * 获取view的方法 
  17.      * 
  18.      * @return 当前关联的view 
  19.      */  
  20.     public T getView() {  
  21.         return mViewRef.get();  
  22.     }  
  23.   
  24.     /** 
  25.      * 判断是否关联的方法 
  26.      * 
  27.      * @return 
  28.      */  
  29.     public boolean isAttach() {  
  30.         return mViewRef != null && mViewRef.get() != null;  
  31.     }  
  32.   
  33.     /** 
  34.      * 解除关联的方法 
  35.      */  
  36.     public void detachView() {  
  37.         if (mViewRef != null) {  
  38.             mViewRef.clear();  
  39.             mViewRef = null;  
  40.         }  
  41.     }  
  42.   
  43.     /** 
  44.      * 联网校验密码 
  45.      * 
  46.      * @param phone    电话 
  47.      * @param password 密码 
  48.      */  
  49.     public void checkPasswordOnline(final String phone, final String password) {  
  50.         // 联网校验  
  51.         new LoginProtocol(new LoginRequestBean(phone, password, 3))  
  52.                 .getDataByPost(new BaseProtocol.OnSuccessCallBack<UserBean>() {  
  53.                     @Override  
  54.                     public void onSuccess(UserBean userBean) {  
  55.                         if (userBean.resultCode == Constants.SUCCESS_CODE) {  
  56.                             UIUtils.showToastShort(UIUtils.getString(R.string.login_success));  
  57.                             if (userBean.object != null) {  
  58.                                 SharePreferenceUtils.putLong(UIUtils.getContext(), UserConfig.businessId, userBean.object.id);  
  59.                                 SharePreferenceUtils.putString(UIUtils.getContext(), UserConfig.miAlias, userBean.object.alias);  
  60.                                 SharePreferenceUtils.putString(UIUtils.getContext(), UserConfig.shopImg, userBean.object.shopInfo.shopImg);  
  61.                                 SharePreferenceUtils.putDouble(UIUtils.getContext(), UserConfig.businessLat, userBean.object.shopInfo.addrLat);  
  62.                                 SharePreferenceUtils.putDouble(UIUtils.getContext(), UserConfig.businessLng, userBean.object.shopInfo.addrLng);  
  63.                                 if (userBean.object.shopInfo != null) {  
  64.                                     SharePreferenceUtils.putLong(UIUtils.getContext(), UserConfig.shopId, userBean.object.shopInfo.id);  
  65.                                 }  
  66.                                 Logger.e(TAG, "alias:" + userBean.object.alias);  
  67.                                 // 小米推送设置别名  
  68.                                 MiPushClient.setAlias(UIUtils.getContext(), userBean.object.alias, null);  
  69.                             }  
  70.                             // 校验成功之后保存密码  
  71.                             SharePreferenceUtils.saveAccAndPwd(phone, password);  
  72.   
  73.                             onLoginSuccess();  
  74.   
  75.                         } else {  
  76.                             // 3.失败提示:“账号或密码不正确”  
  77.                             UIUtils.showToastShort(userBean.resultDesc);  
  78.                             onLoginFail();  
  79.                         }  
  80.                     }  
  81.                 }, new BaseProtocol.OnErrorCallBack() {  
  82.                     @Override  
  83.                     public void onError() {  
  84.                         UIUtils.showToastShort(UIUtils.getString(R.string.fail_to_connect));  
  85.                         onLoginFail();  
  86.                     }  
  87.                 });  
  88.     }  
  89.   
  90.     /** 
  91.      * 登录失败回调 
  92.      */  
  93.     public void onLoginFail() {  
  94.   
  95.     }  
  96.   
  97.     /** 
  98.      * 登录成功回调 
  99.      */  
  100.     public void onLoginSuccess() {  
  101.   
  102.     }  
  103.   
  104. }  


basepresenter里面的逻辑主要是防止presenter做一些耗时操作,而presenter长期持有activity实例,当activity被销毁时,就会导致内存泄漏。
在baseactivity里将presenter与activity的生命周期绑定起来,就可以解决这个问题。代码如下
[java] view plain copy
 
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     super.onCreate(savedInstanceState);  
  4.     mPresenter = createPresenter();  
  5.     if (mPresenter != null) {  
  6.         mPresenter.attachView((T) this);  
  7.     }  
  8. }  
  9.   
  10. @Override  
  11. protected void onDestroy() {  
  12.     super.onDestroy();  
  13.     if (mPresenter != null) {  
  14.         mPresenter.detachView();  
  15.     }  
  16. }  


其余的代码不重要就省略了。
接下来写一个loginpresenter继承basepresenter实现logincontract.presenter ,下面看代码
[java] view plain copy
 
  1. public class LoginPresenter extends BasePresenter<LoginContract.View> implements LoginContract.Presenter {  
  2.   
  3.     @Override  
  4.     public void onLoginSuccess() {  
  5.         super.onLoginSuccess();  
  6.         downloadImg();  
  7.         getView().goToMain();  
  8.     }  
  9.   
  10.     @Override  
  11.     public void onLoginFail() {  
  12.         super.onLoginFail();  
  13.   
  14.     }  
  15.   
  16.     public static String SHOP_ICON = "shop_icon.png";  
  17.   
  18.     /** 
  19.      * 下载图片的方法 
  20.      */  
  21.     private void downloadImg() {  
  22.         String shopImg = SharePreferenceUtils.getString(UIUtils.getContext(), UserConfig.shopImg);  
  23.         new ImageProtocol().downloadImg(shopImg, SHOP_ICON);  
  24.     }  
  25. }  
  26. 由于在base里写了一些逻辑,这里代码比较简单,最后再让loginactivity实现logincontract.view,改完之后,是不是发现现在代码看起来舒服多了?  
  27. public class LoginActivity extends BaseActivity<LoginContract.View>  
  28.         implements View.OnClickListener, CompoundButton.OnCheckedChangeListener, LoginContract.View {  
  29.   
  30.     private static final String TAG = "LoginActivity";  
  31.     。。。省略一部分绑定view的代码  
  32.     private boolean isAgree = true;  
  33.   
  34.     @Override  
  35.     protected void onCreate(Bundle savedInstanceState) {  
  36.         super.onCreate(savedInstanceState);  
  37.         setContentView(R.layout.activity_login);  
  38.         initView();  
  39.         initEvent();  
  40.     }  
  41.   
  42.     @Override  
  43.     protected BasePresenter<LoginContract.View> createPresenter() {  
  44.         return new LoginPresenter();  
  45.     }  
  46.   
  47.     /** 
  48.      * 事件处理 
  49.      */  
  50.     private void initEvent() {  
  51.         。。。设置点击事件的逻辑,这里省略  
  52.   
  53.     }  
  54.   
  55.     private void initView() {  
  56.         ButterKnife.bind(this);  
  57.         。。。省略部分初始化页面逻辑  
  58.   
  59.         // 帐号密码回显  
  60.         SharePreferenceUtils.getAccAndPwd(login_phone, login_password);  
  61.         Intent intent = getIntent();  
  62.         boolean haveUpdate = intent.getBooleanExtra(Constants.HAVE_UPDATE, false);  
  63.         // 判断是否需要更新  
  64.         if (haveUpdate) {  
  65.             String versionName = SharePreferenceUtils.getString(UIUtils.getContext(), UserConfig.versionName);  
  66.             if (!TextUtils.isEmpty(versionName)) {  
  67.                 // 显示更新对话框  
  68.                 showUpdateDialog();  
  69.             }  
  70.         }  
  71.   
  72.     }  
  73.   
  74.     @Override  
  75.     public void onClick(View v) {  
  76.         switch (v.getId()) {  
  77.             case R.id.tv_agree_protocol:  
  78.                 // 跳转到webview  
  79.                 Intent intent = new Intent(this, WebViewActivity.class);  
  80.                 intent.putExtra(Constants.TITLE, getString(R.string.qtz_protocol));  
  81.                 startActivity(intent);  
  82.                 break;  
  83.             case R.id.btn_login:  
  84.                 login();  
  85.                 break;  
  86.             case R.id.tv_login_bottom:  
  87.                 registerFromPc();  
  88.                 break;  
  89.         }  
  90.     }  
  91.   
  92.     /** 
  93.      * 访问网络注册帐号 
  94.      */  
  95.     private void registerFromPc() {  
  96.         Intent intent = new Intent();  
  97.         intent.setAction("android.intent.action.VIEW");  
  98.         Uri content_url = Uri.parse(getString(R.string.register_from_net));  
  99.         intent.setData(content_url);  
  100.         startActivity(intent);  
  101.     }  
  102.   
  103.     /** 
  104.      * 登录的逻辑 
  105.      */  
  106.     private void login() {  
  107.         。。。省略部分本地校验密码的逻辑  
  108.         // 校验密码的网络请求写到了presenter里面,这里的逻辑就变简单了  
  109.         mPresenter.checkPasswordOnline(phone, password);  
  110.     }  
  111.   
  112.     /** 
  113.      * 跳转到主界面的方法 
  114.      */  
  115.     @Override  
  116.     public void goToMain() {  
  117.         Intent intent = new Intent(UIUtils.getContext(), MainActivity.class);  
  118.         startActivity(intent);  
  119.         finish();  
  120.     }  
  121.   
  122.     @Override  
  123.     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {  
  124.         switch (buttonView.getId()) {  
  125.             case R.id.cb_agree_protocol:  
  126.                 // 是否同意协议  
  127.                 cb_agree_protocol.setChecked(!isAgree);  
  128.                 isAgree = !isAgree;  
  129.                 break;  
  130.         }  
  131.     }  
  132. }  


到这里就完成了第一个mvp的页面,不过这里由于页面的逻辑本身就不多,所以看不出这个架构的好处,如果页面逻辑多一些,网络请求多一些,mvp的优势就很明显了,而且model和view完全没有交互,项目维护起来也方便了。
不过任何事情都是有两面性的,mvp架构带给我们的弊端就是类的膨胀。。。每一个界面就会有一个contract类,一个presenter类,当界面多的时候难以想象。
到这吧,道行比较浅,只能写成这样了,第一次写博客,请各位前辈多多指教!
posted @ 2017-04-29 14:23  天涯海角路  阅读(262)  评论(0)    收藏  举报