Android(安卓)MVP架构搭建
目录
- 鸣谢
- 摘要
- 开始
- 环境
- 别人的话
- 我的理解
- 我的实现
- 实现结果
- 总结
- 凑页数的源码展示
- LoginActivity
- activity_login.xml
- BaseActivity
- LoginView
- BaseView
- LoginPresenter
- BasePresenter
- DataModel
- LoginModel
- BaseModel
- Callback
- Token
- SharedPreferencesUtil
- PatternUtil
鸣谢
感谢泡在网上的日子用户@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架构的代码量很大,但方便维护和开发,大量公用的东西也可以不用写了。还在不断地学习和使用中,有了心得会继续撰文。
凑页数的源码展示
LoginActivity
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(); }}
activity_login.xml
后面我寻思着还是得再整个高大上的登陆界面去参赛。
<?xml version="1.0" encoding="utf-8"?><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>
BaseActivity
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; }}
LoginView
package top.spencer.crabscore.view;import top.spencer.crabscore.base.BaseView;/** * @author spencercjh */public interface LoginView extends BaseView { /** * 初始化用户组Spinner */ void initSpinner(); /** * 读取SharedPreferences,执行相关业务逻辑 */ void readSharedPreferences();}
BaseView
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();}
LoginPresenter
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(); } }); }}
BasePresenter
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; }}
DataModel
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; }}
LoginModel
这里校验参数的返回错误信息写得有点呆,不太会解决了。后面要传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, ""); }}
BaseModel
这里把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(); } }); }}
Callback
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();}
Token
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();}
SharedPreferencesUtil
这个工具类挺有用的。
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; } }}
PatternUtil
原来我是看得懂正则的……
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(); }}
更多相关文章
- mybatisplus的坑 insert标签insert into select无参数问题的解决
- python起点网月票榜字体反爬案例
- 《Android开发从零开始》——25.数据存储(4)
- Android(安卓)Wifi模块分析(三)
- Android系统配置数据库注释(settings.db)
- Android中dispatchDraw分析
- Android中不同应用间实现SharedPreferences数据共享
- Android四大基本组件介绍与生命周期
- Android(安卓)Service AIDL