从零搭建Android-MVP框架、基类封装以及简单使用
首先我们先了解一下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框架的基本使用。
注意上面的例子使用的是面向接口的方法来实现的,也就是由契约类来规定相应的行为。
最后附上相应的操作截图:
如有错误的地方欢迎指导。