感谢泡在网上的日子用户@JesseBraveMan的两篇关于MVP架构的博文:Android MVP架构搭建和Android MVP升级路(二)时尚版。
Android中MVP架构的理论与使用,完整相关项目网站:基于MVP的特色河蟹大赛评比管理系统客户端。
用架构和不用架构的区别是非常明显的,我们需要多多利用Java的接口、继承、实现等面向对象思想来让我们越来越大的Android项目更易开发和维护。
apply plugin: 'com.android.application'
//apply plugin: 'com.jakewharton.butterknife'
android {
compileSdkVersion 27
defaultConfig {
applicationId "top.spencer.crabscore"
minSdkVersion 23
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath true
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
dataBinding {
enabled true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.0.2'
implementation 'com.android.support:support-v4:27.1.1'
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
// https://mvnrepository.com/artifact/cn.hutool/hutool-core
implementation group: 'cn.hutool', name: 'hutool-core', version: '4.1.18'
// https://mvnrepository.com/artifact/com.alibaba/fastjson
implementation group: 'com.alibaba', name: 'fastjson', version: '1.2.51'
// https://mvnrepository.com/artifact/commons-codec/commons-codec
implementation group: 'commons-codec', name: 'commons-codec', version: '1.11'
//DataBinding
annotationProcessor 'com.android.databinding:compiler:3.1.2'
//ButterKnife
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
// https://mvnrepository.com/artifact/org.projectlombok/lombok
compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.2'
// https://mvnrepository.com/artifact/com.qiniu/qiniu-android-sdk
implementation group: 'com.qiniu', name: 'qiniu-android-sdk', version: '7.3.13'
// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
// https://mvnrepository.com/artifact/com.github.bumptech.glide/glide
implementation group: 'com.github.bumptech.glide', name: 'glide', version: '4.8.0'
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.11.0'
// https://mvnrepository.com/artifact/com.google.code.gson/gson
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
}
在MVP 架构中跟MVC类似的是同样也分为三层。
Activity 和Fragment 视为View层,负责处理 UI。
Presenter 为业务处理层,既能调用UI逻辑,又能请求数据,该层为纯Java类,不涉及任何Android API。
Model 层中包含着具体的数据请求,数据源。
三层之间调用顺序为view->presenter->model,为了调用安全着想不可反向调用!不可跨级调用!
那Model 层如何反馈给Presenter 层的呢?Presenter 又是如何操控View 层呢?看图!
上图中说明了低层的不会直接给上一层做反馈,而是通过 View 、 Callback 为上级做出了反馈,这样就解决了请求数据与更新界面的异步操作。上图中 View 和 Callback 都是以接口的形式存在的,其中 View 是经典 MVP 架构中定义的,Callback 是我自己加的。
View 中定义了 Activity 的具体操作,主要是些将请求到的数据在界面中更新之类的。
Callback 中定义了请求数据时反馈的各种状态:成功、失败、异常等。
我就用我自己的话来复述一下这张图吧。
在MVP架构中:
Activity和Fragment只负责UI初始化(组件绑定、组件适配器绑定、组件监听器绑定等等)和UI逻辑变化(这些UI变化的代码都应该是实现图中那个View的接口);
Presenter里写调用数据业务逻辑(网络请求等)和UI逻辑的代码,注意都是**“调用”,而不是具体实现**。
UI逻辑的具体实现在Activity和Fragment里面。
数据业务逻辑的具体实现在Model层里面。
Model层通过CallBack将数据返回给Presenter层。
Presenter层再通过View接口把数据传给Activity和Fragment。
Activity和Fragment拿到数据以后执行相关UI逻辑。
简单地总结就是:只要是和Android组件的实质性业务代码都在Activity和Fragment里,Presenter会通过View来调用。
至于Android MVP升级路(二)时尚版里说的我觉得就是利用Java的反射机制的Model层的优雅实现,。
根据以上思想和相关参考作出以下Demo。
这个example
我懒得截图了呃,代码绝对没bug,都调试过了。
单击登陆会有相关MVP整个流程的调用。
MVP架构的代码量很大,但方便维护和开发,大量公用的东西也可以不用写了。还在不断地学习和使用中,有了心得会继续撰文。
package top.spencer.crabscore.activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.*;
import butterknife.*;
import com.alibaba.fastjson.JSONObject;
import top.spencer.crabscore.R;
import top.spencer.crabscore.base.BaseActivity;
import top.spencer.crabscore.common.CommonConstant;
import top.spencer.crabscore.presenter.LoginPresenter;
import top.spencer.crabscore.util.SharedPreferencesUtil;
import top.spencer.crabscore.view.LoginView;
import static android.content.ContentValues.TAG;
/**
* @author spencercjh
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class LoginActivity extends BaseActivity implements LoginView {
private LoginPresenter loginPresenter;
@BindView(R.id.edit_username)
EditText username;
@BindView(R.id.edit_password)
EditText password;
@BindView(R.id.button_login)
Button login;
@BindView(R.id.button_register)
Button register;
@BindView(R.id.button_forget_password)
Button forgetPassword;
@BindView(R.id.spinner_role)
Spinner roleSpinner;
@BindArray(R.array.roles)
String[] roles;
@BindView(R.id.checkbox_remember_password)
CheckBox rememberPassword;
@BindView(R.id.checkbox_auto_login)
CheckBox autoLogin;
private int roleChoice = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_login);
ButterKnife.bind(this);
loginPresenter = new LoginPresenter();
loginPresenter.attachView(this);
SharedPreferencesUtil.getInstance(getContext(), "LOGIN");
initSpinner();
readSharedPreferences();
}
@Override
protected void onDestroy() {
super.onDestroy();
loginPresenter.detachView();
}
@Override
public void initSpinner() {
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, roles);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
roleSpinner.setAdapter(adapter);
roleSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
Log.d(TAG, "用户组改变");
showToast("用户组改变");
roleChoice = pos;
if (pos == CommonConstant.USER_TYPE_ADMIN) {
SharedPreferencesUtil.putData(CommonConstant.ADMINISTRATOR, true);
} else if (pos == CommonConstant.USER_TYPE_JUDGE) {
SharedPreferencesUtil.putData(CommonConstant.JUDGE, true);
} else if (pos == CommonConstant.USER_TYPE_STAFF) {
SharedPreferencesUtil.putData(CommonConstant.STAFF, true);
} else if (pos == CommonConstant.USER_TYPE_COMPANY) {
SharedPreferencesUtil.putData(CommonConstant.COMPANY, true);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
Log.d(TAG, "用户组未改变");
SharedPreferencesUtil.putData(CommonConstant.ADMINISTRATOR, false);
SharedPreferencesUtil.putData(CommonConstant.JUDGE, false);
SharedPreferencesUtil.putData(CommonConstant.STAFF, false);
SharedPreferencesUtil.putData(CommonConstant.COMPANY, false);
}
});
}
@Override
public void readSharedPreferences() {
//读取SharedPreferences中的用户组信息
if (SharedPreferencesUtil.getData(CommonConstant.ADMINISTRATOR, Boolean.FALSE).equals(true)) {
roleSpinner.setSelection(1);
roleChoice = 1;
} else if (SharedPreferencesUtil.getData(CommonConstant.JUDGE, Boolean.FALSE).equals(true)) {
roleSpinner.setSelection(2);
roleChoice = 2;
} else if (SharedPreferencesUtil.getData(CommonConstant.STAFF, Boolean.FALSE).equals(true)) {
roleSpinner.setSelection(3);
roleChoice = 3;
} else if (SharedPreferencesUtil.getData(CommonConstant.COMPANY, Boolean.FALSE).equals(true)) {
roleSpinner.setSelection(4);
roleChoice = 4;
}
//读取上一次用户选择的用户组
try {
roleChoice = (Integer) SharedPreferencesUtil.getData("ROLE_CHOICE", roleChoice);
roleSpinner.setSelection(roleChoice);
} catch (ClassCastException e) {
e.printStackTrace();
}
//记住密码
if (SharedPreferencesUtil.getData(CommonConstant.REMEMBER_PASSWORD, false).equals(true)) {
username.setText((String) SharedPreferencesUtil.getData("USERNAME", ""));
password.setText((String) SharedPreferencesUtil.getData("PASSWORD", ""));
}
//自动登录
if (SharedPreferencesUtil.getData(CommonConstant.AUTO_LOGIN, false).equals(true)) {
loginPresenter.login(username.getText().toString().trim(), password.getText().toString().trim(), String.valueOf(roleChoice));
}
}
@OnCheckedChanged(R.id.checkbox_remember_password)
public void rememberPassword(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
Log.d(TAG, "记住密码已选中");
showToast("记住密码已选中");
SharedPreferencesUtil.putData("REMEMBER_PASSWORD", true);
} else {
Log.d(TAG, "记住密码没有选中");
showToast("记住密码没有选中");
SharedPreferencesUtil.putData("REMEMBER_PASSWORD", false);
}
}
@OnCheckedChanged(R.id.checkbox_auto_login)
public void autoLogin(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
Log.d(TAG, "自动登录已选中");
showToast("自动登录已选中");
SharedPreferencesUtil.putData("AUTO_LOGIN", true);
} else {
Log.d(TAG, "自动登录未选中");
showToast("自动登录未选中");
SharedPreferencesUtil.putData("AUTO_LOGIN", false);
}
}
@OnClick(R.id.button_login)
public void login(View view) {
loginPresenter.login(username.getText().toString().trim(), password.getText().toString().trim(), String.valueOf(roleChoice));
}
@OnClick(R.id.button_register)
public void register(View view) {
Intent intent = new Intent();
//TODO 跳转注册活动
}
@OnClick(R.id.button_forget_password)
public void forgetPassword(View view) {
Intent intent = new Intent();
//TODO 跳转忘记密码活动
}
@Override
public void showData(JSONObject successData) {
Toast.makeText(getContext(), successData.getString("message"), Toast.LENGTH_SHORT).show();
SharedPreferencesUtil.putData("USERNAME", username.getText().toString().trim());
SharedPreferencesUtil.putData("PASSWORD", password.getText().toString().trim());
Intent intent = new Intent();
//TODO 跳转主活动
}
@Override
public void showFailure(JSONObject errorData) {
Toast.makeText(getContext(), errorData.getString("message"), Toast.LENGTH_SHORT).show();
}
}
<android.support.constraint.ConstraintLayout 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:background="@color/colorPrimary"
tools:context=".activity.LoginActivity">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/please_login_first"
android:textColor="@color/textcolor" android:textSize="30sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/loginPanel" app:layout_constraintVertical_bias="0.517"/>
<LinearLayout
android:id="@+id/loginPanel"
android:layout_width="327dp"
android:layout_height="348dp"
android:layout_centerHorizontal="true"
android:layout_marginBottom="8sp"
android:layout_marginLeft="8sp"
android:layout_marginRight="8sp"
android:layout_marginTop="8sp"
android:background="@drawable/background_login_div"
android:gravity="center"
android:orientation="vertical"
android:weightSum="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.417">
<EditText
android:id="@+id/edit_username"
android:layout_width="match_parent"
android:layout_height="50sp"
android:layout_marginLeft="50sp"
android:layout_marginRight="50sp"
android:layout_marginTop="15sp"
android:ems="10"
android:hint="@string/请输入您的用户名"
android:inputType="none"
android:maxLines="1"
android:selectAllOnFocus="false"
android:textSize="17sp"
android:singleLine="true"
>
<requestFocus/>
EditText>
<EditText
android:id="@+id/edit_password"
android:layout_width="match_parent"
android:layout_height="50sp"
android:layout_marginLeft="50sp"
android:layout_marginRight="50sp"
android:layout_marginTop="15sp"
android:ems="10"
android:hint="@string/请输入您的密码"
android:inputType="textPassword"
android:maxLines="1"
android:textSize="17sp"
android:singleLine="true">
<requestFocus/>
EditText>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100sp"
android:layout_weight="0.25"
android:orientation="vertical"
android:weightSum="1"
tools:ignore="InefficientWeight">
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:weightSum="1">
<CheckBox
android:id="@+id/checkbox_remember_password"
android:layout_width="100sp"
android:layout_height="40sp"
android:text="@string/记住密码"/>
<CheckBox
android:id="@+id/checkbox_auto_login"
android:layout_width="100sp"
android:layout_height="40sp"
android:text="@string/自动登录"/>
RadioGroup>
<Spinner
android:id="@+id/spinner_role"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.39"
android:autofillHints="sd"
tools:ignore="InefficientWeight,NestedWeights"
tools:targetApi="o"/>
LinearLayout>
<Button
android:id="@+id/button_login"
android:layout_width="150sp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20sp"
android:background="@drawable/background_button_div"
android:text="@string/登录"
android:textColor="@color/white"
android:textSize="20sp"/>
LinearLayout>
<Button
android:id="@+id/button_register"
android:background="#00000000"
android:layout_width="117dp"
android:layout_height="25dp"
android:layout_marginBottom="33dp"
android:layout_marginTop="8dp"
android:textColor="@color/textcolor"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.106"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
android:text="@string/新用户注册点我"/>
<Button
android:id="@+id/button_forget_password"
android:background="#00000000"
android:layout_width="77sp"
android:layout_height="25dp"
android:layout_marginBottom="33dp"
android:layout_marginTop="8dp"
android:textColor="@color/textcolor"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.893"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
android:text="@string/忘记密码"/>
<android.support.constraint.Guideline android:layout_width="wrap_content" android:layout_height="wrap_content"
android:id="@+id/guideline" app:layout_constraintGuide_begin="20dp"
android:orientation="vertical"/>
android.support.constraint.ConstraintLayout>
package top.spencer.crabscore.base;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
import top.spencer.crabscore.R;
/**
* @author spencercjh
*/
@SuppressWarnings("deprecation")
public abstract class BaseActivity extends AppCompatActivity implements BaseView {
private ProgressDialog mProgressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setCancelable(false);
}
@Override
public void showLoading() {
if (!mProgressDialog.isShowing()) {
mProgressDialog.show();
}
}
@Override
public void hideLoading() {
if (mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
}
@Override
public void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void showErr() {
showToast(getResources().getString(R.string.api_sever_error_msg));
}
@Override
public Context getContext() {
return BaseActivity.this;
}
}
package top.spencer.crabscore.view;
import top.spencer.crabscore.base.BaseView;
/**
* @author spencercjh
*/
public interface LoginView extends BaseView {
/**
* 初始化用户组Spinner
*/
void initSpinner();
/**
* 读取SharedPreferences,执行相关业务逻辑
*/
void readSharedPreferences();
}
package top.spencer.crabscore.base;
import android.content.Context;
import com.alibaba.fastjson.JSONObject;
/**
* @author spencercjh
*/
public interface BaseView {
/**
* 当数据请求成功后,调用此接口显示数据
*
* @param successData 成功数据源
*/
void showData(JSONObject successData);
/**
* 显示正在加载view
*/
void showLoading();
/**
* 关闭正在加载view
*/
void hideLoading();
/**
* 显示提示
*
* @param msg Toast message
*/
void showToast(String msg);
/**
* 显示失败
*
* @param errorData 错误数据源
*/
void showFailure(JSONObject errorData);
/**
* 显示请求错误提示
*/
void showErr();
/**
* 获取上下文
*
* @return 上下文
*/
Context getContext();
}
package top.spencer.crabscore.presenter;
import com.alibaba.fastjson.JSONObject;
import top.spencer.crabscore.base.BasePresenter;
import top.spencer.crabscore.data.Callback;
import top.spencer.crabscore.data.constant.Token;
import top.spencer.crabscore.data.model.DataModel;
import top.spencer.crabscore.view.LoginView;
/**
* @author spencercjh
*/
public class LoginPresenter extends BasePresenter<LoginView> {
/**
* 登陆请求
*
* @param username 用户名
* @param password 密码
* @param roleId 用户组(1、2、3、4)
*/
public void login(String username, String password, String roleId) {
if (!isViewAttached()) {
//如果没有View引用就不加载数据
return;
}
//显示正在加载进度条
getView().showLoading();
DataModel
// 设置请求标识token
.request(Token.API_LOGIN)
// 添加请求参数,没有则不添加`
.params(username, password, roleId)
// 注册监听回调
.execute(new Callback<JSONObject>() {
@Override
public void onSuccess(JSONObject data) {
//调用view接口显示数据,在具体的Activity中被重载
getView().showData(data);
}
@Override
public void onFailure(JSONObject data) {
//调用view接口提示失败信息,在具体的Activity中被重载
getView().showFailure(data);
}
@Override
public void onError() {
//调用view接口提示请求异常,在BaseActivity中已经实现
getView().showErr();
}
@Override
public void onComplete() {
// 隐藏正在加载进度条,在BaseActivity中已经实现
getView().hideLoading();
}
});
}
}
package top.spencer.crabscore.base;
/**
* @author spencercjh
*/
public class BasePresenter<V extends BaseView> {
/**
* 绑定的view
*/
private V mvpView;
/**
* 绑定view,一般在初始化中调用该方法
*/
public void attachView(V view) {
this.mvpView = view;
}
/**
* 断开view,一般在onDestroy中调用
*/
public void detachView() {
this.mvpView = null;
}
/**
* 是否与View建立连接
* 每次调用业务请求的时候都要出先调用方法检查是否与View建立连接
*/
public boolean isViewAttached() {
return mvpView != null;
}
/**
* 获取连接的view
*/
public V getView() {
return mvpView;
}
}
package top.spencer.crabscore.data.model;
import android.util.Log;
import top.spencer.crabscore.base.BaseModel;
import static android.content.ContentValues.TAG;
/**
* @author spencercjh
*/
public class DataModel {
public static BaseModel request(String token) {
// 声明一个空的BaseModel
BaseModel model = null;
try {
//利用反射机制获得对应Model对象的引用
model = (BaseModel) Class.forName(token).newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
Log.e(TAG, "Model反射错误");
}
return model;
}
}
这里校验参数的返回错误信息写得有点呆,不太会解决了。后面要传JSONObject获取,这边只要String。我觉得哪边改其实都一样吧:都改成String以后后面就要增加转JSONObject的代码。
package top.spencer.crabscore.data.model;
import android.content.Intent;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.digest.DigestUtils;
import top.spencer.crabscore.base.BaseModel;
import top.spencer.crabscore.data.Callback;
import top.spencer.crabscore.common.CommonConstant;
/**
* 调用Login接口的Model层
*
* @author spencercjh
*/
public class LoginModel extends BaseModel {
@Override
public void execute(final Callback<JSONObject> myCallBack) {
if (StrUtil.isEmpty(mvpParams[0])) {
String result = "{\"code\":501,\"message\":\"用户名不能为空\",\"result\":{},\"success\":false,\"timestamp\":0}";
JSONObject resultJson = JSON.parseObject(result);
myCallBack.onFailure(resultJson);
myCallBack.onComplete();
return;
} else if (StrUtil.isEmpty(mvpParams[1])) {
String result = "{\"code\":501,\"message\":\"密码不能为空\",\"result\":{},\"success\":false,\"timestamp\":0}";
JSONObject resultJson = JSON.parseObject(result);
myCallBack.onFailure(resultJson);
myCallBack.onComplete();
return;
} else if (Integer.parseInt(mvpParams[2]) == 0) {
String result = "{\"code\":501,\"message\":\"用户组不能不选择\",\"result\":{},\"success\":false,\"timestamp\":0}";
JSONObject resultJson = JSON.parseObject(result);
myCallBack.onFailure(resultJson);
myCallBack.onComplete();
return;
}
String url = CommonConstant.URL + "common/login" + "?username=" + mvpParams[0] +
"&password=" + DigestUtils.md5Hex(mvpParams[1]) +
"&roleId=" + mvpParams[2];
//login接口JWT为空,返回会拿到一个JWT,JWT是放在ResponseBody里的,再下次请求时要把JWT放进RequestHeader
requestGetAPI(url, myCallBack, "");
}
}
这里把REST请求都实现了。
package top.spencer.crabscore.base;
import android.support.annotation.NonNull;
import android.util.Log;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import okhttp3.*;
import top.spencer.crabscore.common.CommonConstant;
import top.spencer.crabscore.data.Callback;
import java.io.IOException;
import java.util.Map;
import static android.content.ContentValues.TAG;
/**
* @author spencercjh
*/
@SuppressWarnings("Duplicates")
public abstract class BaseModel {
/**
* 数据请求参数
*/
protected String[] mvpParams;
/**
* 设置数据请求参数
*
* @param args 参数数组
*/
public BaseModel params(String... args) {
mvpParams = args;
return this;
}
/**
* 具体的数据请求由子类实现
*
* @param myCallBack myCallBack
*/
public abstract void execute(Callback<JSONObject> myCallBack);
/**
* OkHttp3 异步Get请求
*
* @param url URL (需要在外面处理好)
* @param myCallBack myCallBack
* @param jwt Header里的JWT串
*/
protected void requestGetAPI(String url, final Callback<JSONObject> myCallBack, String jwt) {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.get()
.addHeader("jwt", jwt)
.build();
okHttpClient.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
myCallBack.onError();
myCallBack.onComplete();
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
String jwt = response.headers().get("jwt");
JSONObject responseJsonResult;
try {
assert response.body() != null;
Log.d(TAG, response.body().string());
responseJsonResult = JSON.parseObject(response.body().string());
responseJsonResult.put("jwt", jwt);
} catch (IOException | NullPointerException e) {
e.printStackTrace();
myCallBack.onError();
myCallBack.onComplete();
return;
}
Integer code = responseJsonResult.getInteger("code");
if (code.equals(CommonConstant.SUCCESS)) {
myCallBack.onSuccess(responseJsonResult);
} else {
myCallBack.onFailure(responseJsonResult);
}
myCallBack.onComplete();
}
});
}
/**
* OkHttp3 Post异步方式提交表单
*
* @param url URL
* @param postParams body中的参数
* @param myCallBack myCallBack
* @param jwt Header里的JWT串
*/
protected void requestPostAPI(String url, Map<String, Object> postParams,
final Callback<JSONObject> myCallBack, String jwt) {
OkHttpClient okHttpClient = new OkHttpClient();
FormBody.Builder formBody = new FormBody.Builder();
for (Map.Entry<String, Object> param : postParams.entrySet()) {
formBody.add(param.getKey(), param.getValue().toString());
}
RequestBody requestBody = formBody.build();
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.addHeader("jwt", jwt)
.build();
okHttpClient.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
myCallBack.onError();
myCallBack.onComplete();
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
String jwt = response.headers().get("jwt");
JSONObject responseJsonResult;
try {
assert response.body() != null;
Log.d(TAG, response.body().string());
responseJsonResult = JSON.parseObject(response.body().string());
responseJsonResult.put("jwt", jwt);
} catch (IOException | NullPointerException e) {
e.printStackTrace();
myCallBack.onError();
myCallBack.onComplete();
return;
}
Integer code = responseJsonResult.getInteger("code");
if (code.equals(CommonConstant.SUCCESS)) {
myCallBack.onSuccess(responseJsonResult);
} else {
myCallBack.onFailure(responseJsonResult);
}
myCallBack.onComplete();
}
});
}
/**
* OkHttp3 PUT异步请求
*
* @param url URL
* @param putParams body中的参数
* @param myCallBack myCallBack
* @param jwt Header里的JWT串
*/
protected void requestPutAPI(String url, Map<String, Object> putParams,
final Callback<JSONObject> myCallBack, String jwt) {
OkHttpClient okHttpClient = new OkHttpClient();
FormBody.Builder formBody = new FormBody.Builder();
for (Map.Entry<String, Object> param : putParams.entrySet()) {
formBody.add(param.getKey(), param.getValue().toString());
}
RequestBody requestBody = formBody.build();
Request request = new Request.Builder()
.url(url)
.put(requestBody)
.addHeader("jwt", jwt)
.build();
okHttpClient.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
myCallBack.onError();
myCallBack.onComplete();
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
String jwt = response.headers().get("jwt");
JSONObject responseJsonResult;
try {
assert response.body() != null;
Log.d(TAG, response.body().string());
responseJsonResult = JSON.parseObject(response.body().string());
responseJsonResult.put("jwt", jwt);
} catch (IOException | NullPointerException e) {
e.printStackTrace();
myCallBack.onError();
myCallBack.onComplete();
return;
}
Integer code = responseJsonResult.getInteger("code");
if (code.equals(CommonConstant.SUCCESS)) {
myCallBack.onSuccess(responseJsonResult);
} else {
myCallBack.onFailure(responseJsonResult);
}
myCallBack.onComplete();
}
});
}
/**
* OkHttp3 Delete异步请求
*
* @param url URL (需要在外面处理好)
* @param myCallBack myCallBack
* @param jwt Header里的JWT串
*/
protected void requestDeleteAPI(String url, final Callback<JSONObject> myCallBack, String jwt) {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.delete()
.addHeader("jwt", jwt)
.build();
okHttpClient.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
myCallBack.onError();
myCallBack.onComplete();
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
String jwt = response.headers().get("jwt");
JSONObject responseJsonResult;
try {
assert response.body() != null;
Log.d(TAG, response.body().string());
responseJsonResult = JSON.parseObject(response.body().string());
responseJsonResult.put("jwt", jwt);
} catch (IOException | NullPointerException e) {
e.printStackTrace();
myCallBack.onError();
myCallBack.onComplete();
return;
}
Integer code = responseJsonResult.getInteger("code");
if (code.equals(CommonConstant.SUCCESS)) {
myCallBack.onSuccess(responseJsonResult);
} else {
myCallBack.onFailure(responseJsonResult);
}
myCallBack.onComplete();
}
});
}
}
package top.spencer.crabscore.data;
/**
* @author spencercjh
*/
public interface Callback<T> {
/**
* 数据请求成功
*
* @param data 请求到的数据
*/
void onSuccess(T data);
/**
* 使用网络API接口请求方式时,虽然已经请求成功但是由
* 于{@code msg}的原因无法正常返回数据。
*
* @param data 失败数据
*/
void onFailure(T data);
/**
* 请求数据失败,指在请求网络API接口请求方式时,出现无法联网、
* 缺少权限,内存泄露等原因导致无法连接到请求数据源。
*/
void onError();
/**
* 当请求数据结束时,无论请求结果是成功,失败或是抛出异常都会执行此方法给用户做处理,通常做网络
* 请求时可以在此处隐藏“正在加载”的等待控件
*/
void onComplete();
}
package top.spencer.crabscore.data.constant;
import top.spencer.crabscore.data.model.LoginModel;
/**
* 具体Model类,常量用于反射
*
* @author spencercjh
*/
public class Token {
public static final String API_LOGIN = LoginModel.class.getName();
}
这个工具类挺有用的。
package top.spencer.crabscore.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import com.google.gson.*;
import java.util.*;
/**
* https://blog.csdn.net/a512337862/article/details/73633420
*
* @author ZhangHao
* @date 2016/6/21
* SharedPreferences 工具类
*/
@SuppressWarnings("unused")
public class SharedPreferencesUtil {
private static SharedPreferencesUtil util;
private static SharedPreferences sp;
private SharedPreferencesUtil(Context context, String name) {
sp = context.getSharedPreferences(name, Context.MODE_PRIVATE);
}
/**
* 初始化SharedPreferencesUtil,只需要初始化一次,建议在Application中初始化
*
* @param context 上下文对象
* @param name SharedPreferences Name
*/
public static void getInstance(Context context, String name) {
if (util == null) {
util = new SharedPreferencesUtil(context, name);
}
}
/**
* 保存数据到SharedPreferences
*
* @param key 键
* @param value 需要保存的数据
* @return 保存结果
*/
public static boolean putData(String key, Object value) {
boolean result;
SharedPreferences.Editor editor = sp.edit();
String type = value.getClass().getSimpleName();
try {
switch (type) {
case "Boolean":
editor.putBoolean(key, (Boolean) value);
break;
case "Long":
editor.putLong(key, (Long) value);
break;
case "Float":
editor.putFloat(key, (Float) value);
break;
case "String":
editor.putString(key, (String) value);
break;
case "Integer":
editor.putInt(key, (Integer) value);
break;
default:
Gson gson = new Gson();
String json = gson.toJson(value);
editor.putString(key, json);
break;
}
result = true;
} catch (Exception e) {
result = false;
e.printStackTrace();
}
editor.apply();
return result;
}
/**
* 获取SharedPreferences中保存的数据
*
* @param key 键
* @param defaultValue 获取失败默认值
* @return 从SharedPreferences读取的数据
*/
public static Object getData(String key, Object defaultValue) {
Object result;
String type = defaultValue.getClass().getSimpleName();
try {
switch (type) {
case "Boolean":
result = sp.getBoolean(key, (Boolean) defaultValue);
break;
case "Long":
result = sp.getLong(key, (Long) defaultValue);
break;
case "Float":
result = sp.getFloat(key, (Float) defaultValue);
break;
case "String":
result = sp.getString(key, (String) defaultValue);
break;
case "Integer":
result = sp.getInt(key, (Integer) defaultValue);
break;
default:
Gson gson = new Gson();
String json = sp.getString(key, "");
if (!"".equals(json) && json.length() > 0) {
result = gson.fromJson(json, defaultValue.getClass());
} else {
result = defaultValue;
}
break;
}
} catch (Exception e) {
result = null;
e.printStackTrace();
}
return result;
}
/**
* 用于保存集合
*
* @param key key
* @param list 集合数据
* @return 保存结果
*/
public static <T> boolean putListData(String key, List<T> list) {
boolean result;
String type = list.get(0).getClass().getSimpleName();
SharedPreferences.Editor editor = sp.edit();
JsonArray array = new JsonArray();
try {
switch (type) {
case "Boolean":
for (T aList : list) {
array.add((Boolean) aList);
}
break;
case "Long":
for (T aList : list) {
array.add((Long) aList);
}
break;
case "Float":
for (T aList : list) {
array.add((Float) aList);
}
break;
case "String":
for (T aList : list) {
array.add((String) aList);
}
break;
case "Integer":
for (T aList : list) {
array.add((Integer) aList);
}
break;
default:
Gson gson = new Gson();
for (T aList : list) {
JsonElement obj = gson.toJsonTree(aList);
array.add(obj);
}
break;
}
editor.putString(key, array.toString());
result = true;
} catch (Exception e) {
result = false;
e.printStackTrace();
}
editor.apply();
return result;
}
/**
* 获取保存的List
*
* @param key key
* @return 对应的Lis集合
*/
public static <T> List<T> getListData(String key, Class<T> cls) {
List<T> list = new ArrayList<>();
String json = sp.getString(key, "");
if (!"".equals(json) && json.length() > 0) {
Gson gson = new Gson();
JsonArray array = new JsonParser().parse(json).getAsJsonArray();
for (JsonElement elem : array) {
list.add(gson.fromJson(elem, cls));
}
}
return list;
}
/**
* 用于保存集合
*
* @param key key
* @param map map数据
* @return 保存结果
*/
public static <K, V> boolean putHashMapData(String key, Map<K, V> map) {
boolean result;
SharedPreferences.Editor editor = sp.edit();
try {
Gson gson = new Gson();
String json = gson.toJson(map);
editor.putString(key, json);
result = true;
} catch (Exception e) {
result = false;
e.printStackTrace();
}
editor.apply();
return result;
}
/**
* 用于保存集合
*
* @param key key
* @return HashMap
*/
public static <V> HashMap<String, V> getHashMapData(String key, Class<V> clsV) {
String json = sp.getString(key, "");
HashMap<String, V> map = new HashMap<>(16);
Gson gson = new Gson();
JsonObject obj = new JsonParser().parse(json).getAsJsonObject();
Set<Map.Entry<String, JsonElement>> entrySet = obj.entrySet();
for (Map.Entry<String, JsonElement> entry : entrySet) {
String entryKey = entry.getKey();
JsonElement value = entry.getValue();
map.put(entryKey, gson.fromJson(value, clsV));
}
Log.e("SharedPreferencesUtil", obj.toString());
return map;
}
/**
* 删除键值
*
* @param key key
* @return result
*/
public static boolean deleteData(String key) {
SharedPreferences.Editor editor = sp.edit();
try {
editor.remove(key);
editor.apply();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
原来我是看得懂正则的……
package top.spencer.crabscore.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 用户名验证工具类
*
* @author Exrickx
*/
@SuppressWarnings("unused")
public class PatternUtil {
/**
* 由字母数字下划线组成且开头必须是字母,不能超过16位
*/
private static final Pattern USERNAME = Pattern.compile("[a-zA-Z][a-zA-Z0-9_]{1,15}");
/**
* 手机号
*/
private static final Pattern MOBILE = Pattern.compile("^1[3|4|5|7|8][0-9]\\d{8}$");
/**
* 邮箱
*/
private static final Pattern EMAIL = Pattern.compile("^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.[a-zA-Z0-9]{2,6}$");
public static boolean isUsername(String v) {
Matcher m = USERNAME.matcher(v);
return m.matches();
}
public static boolean isMobile(String v) {
Matcher m = MOBILE.matcher(v);
return m.matches();
}
public static boolean isEmail(String v) {
Matcher m = EMAIL.matcher(v);
return m.matches();
}
}