目录

  • 鸣谢
  • 摘要
  • 开始
    • 环境
    • 别人的话
    • 我的理解
    • 我的实现
    • 实现结果
  • 总结
  • 凑页数的源码展示
    • 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();    }}

更多相关文章

  1. mybatisplus的坑 insert标签insert into select无参数问题的解决
  2. python起点网月票榜字体反爬案例
  3. 《Android开发从零开始》——25.数据存储(4)
  4. Android(安卓)Wifi模块分析(三)
  5. Android系统配置数据库注释(settings.db)
  6. Android中dispatchDraw分析
  7. Android中不同应用间实现SharedPreferences数据共享
  8. Android四大基本组件介绍与生命周期
  9. Android(安卓)Service AIDL

随机推荐

  1. ListView 圆角
  2. Android百度地图相关内容汇总
  3. Android笔记:Socket客户端收发数据
  4. Android——获取网络图片
  5. 日历
  6. android 获取IP地址
  7. 【Android】移动GIS开发必备(文档、帮助、
  8. Android——实现两个控件水平居中
  9. android控件EditText
  10. android 2.2 apidemos 赏析笔记 2