首先我们先了解一下MVP的原理以及流程: MVP分三层:View、Presenter、Model view层不直接与model交互,而是通过presenter来与model交互,view负责数据展示,发起请求,而presenter则负责将view的请求转发给model,然后有model来处理相应的数据请求等操作。
MVP的优点:前后端分离,降低耦合度,逻辑分明,思路清晰等
MVP缺点:很明显的就是类的数量变多了
在Android中,对于Activity并没有明确的说它是属于View还是Controller的范畴,Activity既有View的性质,也具有Controller的性质,所以导致MVC在Android中很难明确分工使用,导致Activity很重。而且MVC中View会与Model直接交互,所以Activity与Model的耦合性很高,当后期维护时,稍有变动,可能Model、Activity、XML都会跟着改变,工作量很大,成本太高。
而MVP与MVC最大的不同之处是,MVP将M与V分隔开来,通过P交互,这样在Android中,就可以明确的把Activity当作View处理,虽然可能还有一点逻辑在其中,但是已经无伤大雅;View和Model不直接交互,当View有变动或者Model有变动时,不会相互影响,有太大变动,,耦合性低,对于后期维护来说,特别是项目越来越庞大时,可以很快的理清项目结构,找到需要修改的地方,大大的缩短了工作量。而且,因为View与Model分离的缘故,Model可以单独进行单元测试。
好了,上面说了那么多,我们还是来点实际的吧,下面是本人在项目中对MVP的处理方式,有不同见解的,欢迎大家提出。
首先对我们的基类进行封装,之后所有的子类都要继承自基类:
封装一个超级父类,传入一个泛型:CONTRACT
package com.org.huanjianmvp.Base; /** * 整个架构的父类、超级父类 * 泛型 CONTRACT 面向的是接口类 * 子类必须实现方法接口getContract(),具体实现哪些看传过来的方法接口中有哪些方法 * Created by Administrator on 2020/8/19. */ public abstract class SuperBase<CONTRACT> { /*【子类要实现的方法,具体实现由传过来的接口中的方法决定】*/ public abstract CONTRACT getContract(); }View层封装:Activity 或者 Fragment
package com.org.huanjianmvp.Base; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; import com.org.huanjianmvp.BootApplication; import com.org.huanjianmvp.Login; import cn.pedant.SweetAlert.SweetAlertDialog; /** * 只关注P层,不与V层进行交互 * 继承自AppCompatActivity的基类 * Created by Administrator on 2020/8/19. */ public abstract class BaseActivity<P extends BaseActivityPresenter, CONTRACT> extends AppCompatActivity implements View.OnClickListener{ public P mPresenter; private Long startTime = System.currentTimeMillis(); //第一次点击时间 private Long clickTime = System.currentTimeMillis(); //每一次点击都重置该时间,判断两次时间间隔 private Intent intent ; //超时跳转页面 private SweetAlertDialog alertDialog; //超时提示框 public BootApplication application; //程序页面管理 public abstract CONTRACT getContract(); //方法接口,实现接口中的方法 public abstract P createPresenter(); //实例化P层 public abstract int getViewID(); //拿到布局视图 public abstract void initViewUI(); //初始化组件 public abstract void initListener(); //初始化监听 public abstract void destroy(); //销毁时执行方法 public abstract void initDatas(); //初始化数据 //处理相应错误信息 public abstract<ERROR extends Object> void responseError(ERROR error , Throwable throwable); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.requestWindowFeature(Window.FEATURE_NO_TITLE); this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(getViewID()); //引入布局文件 this.mPresenter = createPresenter(); //实例化P层 application = (BootApplication)getApplication(); mPresenter.attach(this); //绑定 initViewUI(); //初始化UI组件 initListener(); //初始化监听 initDatas(); //初始化数据 } @Override protected void onDestroy() { destroy(); //销毁时触发 mPresenter.detach(); //解绑 if (alertDialog!=null){ alertDialog.dismiss(); } super.onDestroy(); } }Model层基类封装:
package com.org.huanjianmvp.Base; /** * 拿到P层,把结果给P层 * 将由子类传泛型 CONTRACT 到父类中 * 子类传入到父类中的泛型 CONTRACT 一般为方法接口,具体子类要实现的方法由传入的方法接口决定 * Created by Administrator on 2020/8/19. */ public abstract class BaseActivityModel<P extends BaseActivityPresenter, CONTRACT> extends SuperBase<CONTRACT> { public P presenter; public BaseActivityModel(P mPresenter){ this.presenter = mPresenter; //拿到P层 } }Presenter层基类封装:
package com.org.huanjianmvp.Base; import java.lang.ref.WeakReference; /** * 拿到V层和M层,View必须是继承BaseActivity * Presenter关联M和V,M层和V层不能有交互,通过中间层P来进行交互 * Created by Administrator on 2020/8/19. */ public abstract class BaseActivityPresenter<V extends BaseActivity , M extends BaseActivityModel, CONTRACT> extends SuperBase<CONTRACT> { public WeakReference<V> weakView; //弱化引用 public M mModel; public abstract M createModel(); //创建一个model,具体model由子类传入决定 //实例化model,建立与M得关系 public BaseActivityPresenter(){ mModel = createModel(); } //建立与V的关系,绑定 public void attach(V view){ weakView = new WeakReference<>(view); } //解绑 public void detach(){ if (weakView.get()!=null){ weakView.get().finish(); } if (weakView != null){ weakView.clear(); weakView=null; } } }到这里基类的封装基本完成,还有个契约类由是基类的子类接口来实现,规定了子类的行为
我这里具体举个简单的登录例子:
登录的契约类接口:接口中规范了相应的行为:
package com.org.huanjianmvp.Contrast; import android.content.Context; import java.util.Map; import cn.pedant.SweetAlert.SweetAlertDialog; /** * 契约接口,规定行为,管理M层、V层、P层的抽象方法 * 降低耦合度 * Created by Administrator on 2020/8/19. */ public interface LoginContrast { /**【 Model 层的方法接口】**/ interface Model{ //登录验证 void validateLogin(String userName , String passWord) throws Exception; //处理退出请求 void exitAction(SweetAlertDialog alert); //版本号获取 void versionAction(Context context); //权限申请处理 void permissionAction(Context context); //token刷新处理 void tokenAction(); } /**【 View 层和 Presenter 层的方法接口】**/ interface ViewAndPresenter{ //登录请求 void requestLogin(String userName , String passWord); //响应请求结果 void responseResult(String msg); //请求退出 void requestExit(SweetAlertDialog alert); //响应请求退出 void responseExit(Boolean isExit); //请求版本号 void requestVersion(Context context); //响应版本号 void responseVersion(Map<String,String> map); //权限申请 void requestPermission(Context context); //请求刷新token void requestToken(); } }登录布局文件:我这里是直接拉项目中的代码过来,建议做个简单的按钮和输入框即可
<?xml version="1.0" encoding="utf-8"?> <!-- 登录页面布局 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" android:background="@drawable/blue_button_background" tools:context="com.org.huanjianmvp.Login"> <com.beardedhen.androidbootstrap.BootstrapButton android:layout_width="match_parent" android:layout_height="wrap_content" android:text="新环检软件" android:enabled="false" app:bootstrapBrand="info" app:bootstrapSize="xl" app:buttonMode="regular" app:roundedCorners="false" app:showOutline="false" /> <ImageView android:layout_width="150dp" android:layout_height="150dp" android:src="@drawable/login_icon"/> <com.rengwuxian.materialedittext.MaterialEditText android:id="@+id/EditTextUserName" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入登录名" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" app:met_floatingLabel="normal" app:met_floatingLabelText="请在此输入登录名:" app:met_helperText="登录名:" app:met_clearButton="true" app:met_primaryColor="@color/bootstrap_brand_primary" /> <com.rengwuxian.materialedittext.MaterialEditText android:id="@+id/EditTextPassWord" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入登录密码" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" app:met_floatingLabel="normal" app:met_floatingLabelText="请在此输入登录密码:" app:met_helperText="登录密码:" app:met_clearButton="true" app:met_primaryColor="@color/bootstrap_brand_primary" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal"> <com.beardedhen.androidbootstrap.BootstrapButton android:id="@+id/exit" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:text="退出" app:bootstrapBrand="warning" app:bootstrapSize="xl" app:buttonMode="regular" app:roundedCorners="true" app:showOutline="true" /> <com.beardedhen.androidbootstrap.BootstrapButton android:id="@+id/setting" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:text="设置" app:bootstrapBrand="info" app:bootstrapSize="xl" app:buttonMode="regular" app:roundedCorners="true" app:showOutline="true" /> <com.beardedhen.androidbootstrap.BootstrapButton android:id="@+id/login" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:text="登录" app:bootstrapBrand="success" app:bootstrapSize="xl" app:buttonMode="regular" app:roundedCorners="true" app:showOutline="true" /> </LinearLayout> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="bottom"> <TextView android:id="@+id/appVersionText" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_gravity="center_horizontal" android:gravity="center" android:text="版本号:" android:textColor="@color/bootstrap_gray_dark" android:textSize="15sp" /> </RelativeLayout> </LinearLayout>登录的View层,也就是我们的LoginActivity:
package com.org.huanjianmvp; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.view.KeyEvent; import android.view.View; import android.widget.TextView; import com.beardedhen.androidbootstrap.BootstrapButton; import com.org.huanjianmvp.Activity.ListDatas; import com.org.huanjianmvp.Activity.Setting; import com.org.huanjianmvp.Base.BaseActivity; import com.org.huanjianmvp.Contrast.LoginContrast; import com.org.huanjianmvp.Presenter.LoginActivityPresenter; import com.org.huanjianmvp.Utils.AlertDialogUtils; import com.rengwuxian.materialedittext.MaterialEditText; import java.util.Map; import cn.pedant.SweetAlert.SweetAlertDialog; /** * 仅负责展示处理,不涉及逻辑以及数据处理 * * **/ public class Login extends BaseActivity<LoginActivityPresenter,LoginContrast.ViewAndPresenter> { private String username , password; private MaterialEditText userName , passWord; private BootstrapButton btnLogin , btnExit , btnSetting; private AlertDialogUtils dialogUtils; private SweetAlertDialog alert; private SharedPreferences preferences; private SharedPreferences.Editor editor; private TextView appVersionText; /**【实现契约接口中的方法】**/ @Override public LoginContrast.ViewAndPresenter getContract() { return new LoginContrast.ViewAndPresenter() { /**【发起登录请求】**/ @Override public void requestLogin(String userName, String passWord) { mPresenter.getContract().requestLogin(userName,passWord); } /**【响应请求结果】**/ @Override public void responseResult(String msg) { if (msg != null){ if (msg.equals("登录成功")){ Intent intent = new Intent(Login.this, ListDatas.class); startActivity(intent); Login.this.finish(); }else{ dialogUtils.AlertTitle(msg,"warning"); } } } @Override public void requestExit(SweetAlertDialog alert) { mPresenter.getContract().requestExit(alert); } @Override public void responseExit(Boolean isExit) { if (isExit){ Login.this.finish(); } } @Override public void requestVersion(Context context) { mPresenter.getContract().requestVersion(context); } @Override public void responseVersion(Map<String,String> map) { //dialogUtils.AlertTitle(map.get("deviceID"),"success"); appVersionText.setText(map.get("versionName")); } @Override public void requestPermission(Context context) { mPresenter.getContract().requestPermission(Login.this); } @Override public void requestToken() { mPresenter.getContract().requestToken(); } }; } /**【创建实例化一个Presenter】**/ @Override public LoginActivityPresenter createPresenter() { return new LoginActivityPresenter(); } /**【拿到、引用布局文件】**/ @Override public int getViewID() { return R.layout.activity_login; } /**【初始化UI组件】**/ @Override public void initViewUI() { application.addActivity(Login.this); appVersionText = findViewById(R.id.appVersionText); userName = findViewById(R.id.EditTextUserName); passWord = findViewById(R.id.EditTextPassWord); btnLogin = findViewById(R.id.login); btnExit = findViewById(R.id.exit); btnSetting = findViewById(R.id.setting); dialogUtils = new AlertDialogUtils(this); alert = dialogUtils.getAlertDialog("warning"); alert.setCancelable(false); preferences = getSharedPreferences("userName",0); editor = preferences.edit(); } /**【初始化监听事件】**/ @Override public void initListener() { btnLogin.setOnClickListener(this); btnExit.setOnClickListener(this); btnSetting.setOnClickListener(this); } /**【销毁时执行方法】**/ @Override public void destroy() { if (dialogUtils != null){ dialogUtils.dismissDialog(); } if (alert != null){ alert.dismiss(); } dialogUtils = null; alert = null; } /**【初始化数据】**/ @Override public void initDatas() { userName.setText(preferences.getString("userName","")); getContract().requestVersion(Login.this); getContract().requestPermission(Login.this); } /**【报错处理】**/ @Override public void responseError(Object o, Throwable throwable) { dialogUtils.AlertTitle(throwable.getMessage(),"error"); } /**【点击执行事件】**/ @Override public void onClick(View view) { username = userName.getText().toString().trim(); password = passWord.getText().toString().trim(); switch (view.getId()){ case R.id.login: editor.putString("userName",username); editor.commit(); /**【交由契约类处理,契约类交给P层,P层交给M层】**/ getContract().requestLogin(username,password); break; case R.id.exit: getContract().requestToken(); getContract().requestExit(alert); break; case R.id.setting: Intent intent = new Intent(Login.this, Setting.class); startActivity(intent); break; } } /**【重写返回键】**/ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { getContract().requestExit(alert); return true; } return super.onKeyDown(keyCode, event); } }Presenter层:负责转交给model层来处理
package com.org.huanjianmvp.Presenter; import android.content.Context; import com.org.huanjianmvp.Base.BaseActivityPresenter; import com.org.huanjianmvp.Contrast.LoginContrast; import com.org.huanjianmvp.Login; import com.org.huanjianmvp.Model.LoginActivityModel; import java.util.Map; import cn.pedant.SweetAlert.SweetAlertDialog; /** * Created by Administrator on 2020/8/19. */ public class LoginActivityPresenter extends BaseActivityPresenter<Login,LoginActivityModel,LoginContrast.ViewAndPresenter> { @Override public LoginContrast.ViewAndPresenter getContract() { return new LoginContrast.ViewAndPresenter() { @Override public void requestLogin(String userName, String passWord) { try { mModel.getContract().validateLogin(userName,passWord); } catch (Exception e) { weakView.get().responseError(userName,e); e.printStackTrace(); } } @Override public void responseResult(String msg) { weakView.get().getContract().responseResult(msg); } @Override public void requestExit(SweetAlertDialog alert) { mModel.getContract().exitAction(alert); } @Override public void responseExit(Boolean isExit) { weakView.get().getContract().responseExit(isExit); } @Override public void requestVersion(Context context) { mModel.getContract().versionAction(context); } @Override public void responseVersion(Map<String,String> map) { weakView.get().getContract().responseVersion(map); } @Override public void requestPermission(Context context) { mModel.getContract().permissionAction(context); } @Override public void requestToken() { try { mModel.getContract().tokenAction(); }catch (Exception e){ weakView.get().responseError(e,e); } } }; } @Override public LoginActivityModel createModel() { return new LoginActivityModel(this); } }Model层:具体的逻辑操作在这里执行
package com.org.huanjianmvp.Model; import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.provider.Settings; import android.support.v4.app.ActivityCompat; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; import com.org.huanjianmvp.Base.BaseActivityModel; import com.org.huanjianmvp.Contrast.LoginContrast; import com.org.huanjianmvp.Domain.token; import com.org.huanjianmvp.Internet.ObserverManager; import com.org.huanjianmvp.Internet.RetrofitManager; import com.org.huanjianmvp.Presenter.LoginActivityPresenter; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import cn.pedant.SweetAlert.SweetAlertDialog; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; /** * 在这里实现数据处理 * Created by Administrator on 2020/8/19. */ public class LoginActivityModel extends BaseActivityModel<LoginActivityPresenter,LoginContrast.Model> { public LoginActivityModel(LoginActivityPresenter mPresenter) { super(mPresenter); } @Override public LoginContrast.Model getContract() { return new LoginContrast.Model() { /**【登录验证】**/ @Override public void validateLogin(String userName, String passWord) throws Exception { if (userName.equals("admin")){ presenter.getContract().responseResult("登录成功"); }else{ presenter.getContract().responseResult("登录失败"); } } /**【请求退出程序】**/ @Override public void exitAction(final SweetAlertDialog alert) { if (alert != null){ alert.setTitle("是否退出当前软件?"); alert.setConfirmText("确定"); alert.setCancelText("取消"); alert.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() { @Override public void onClick(SweetAlertDialog sweetAlertDialog) { presenter.getContract().responseExit(true); } }); alert.setCancelClickListener(new SweetAlertDialog.OnSweetClickListener() { @Override public void onClick(SweetAlertDialog sweetAlertDialog) { alert.dismiss(); } }); alert.show(); } } /**【获取程序版本号、手机识别标识】**/ @Override public void versionAction(Context context) { Map<String,String> map = new HashMap<>(); try { /**【获取程序版本】**/ PackageManager manager = context.getPackageManager(); PackageInfo info = manager.getPackageInfo(context.getPackageName(),0); String versionName = info.versionName; if (versionName != null){ versionName = "版本号:20.08.28.20(v" + versionName + ")"; }else { versionName = "版本号:20.08.28.20(v3.3.3)"; } map.put("versionName",versionName); /**【获取设备识别标识】**/ TelephonyManager telephony = (TelephonyManager) context.getSystemService(context.TELEPHONY_SERVICE); @SuppressLint("MissingPermission") String deviceID = telephony.getDeviceId(); if (TextUtils.isEmpty(deviceID)){ deviceID = Settings.System.getString(context.getContentResolver(),Settings.Secure.ANDROID_ID); } map.put("deviceID",deviceID); presenter.getContract().responseVersion(map); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } /**【权限申请】**/ @Override public void permissionAction(Context context) { String [] permissions = new String[] { Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, }; ActivityCompat.requestPermissions((Activity) context,permissions,100); } /**【刷新token请求】**/ @Override public void tokenAction() { Observable<token> observable = RetrofitManager.getRetrofitManager().getApiService() .requestToken("refresh_token","web_client","web_secret","7f30f196-d063-4678-afd8-a31644620d03"); observable.debounce(5000, TimeUnit.MILLISECONDS) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new ObserverManager<token>() { @Override public void onSuccess(token token) { token.tokenShow(); } @Override public void onFail(Throwable throwable) { Log.e("错误信息",throwable.toString()); } @Override public void onFinish() { Log.e("请求信息","请求完成!"); } @Override public void onDisposable(Disposable disposable) { } }); } }; } }以上就是基类的封装以及MVP框架的基本使用。
注意上面的例子使用的是面向接口的方法来实现的,也就是由契约类来规定相应的行为。
最后附上相应的操作截图: 如有错误的地方欢迎指导。