android项目重构之mvp
- 一直想用mvp架构写个项目,但是一直没有机会,最近项目上事情比较少,就看了看自己之前写的代码,发现项目虽然小,但是代码看起来很混乱,耦合性太高,于是产生了重构项目的念头。
在网上找了一些资料,再看了google官方的mvp架构demo之后,我开始了重构之旅。
众所周知,mvp分别就是model、view 、presenter ,model就是数据模型,view就是界面显示,而presenter就是view和model交互的中介。
google官方的demo通过contract接口把view和presenter的接口写到了一起,这样更方便项目的维护。
不多说,上代码,由于mvp的架构每个页面都差不多,这里就以项目里的login页面为例吧。
原本的代码是这样的:
- public class LoginActivity extends BaseActivity
- implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
- 。。。省略一部分绑定view代码
- private boolean isAgree = true;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_login);
- initView();
- initEvent();
- }
- /**
- * 事件处理
- */
- private void initEvent() {
- 设置点击事件的逻辑,省略。。。
- }
- private void initView() {
- ButterKnife.bind(this);
- 。。。。这里主要是一些页面初始化的逻辑
- Intent intent = getIntent();
- boolean haveUpdate = intent.getBooleanExtra(Constants.HAVE_UPDATE, false);
- // 判断是否需要更新
- if (haveUpdate) {
- String versionName = SharePreferenceUtils.getString(UIUtils.getContext(), UserConfig.versionName);
- if (!TextUtils.isEmpty(versionName)) {
- // 显示更新对话框
- showUpdateDialog();
- }
- }
- }
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.tv_agree_protocol:
- // 跳转到webview
- Intent intent = new Intent(this, WebViewActivity.class);
- intent.putExtra(Constants.TITLE, getString(R.string.qtz_protocol));
- startActivity(intent);
- break;
- case R.id.btn_login:
- login();
- break;
- case R.id.tv_login_bottom:
- registerFromPc();
- break;
- }
- }
- /**
- * 访问网络注册帐号
- */
- private void registerFromPc() {
- Intent intent = new Intent();
- intent.setAction("android.intent.action.VIEW");
- Uri content_url = Uri.parse(getString(R.string.register_from_net));
- intent.setData(content_url);
- startActivity(intent);
- }
- /**
- * 登录的逻辑
- */
- private void login() {</span><span style="font-size: 18px;">
- </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个数字
- final String phone = login_phone.getText().toString().trim();
- if (phone.length() != 11) {
- UIUtils.showToastShort(getString(R.string.plese_enter_acc));
- return;
- }
- // 6-15位字母或数字,区分大小写; 校验提示:“请输入密码”
- String reg = "^[a-zA-Z0-9]{6,15}$";
- final String password = login_password.getText().toString().trim();
- if (!password.matches(reg)) {
- UIUtils.showToastShort(getString(R.string.plece_enter_pwd));
- return;
- }
- // 2.必须勾选同意服务协议
- if (!isAgree) {
- UIUtils.showToastShort(getString(R.string.read_and_agree_protocol));
- return;
- }
- // 联网校验 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接口
- public interface LoginContract {
- interface View {
- /**
- * 跳转到主页的方法
- */
- void goToMain();
- }
- interface Presenter {
- }
- }
这里的presenter没有任何抽象方法,是由于项目中登录的入口不止一个,所以把这段逻辑抽取到了basepresenter里面去,以便代码的复用
下面看看basepresenter里面的逻辑
- public abstract class BasePresenter<T> {
- private static final String TAG = "updateDialog";
- protected Reference<T> mViewRef; // view 的弱引用
- /**
- * 将presenter与view建立关联
- *
- * @param view
- */
- public void attachView(T view) {
- mViewRef = new WeakReference<>(view);
- }
- /**
- * 获取view的方法
- *
- * @return 当前关联的view
- */
- public T getView() {
- return mViewRef.get();
- }
- /**
- * 判断是否关联的方法
- *
- * @return
- */
- public boolean isAttach() {
- return mViewRef != null && mViewRef.get() != null;
- }
- /**
- * 解除关联的方法
- */
- public void detachView() {
- if (mViewRef != null) {
- mViewRef.clear();
- mViewRef = null;
- }
- }
- /**
- * 联网校验密码
- *
- * @param phone 电话
- * @param password 密码
- */
- public void checkPasswordOnline(final String phone, final String password) {
- // 联网校验
- 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(UIUtils.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(UIUtils.getContext(), userBean.object.alias, null);
- }
- // 校验成功之后保存密码
- SharePreferenceUtils.saveAccAndPwd(phone, password);
- onLoginSuccess();
- } else {
- // 3.失败提示:“账号或密码不正确”
- UIUtils.showToastShort(userBean.resultDesc);
- onLoginFail();
- }
- }
- }, new BaseProtocol.OnErrorCallBack() {
- @Override
- public void onError() {
- UIUtils.showToastShort(UIUtils.getString(R.string.fail_to_connect));
- onLoginFail();
- }
- });
- }
- /**
- * 登录失败回调
- */
- public void onLoginFail() {
- }
- /**
- * 登录成功回调
- */
- public void onLoginSuccess() {
- }
- }
basepresenter里面的逻辑主要是防止presenter做一些耗时操作,而presenter长期持有activity实例,当activity被销毁时,就会导致内存泄漏。
在baseactivity里将presenter与activity的生命周期绑定起来,就可以解决这个问题。代码如下
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mPresenter = createPresenter();
- if (mPresenter != null) {
- mPresenter.attachView((T) this);
- }
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (mPresenter != null) {
- mPresenter.detachView();
- }
- }
其余的代码不重要就省略了。
接下来写一个loginpresenter继承basepresenter实现logincontract.presenter ,下面看代码
- public class LoginPresenter extends BasePresenter<LoginContract.View> implements LoginContract.Presenter {
- @Override
- public void onLoginSuccess() {
- super.onLoginSuccess();
- downloadImg();
- getView().goToMain();
- }
- @Override
- public void onLoginFail() {
- super.onLoginFail();
- }
- public static String SHOP_ICON = "shop_icon.png";
- /**
- * 下载图片的方法
- */
- private void downloadImg() {
- String shopImg = SharePreferenceUtils.getString(UIUtils.getContext(), UserConfig.shopImg);
- new ImageProtocol().downloadImg(shopImg, SHOP_ICON);
- }
- }
- 由于在base里写了一些逻辑,这里代码比较简单,最后再让loginactivity实现logincontract.view,改完之后,是不是发现现在代码看起来舒服多了?
- public class LoginActivity extends BaseActivity<LoginContract.View>
- implements View.OnClickListener, CompoundButton.OnCheckedChangeListener, LoginContract.View {
- private static final String TAG = "LoginActivity";
- 。。。省略一部分绑定view的代码
- private boolean isAgree = true;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_login);
- initView();
- initEvent();
- }
- @Override
- protected BasePresenter<LoginContract.View> createPresenter() {
- return new LoginPresenter();
- }
- /**
- * 事件处理
- */
- private void initEvent() {
- 。。。设置点击事件的逻辑,这里省略
- }
- private void initView() {
- ButterKnife.bind(this);
- 。。。省略部分初始化页面逻辑
- // 帐号密码回显
- SharePreferenceUtils.getAccAndPwd(login_phone, login_password);
- Intent intent = getIntent();
- boolean haveUpdate = intent.getBooleanExtra(Constants.HAVE_UPDATE, false);
- // 判断是否需要更新
- if (haveUpdate) {
- String versionName = SharePreferenceUtils.getString(UIUtils.getContext(), UserConfig.versionName);
- if (!TextUtils.isEmpty(versionName)) {
- // 显示更新对话框
- showUpdateDialog();
- }
- }
- }
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.tv_agree_protocol:
- // 跳转到webview
- Intent intent = new Intent(this, WebViewActivity.class);
- intent.putExtra(Constants.TITLE, getString(R.string.qtz_protocol));
- startActivity(intent);
- break;
- case R.id.btn_login:
- login();
- break;
- case R.id.tv_login_bottom:
- registerFromPc();
- break;
- }
- }
- /**
- * 访问网络注册帐号
- */
- private void registerFromPc() {
- Intent intent = new Intent();
- intent.setAction("android.intent.action.VIEW");
- Uri content_url = Uri.parse(getString(R.string.register_from_net));
- intent.setData(content_url);
- startActivity(intent);
- }
- /**
- * 登录的逻辑
- */
- private void login() {
- 。。。省略部分本地校验密码的逻辑
- // 校验密码的网络请求写到了presenter里面,这里的逻辑就变简单了
- mPresenter.checkPasswordOnline(phone, password);
- }
- /**
- * 跳转到主界面的方法
- */
- @Override
- public 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;
- }
- }
- }
到这里就完成了第一个mvp的页面,不过这里由于页面的逻辑本身就不多,所以看不出这个架构的好处,如果页面逻辑多一些,网络请求多一些,mvp的优势就很明显了,而且model和view完全没有交互,项目维护起来也方便了。
不过任何事情都是有两面性的,mvp架构带给我们的弊端就是类的膨胀。。。每一个界面就会有一个contract类,一个presenter类,当界面多的时候难以想象。
到这吧,道行比较浅,只能写成这样了,第一次写博客,请各位前辈多多指教!

浙公网安备 33010602011771号