Android MVP架构搭建

鸣谢

感谢泡在网上的日子用户@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();
    }
}

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值