带你封装MVP架构(上)|青训营笔记
这是我参与「第四届青训营 」笔记创作活动的的第10天
前置知识
MVP:MVP架构模式详解 - 简书 (jianshu.com)
Retrofit:Retrofit使用详解-注解介绍 - 掘金 (juejin.cn)
RxJava:Carson带你学Android:这是一篇清晰易懂的Rxjava入门教程 - 简书 (jianshu.com)
ViewBinding:kotlin-android-extensions插件也被废弃了?扶我起来_guolin的博客-CSDN博客_kotlin-android-extensions废弃
浅析封装层级
我们做一个 MVP
架构的封装,主要其相对于MVC更加解耦,能让开发人员在编写代码的时候更加高效和舒服。
那么我们最主要需要做到的就是职责区分以及减少重复,所以必不可少的就是做到可以让 MVP 模式下的数据层、视图层、发布层的更低成本的各司其职,以及在项目中对 Base
基类进行提取关联。此外,我们需要集成常用的工具类以及打造好优雅的网络请求调用方式等等。
那我们可以简单的对项目分包,其至少包括以下几个
-app
-base
-http
-module
-util
声明:
文章使用到的MVP封装方法是学习于:
yechaoa/wanandroid_java: 🎨 玩安卓客户端 ,MD + Retrofit + RxJava + MVP + AndroidX (github.com)
带你封装自己的MVP+Retrofit+RxJava2框架(二) - 掘金 (juejin.cn)
笔者对该架构进行升级修改,以及在文章中会讲述对网络请求部分的功能添加
工欲善其事,必先利其器
首先,我们载入后续会使用到的开源框架
//app/build.gradle
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments += [
// "room.schemaLocation":"$projectDir/schemas".toString(),
eventBusIndex : 'com.qxy.potato.MyEventBusIndex' ]
}
}
...
}
...
// 支持使用viewBinding
buildFeatures {
viewBinding = true
}
...
}
dependencies {
//MMKV替代SP https://github.com/Tencent/MMKV/wiki/android_tutorial_cn
implementation 'com.tencent:mmkv:1.2.14'
/*retrofit raJava*/
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0'
//glide
implementation 'com.github.bumptech.glide:glide:4.13.2'
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.2'
//引入的崩溃管理和跳转UI哭
implementation 'com.github.tamsiree.RxTool:RxKit:2.6.3'
implementation 'com.github.tamsiree.RxTool:RxUI:2.6.3'
//ViewBinding
implementation 'com.github.DylanCaiCoding.ViewBindingKTX:viewbinding-base:2.0.6'
implementation 'com.github.DylanCaiCoding.ViewBindingKTX:viewbinding-brvah:2.0.6'
//引入Rv管理
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.7'
//沉浸式
implementation 'com.gitee.zackratos:UltimateBarX:0.8.0'
//eventBus
def eventbus_version = '3.3.1'
implementation "org.greenrobot:eventbus:$eventbus_version"
annotationProcessor "org.greenrobot:eventbus-annotation-processor:$eventbus_version"
}
建造一所宏大的房子前,不仅需要图纸,还需要有趁手的工具。
搭建一套好用的框架也是,我们应该构建好几个架构封装过程中必须使用到的工具类
ActivityUtil
该工具类是用于获取当前 Activity ,以及获取封装一些启动 Activity 的操作。
@TargetApi(14)
public class ActivityUtil {
private static Stack<Activity> activityStack = new Stack<>();
private static final MyActivityLifecycleCallbacks instance = new MyActivityLifecycleCallbacks();
public ActivityUtil() {
}
/**
* @return 返回生命周期回调类
*/
public static ActivityLifecycleCallbacks getActivityLifecycleCallbacks() {
return instance;
}
/**
* 结束当前Activity
*
* @param activity 当前Activity
*/
public static void finishActivity(Activity activity) {
if (activity != null) {
activityStack.remove(activity);
activity.finish();
}
}
/**
* 不用 finish 当前 Activity 时直接调用此方法
*
* @param classes
*/
public static void startActivity(Class classes) {
startActivity(classes, false);
}
/**
* 需要 finish 当前 Activity 时调用此方法,布尔值参数传入 true
*
* @param classes 需要打开的 activity
* @param isFinish 是否 finish 当前 activity
*/
public static void startActivity(Class classes, boolean isFinish) {
Activity currentActivity = getCurrentActivity();
Intent intent = new Intent(currentActivity, classes);
currentActivity.startActivity(intent);
if (isFinish) {
finishActivity(currentActivity);
}
}
/**
* 关闭所有 Activity
*/
public static void closeAllActivity() {
while (true) {
Activity activity = getCurrentActivity();
if (null == activity) {
return;
}
finishActivity(activity);
}
}
/**
* 得到当前的 Activity
*
* @return 当前 Activity
*/
public static Activity getCurrentActivity() {
Activity activity = null;
if (!activityStack.isEmpty()) {
activity = activityStack.peek();
}
return activity;
}
/**
* 启动 activity, 带上参数
*
* @param classes 需要打开的 activity
* @param hashMap 需要传递的参数
*/
public static void startActivity(@SuppressWarnings("rawtypes") Class classes, HashMap<String, String> hashMap) {
startActivity(classes, hashMap, false);
}
/**
* 启动 activity, 可以设置是否关闭当前 activity
*
* @param classes 需要打开的 activity
* @param hashMap 需要传递的参数
* @param isFinish 是否关闭当前 activity
*/
public static void startActivity(@SuppressWarnings("rawtypes") Class classes, HashMap<String, String> hashMap, boolean isFinish) {
Activity currentActivity = getCurrentActivity();
Intent intent = new Intent(currentActivity, classes);
for (Map.Entry<String, String> entry : hashMap.entrySet()) {
intent.putExtra(entry.getKey(), entry.getValue());
}
currentActivity.startActivity(intent);
if (isFinish) {
finishActivity(currentActivity);
}
}
private static class MyActivityLifecycleCallbacks implements ActivityLifecycleCallbacks {
private MyActivityLifecycleCallbacks() {
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
ActivityUtil.activityStack.remove(activity);
ActivityUtil.activityStack.push(activity);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
ActivityUtil.activityStack.remove(activity);
}
}
}
该类用一个栈来记录启动的Activity,且永远将新创建的Activity放于栈顶,让外部调用者可以立刻调用到当前的Activity事件。
并且封装了各种Activity启动方法供外部调用。
MyUtil
该工具类获得了一个全局的 Context ,以及根据此context,创建了开启加载动画以及关闭加载动画的方法
public class MyUtil {
/**
* 全局context
*/
private static Application mApplicationContext;
private static RxDialogLoading rxDialogLoading;
/**
* @param app 初始化全局context
*/
public static void initialize(Application app) {
mApplicationContext = app;
}
/**
* 获得全局context
*
* @return 当前的全局context
*/
public static Application getApplication() {
return mApplicationContext;
}
/**
* 关闭键盘
*/
public static void closeSoftKeyboard() {
InputMethodManager inputManger = (InputMethodManager) ActivityUtil.getCurrentActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputManger != null) {
inputManger.hideSoftInputFromWindow(ActivityUtil.getCurrentActivity().getWindow().getDecorView().getWindowToken(), 0);
}
}
public static String getString(int id) {
return getApplication().getResources().getString(id);
}
/**
* 展示dialog
*
* @param context 传入当前Acitivity
*/
public static void showLoading(Context context){
if (rxDialogLoading == null){
rxDialogLoading = new RxDialogLoading(context);
rxDialogLoading.setCanceledOnTouchOutside(false);
}
rxDialogLoading.show();
}
/**
* 成功隐藏dialog,显示成功
*/
public static void dismissSuccessLoading(){
rxDialogLoading.cancel(RxDialogLoading.RxCancelType.success,getString(R.string.load_success));
}
/**
* 失败隐藏dialog,显示失败
*/
public static void dismissFailedLoading(){
rxDialogLoading.cancel(RxDialogLoading.RxCancelType.error,getString(R.string.load_error));
}
}
Retrofit + Rxjava 打造优雅网络请求方式
由于该模块代码繁多,文章不会描述每一个类,完整代码请查阅http
其中的 cookie 包是用于做登录状态持久化,但是本demo中未用到
gson 包是做了一个转换器,对网络数据的请求报文和响应报文做了一次数据筛查
API 类是定义了所有的网络请求方法
RetrofitService 类是对该网络模块的集成的方法,是 Retrofit 的核心方法
BaseResponseBodyConverter
该方法是继承了类型为响应报文的 Retrofit 转换器类,可以对对响应报文做数据筛查。我们可以筛查出响应的报文的错误码或者错误描述进行自动的异常处理。
例如返回的错误码描述为当前登录状态过期,那么我们可以取消登录状态,程序自动跳转到登录页面中。
亦或是错误码为非正常状态下,自动抛出自定义的异常,然后App对该异常做 Toast 描述之类的
public class BaseResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final TypeAdapter<T> adapter;
BaseResponseBodyConverter(TypeAdapter<T> adapter) {
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
String jsonString = value.string();
try{
JSONObject object = new JSONObject(jsonString);
int code = object.getInt(MyUtil.getString(R.string.code));
String data ;
if (code == 1) return adapter.fromJson(jsonString);
else data = object.getString(MyUtil.getString(R.string.msg));
throw new BaseException(code, data);
}catch (JSONException ex){
ex.printStackTrace();
try {
JSONObject object = new JSONObject(jsonString);
int error_code = object.getJSONObject("data").getInt(MyUtil.getString(R.string.error_code));
if (0 != error_code ) {
String data;
data = object.getJSONObject("data").getString(MyUtil.getString(R.string.error_msg));
//异常处理
throw new BaseException(error_code, data);
}
//正确返回整个json
return adapter.fromJson(jsonString);
} catch (JSONException e) {
e.printStackTrace();
//数据解析异常即json格式有变动
throw new BaseException(MyUtil.getString(R.string.PARSE_ERROR_MSG));
}
}finally {
value.close();
}
}
}
需要注意的是,我们需要针对不同格式的响应报文,定制不同的筛查策略和处理逻辑
MoreBaseUrlInterceptor
自定义一个过滤器,在该过滤器中,我们可以对请求的做一些自动的更改
例如下例,我们可以在检测到请求头中带 urlName
字段的时候,去做一个对应的 BaseUrl
的更新;我们也还可以在这里做一些譬如key这些字段的添加等等
public class MoreBaseUrlInterceptor implements Interceptor {
private final HashMap<String,String> keyUrl;
public MoreBaseUrlInterceptor(HashMap<String, String> keyUrl) {
this.keyUrl = keyUrl;
}
@Override
public Response intercept(Chain chain) throws IOException {
//获取原始的originalRequest
Request originalRequest = chain.request();
//获取老的url
HttpUrl oldUrl = originalRequest.url();
//获取originalRequest的创建者builder
Request.Builder builder = originalRequest.newBuilder();
//获取头信息的集合如:base,geo
List<String> urlNameList = originalRequest.headers("urlName");
if (urlNameList.size() > 0) {
//删除原有配置中的值,就是namesAndValues集合里的值
builder.removeHeader("urlName");
//获取头信息中配置的value,如:m或者mock
String urlName = urlNameList.get(0);
HttpUrl baseURL;
//根据头信息中配置的value,来匹配新的base_url地址
baseURL = HttpUrl.parse(Objects.requireNonNull(keyUrl.get(urlName)));
//重建新的HttpUrl,需要重新设置的url部分
HttpUrl newHttpUrl = oldUrl.newBuilder()
.scheme(baseURL.scheme())//http协议如:http或者https
.host(baseURL.host())//主机地址
.encodedPath(baseURL.encodedPath() +
oldUrl.encodedPath().substring(1))//构建路径,为适配MOCK中给出的BaseURL带有Path路径的情况
.port(baseURL.port())//端口
.build();
//获取处理后的新newRequest
Request newRequest = builder.url(newHttpUrl).build();
return chain.proceed(newRequest);
}else{
Request reOriginalRequest = builder.url(oldUrl).build();
return chain.proceed(reOriginalRequest);
}
}
}
RetrofitService
该类是 Retrofit 服务类,对外提供一个服务的单例,然后添加好前面编写的转换器以及过滤器等信息。这样子供外部统一调用这个 Retrofit 单例即可。
public class RetrofitService {
private volatile static RetrofitService apiRetrofit;
private API.SZApi apiServer;
/**
* 单例调用
*
* @return RetrofitService
*/
public static RetrofitService getInstance() {
if (apiRetrofit == null) {
synchronized (Object.class) {
if (apiRetrofit == null) {
apiRetrofit = new RetrofitService();
}
}
}
return apiRetrofit;
}
/**
* 获取api对象
*
* @return api对象
*/
public API.SZApi getApiService() {
return apiServer;
}
/**
* 初始化retrofit
*/
private RetrofitService() {
//配置okHttp并设置时间、日志信息和cookies
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new MoreBaseUrlInterceptor(API.getKeyUrl()))
.addInterceptor(httpLoggingInterceptor)
//设置超时时间
.connectTimeout(15, TimeUnit.SECONDS)
//设置Cookie持久化
// .cookieJar(new CookiesManager(MyUtil.getApplication()))
.build();
//关联okHttp并加上rxJava和Gson的配置和baseUrl
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(BaseConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(API.BASE_URL)
.build();
apiServer = retrofit.create(API.SZApi.class);
}
}
筑基的Application
再接着,我们需要自定义一个 Application,并在此处做好对一些框架和工具类的初始化
public class App extends Application {
private static Context context;
@Override
public void onCreate() {
super.onCreate();
context=getApplicationContext();
MMKV.initialize(this);
//使用订阅索引,加快编译速度
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
//初始化
MyUtil.initialize(this);
//设置UI工具
RxTool.init(this);
//注册Activity生命周期
registerActivityLifecycleCallbacks(ActivityUtil.getActivityLifecycleCallbacks());
}
public static Context getContext() {
return context;
}
}
参考
带你封装自己的MVP+Retrofit+RxJava2框架(二) - 掘金 (juejin.cn)
yechaoa/wanandroid_java: 🎨 玩安卓客户端 ,MD + Retrofit + RxJava + MVP + AndroidX (github.com)