Android(安卓)Annotations+Retrofit+Rxjava2+okhttp3+MVP框架搭建
- 前言
- AndroidAnnotations与注解框架对比
- 1、ButterKnife(编译时注解)
- 2、XUtils(运行时注解)
- 3、AndroidAnnotations(编译时注解)
- 4、注解框架对比
- Retrofit+OkHttp
- RxJava
- MVP搭建
- 1、Retrofit+Okhttp封装
- 2、BaseView、BasePresenter
- 3、使用RxJava配合Retrofit进行请求:
- 4、使用RxJava管理请求
- 使用
- 总结
前言
之前一直在qq空间和公众号发布文章,这几天才转移到博客平台。对博客平台的各种规则还不是特别熟悉,属于博客小白。但是我会坚持更新,将实用的,易懂的文章推送给大家。感谢大家的点赞和关注。
上一篇文章 《Android Gradle统一管理打包》收到了很多点赞和评论。Gradle统一管理在项目体量较小的情况下是不必要的,但是如果体量稍大,或者使用了插件化框架。那么它的好处就会显示出来,在上一篇的基础上,继续进行探索,梳理流行框架Annotations+Retrofit+Rxjava2+okhttp3+MVP框架的搭建。
而Annotations+Retrofit+Rxjava2+okhttp3+MVP框架又和gradle有什么关系呢?
本来想通过一篇 Small插件化+Annotations+Retrofit+Rxjava2+okhttp3+MVP 来描述在使用插件化的情况下moudle过多的情况,但是Small插件化是一个很重要的东西,如果全部混在一起写会造成篇幅过长或者内容过于杂乱。所以本篇抛开Small插件化只描述Annotations+Retrofit+Rxjava2+okhttp3+MVP的搭建。下一篇博文再对Small插件化和此框架进行融合。
AndroidAnnotations与注解框架对比
注解(Annotation)也叫元数据。是一种代码级别的说明。它与类、接口、枚举在同一个层次。可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。说白了就是一种标记,可以用自己的注解解释器在编译时进行处理(编译时注解),或者在运行时通过反射对其进行处理(运行时注解)。注解框架有很多,常用的有ButterKnife、AndroidAnnotations、XUtils等。
1、ButterKnife(编译时注解)
ButterKnfie出自Jakewharton大神,是使用非常多的一个注解框架。
ButterKnife通过反射在编译时生成一个ViewBinder对象,类似于:
MainActivity$ViewBinder implements ButterKnife.ViewBinder
在这个类中就有使用注解声明的一些组件和监听。
因为ButterKnife在编译时通过反射一次性生成了这些需要使用到的类,所以对运行时的速度是没有什么影响的。
2、XUtils(运行时注解)
XUtils和ButterKnife类似,都使用了反射来对注解声明的字段或者对象进行赋值。
区别在于XUtils是在运行时进行反射,众所周知反射的效率相对于原生代码是较慢的。
虽然设备性能在提升,但是在组件、事件过多的情况下还是造成初始化速度降低。
3、AndroidAnnotations(编译时注解)
AndroidAnnotations和ButterKnife类似,都是编译时注解。所以在性能方面不相上下,但是两者的实现思路却不同:
ButterKnfie使用反射来对注解标记的字段赋值。而AndroidAnnotations会生成一个继承于当前类的子类来对标记的字段进行赋值。
AndroidAnnotations生成的类后面都会带有一个符号“_”,运行的时候也运行的是这个生成的子类,而不是我们的当前类。
看一下AndroidAnnotations生成的代码就能够很清楚的了解它的思路,首先我们看一下使用了注解的StartActivity中的代码。
StartActivity.java
//使用EActivity注解加载布局@EActivity(R.layout.activity_start)public class StartActivity extends BaseActivity implements StartContract.View { private StartContract.Presenter mPresenter; //使用ViewById注解查找组件 @ViewById(R.id.cdpv_start) CountDownProgressView mCountDownView; //倒计时组件 private static final long mDelayTimeLong = 2000l;//倒计时毫秒 //加载布局之前调用方法的注解 @AfterInject void afterInject() { new StartPresenter(this); StatusBarUtil.immersive(this); } //加载布局之后调用方法的注解 @AfterViews void afterViews() { mCountDownView.setTimeMillis(mDelayTimeLong); mCountDownView.start(); countDown(); } //运行在子线程的注解 @Background(delay = mDelayTimeLong, id = "delay_to_main") void countDown() { startMain(); } //运行在主线程的注解 @UiThread void startMain() { if (UserStateUtil.getInstance().isLoggedOn()) { MainActivity_.intent(getContext()).start(); } else { LoginActivity_.intent(getContext()).start(); } finish(); } //点击事件监听注解 @Click(R.id.cdpv_start) void onCountDownClick() { BackgroundExecutor.cancelAll("delay_to_main", true); startMain(); } @Override public void setPresenter(StartContract.Presenter presenter) { mPresenter = Null.checkNotNull(presenter); } @Override public Activity getContext() { return this; } @Override protected void onDestroy() { super.onDestroy(); BackgroundExecutor.cancelAll("delay_to_main", true); }}
上面的代码是启动页本类的实现,可以看到使用了AndroidAnnotations的一些常用注解。
而这些注解都会通过框架生成对应的代码,下面是通过本类生成的StartActivity_.java子类的部分代码:
StartActivity_ . java 部分代码
public final class StartActivity_ extends StartActivity implements HasViews, OnViewChangedListener { private final OnViewChangedNotifier onViewChangedNotifier_ = new OnViewChangedNotifier(); @Override public void onCreate(Bundle savedInstanceState) { OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_); init_(savedInstanceState);//此处调用了AfterInject super.onCreate(savedInstanceState); OnViewChangedNotifier.replaceNotifier(previousNotifier); setContentView(R.layout.activity_start);//此处通过EActivity加载 } private void init_(Bundle savedInstanceState) { OnViewChangedNotifier.registerOnViewChangedListener(this); afterInject(); } @Override public <T extends View> T internalFindViewById(int id) { return ((T) this.findViewById(id)); } @Override public void setContentView(int layoutResID) { super.setContentView(layoutResID); onViewChangedNotifier_.notifyViewChanged(this); } //查找组件设置监听 @Override public void onViewChanged(HasViews hasViews) { this.mCountDownView = hasViews.internalFindViewById(R.id.cdpv_start); if (this.mCountDownView != null) { this.mCountDownView.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { StartActivity_.this.onCountDownClick(); } } ); } afterViews(); } //创建UI线程 @Override void startMain() { UiThreadExecutor.runTask("", new Runnable() { @Override public void run() { StartActivity_.super.startMain(); } } , 0L); } //创建子线程 @Override void countDown() { BackgroundExecutor.execute(new BackgroundExecutor.Task("delay_to_main", 2000L, "") { @Override public void execute() { try { StartActivity_.super.countDown(); } catch (final Throwable e) { Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e); } } } ); }}
从上面的代码可以看到,生成的StartActivity_子类中有本类定义的方法,还有对注解事件的实现。
原理大概了解了之后,就是AndroidAnnotations的使用了,使用AndroidAnnotations需要导入:
//AndroidAnnotationsimplementation 'org.androidannotations:androidannotations:4.3.1'
在AndroidManifest中注册使用@EActivity标记的Activity需要使用框架生成的Activity,如:
<activity android:name=".moudle.login.LoginActivity_" android:screenOrientation="portrait" />**
同样,使用@EApplication标记的Application也需要使用框架生成的子类,如:
android:name=".application.XXXXApplication_"
Service也同样类似,都需要使用框架生成的子类。
AndroidAnnotations全部的注解文档在其GitHub上可以查阅:
https://github.com/androidannotations/androidannotations/wiki/AvailableAnnotations
4、注解框架对比
这张图片引用自CSDN,Annotations使用的复杂度稍高于另外两个框架,并不是指接入特别困难。
Retrofit+OkHttp
Retrofit是一个请求框架,和Okhttp出自同一个团队,OkHttp负责进行底层的网络请求,Retrofit负责对请求事件进行装配,对请求结果进行解析。Retrofit对请求进行封装,很大程度上降低了代码的耦合程度。
引入:
implementation 'com.squareup.okhttp3:okhttp:3.8.1' //okhttp implementation 'com.squareup.okhttp3:logging-interceptor:3.8.1' //okhttp拦截器 implementation 'com.squareup.retrofit2:retrofit:2.0.2' //retrofit implementation 'com.squareup.retrofit2:converter-gson:2.0.2' //retrofit的Gson解析器 implementation 'com.squareup.retrofit2:adapter-rxjava:2.0.2' //retrofit的RxJava适配器支持 implementation 'com.google.code.gson:gson:2.7' //Gson
RxJava
RxJava是一个很火的线程调度框架,这样描述或许不太准确,想要深入了解的可以打开传送门:
《给 Android 开发者的 RxJava 详解》
《RxJava 与 Retrofit 结合的最佳实践》
引入:
implementation 'io.reactivex:rxandroid:1.0.1' //RxAndroid implementation 'io.reactivex.rxjava2:rxjava:2.1.1' //RxJava
MVP搭建
使用AndroidAnnotations和Retrofit+Rxjava2+okhttp3都简化了代码的逻辑,降低了代码的耦合度,再引入MVP的架构,则会让项目更加清晰明了。MVP的概念已经很早就诞生了,经过了很长时间的发展,依然被很多人采用,存在即是合理,接下来就整个框架进行展示。
1、Retrofit+Okhttp封装
首先,需要对Retrofit和Okhttp进行初始化:
/** * @author WuYang * @version v1.0 设置网络请求 */public abstract class RetrofitSingleton { protected static ApiInterface apiService = null; public static Retrofit retrofit = null; public static OkHttpClient okHttpClient = null; //此处的ApiInterface就是通过注解定义请求接口的类 public static void init() { initOkHttp(); initRetrofit(); if (retrofit != null) apiService = retrofit.create(ApiInterface.class); } //初始化OkHttp private static void initOkHttp() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); //如果不是发布版本,则初始化logging拦截器 if (!TzcmApplication_.isRelease) { HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); builder.addInterceptor(loggingInterceptor); } else { Log.i("温馨提示", "反编译及攻击本软件将承担法律风险!请勿以身试法!"); } //是否强制取消代理来防止抓包 if (TzcmApplication_.isCancelProxy) { builder.proxy(Proxy.NO_PROXY); } //设置超时 builder.connectTimeout(90, TimeUnit.SECONDS); builder.readTimeout(90, TimeUnit.SECONDS); builder.writeTimeout(90, TimeUnit.SECONDS); //错误重连、断线重连 builder.retryOnConnectionFailure(false); //添加自定义请求头捕获器,如token等 builder.addInterceptor(new AddCookiesInterceptor()); okHttpClient = builder.build(); } //初始化Retrofit private static void initRetrofit() { retrofit = new Retrofit.Builder() .baseUrl(XXXXApplication_.isRelease ? ApiInterface.API_HOST_ONLINE : ApiInterface.API_HOST_OFFLINE) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); } //拦截器,添加一些固定的参数。如:token、deviceId、cookie等 public static class AddCookiesInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request.Builder builder = chain.request().newBuilder(); //添加token if (UserStateUtil.getInstance().isLoggedOn()) { builder.addHeader(CommonKey.ApiParams.TOKEN, UserStateUtil.getInstance().getToken()); } return chain.proceed(builder.build()); } }}
接下来就创建ApiInterface:
/** * @author WuYang * @version v1.0 API接口 */public interface ApiInterface { String API_HOST_OFFLINE = "http://www.xxxxxxxx.com/";//线下的api 地址 String API_HOST_ONLINE = "http://www.xxxxxxxx.com/";//线上地址 String API_LOGIN = "ChujiMallServer/app/appDelivery/api/deliveryLogin"; //用户登录 @FormUrlEncoded @POST(API_LOGIN) Observable<UserInfoResponse> mLoginAPI(@FieldMap Map<String, Object> params);
定义好接口之后需要对接口声明的方法进行实现:
public class RetrofitApi extends RetrofitSingleton { private volatile static RetrofitApi instance; /** * Returns singleton class instance * 使用单例模式中的双重加锁 */ public static RetrofitApi getInstance() { if (instance == null) { synchronized (RetrofitApi.class) { if (instance == null) { instance = new RetrofitApi(); } } } return instance; } /** * 登录 * @param account 账号,必填 * @param password 密码,必填 */ public Observable login(String account, String password) { HashMap<String, Object> params = new HashMap<>(); params.put(CommonKey.ApiParams.LOGIN_ID, account); params.put(CommonKey.ApiParams.PASSWORD, password); return apiService.mLoginAPI(params); }}
然后再Application中进行init:
@EApplicationpublic class XXXXApplication extends Application { @Override public void onCreate() { super.onCreate(); initRetrofit(); } private void initRetrofit() { RetrofitSingleton.init(); }}
2、BaseView、BasePresenter
定义接口BaseView和BasePresenter。
public interface BaseView<T extends BasePresenter> { //设置Presenter void setPresenter(T presenter); //获取上下文 Activity getContext(); //可以添加其余在View层的方法 ... ...}public interface BasePresenter { //提供一个公用的开始方法 void start(); //可以添加一些其余公用的方法,如showLoading(),dismissLoading()等。 ... ...}
3、使用RxJava配合Retrofit进行请求:
/** * @author YangWu * @description 请求接口基类 */public abstract class OnRequestCallback<T extends BaseResponse> extends Subscriber<T> { public abstract void onFailed(int code, String respMsg, String respCode, T response); public abstract void onException(Throwable e); public abstract void onResponse(T response); public abstract void onFinish(); public void onStart() { if (!RxNetTool.isAvailable(TzcmApplication_.getInstance())) { onFailed(-1, "网络连接异常,请检查网络连接", "E99999", null); onFinish(); unsubscribe(); return; } } public final void onCompleted() { onFinish(); } public final void onError(Throwable e) { try { onException(e); onFinish(); } catch (Exception ex) { e.printStackTrace(); } } public final void onNext(T response) { if (!Null.isNull(response) && response.isSuccess()) { onResponse(response); } else { onFailed(response.code, response.respMsg, response.respCode, response); } }}/** * @author YangWu * @description 请求接口 */public abstract class OnSimpleRequestCallback<T extends BaseResponse> extends OnRequestCallback<T> { private Context mContext; public OnSimpleRequestCallback(Context context) { this.mContext = context; } @Override public void onStart() { super.onStart(); } @Override public void onFinish() { } @Override public void onFailed(int code, String respMsg, String respCode, T response) { ToastUtils.show(respMsg); XPopupManager.dismissLoading(); } @Override public void onException(Throwable e) { Log.e(mContext.getPackageName(), e.getMessage()); XPopupManager.dismissLoading(); }}
定义一个BaseResponse来统一接口请求的数据格式:
/** * @author YangWu * @description 请求体基类 */public class BaseResponse implements Serializable { public String respMsg; public String respCode; public int code; public boolean isSuccess() { return code == 200; }}
4、使用RxJava管理请求
到这里,框架需要的基本东西已经搭建完成了。接下来使用RxJava对请求的队列进行管理:
/** * @author YangWu * @description RxJavaActionManager 请求队列操作接口 */public interface RxActionManager<T> { void add(T tag, Subscription subscription); //添加请求 void remove(T tag); //移除请求 void cancel(T tag); //取消请求 void cancelAll(); //取消所有请求}/** * @author YangWu * @description RxApi管理器 */public class RxApiManager implements RxActionManager<Object> { private static RxApiManager sInstance = null; private ArrayMap<Object, Subscription> maps; public static RxApiManager get() { if (sInstance == null) { synchronized (RxApiManager.class) { if (sInstance == null) { sInstance = new RxApiManager(); } } } return sInstance; } @TargetApi(Build.VERSION_CODES.KITKAT) private RxApiManager() { maps = new ArrayMap<>(); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public void add(Object tag, Subscription subscription) { maps.put(tag, subscription); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public void remove(Object tag) { if (!maps.isEmpty()) { maps.remove(tag); } } @TargetApi(Build.VERSION_CODES.KITKAT) public void removeAll() { if (!maps.isEmpty()) { maps.clear(); } } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public void cancel(Object tag) { if (maps.isEmpty()) { return; } if (maps.get(tag) == null) { return; } if (!maps.get(tag).isUnsubscribed()) { maps.get(tag).unsubscribe(); maps.remove(tag); } } @TargetApi(Build.VERSION_CODES.KITKAT) @Override public void cancelAll() { if (maps.isEmpty()) { return; } Set<Object> keys = maps.keySet(); for (Object apiKey : keys) { cancel(apiKey); } }}
使用
还是以Login为例,首先实现一个LoginContract对View层和Presenter层的方法进行管理。
public class LoginContract { //LoginActivity中需要实现的方法LoginSuccess(); interface View extends BaseView<Presenter> { void loginSuccess(); } //LoginPresenter中需要实现的方法login(); interface Presenter extends BasePresenter { void login(String number, String password); }}
然后对LoginActivity进行编写:
@EActivity(R.layout.activity_login)public class LoginActivity extends BaseActivity implements LoginContract.View { private LoginContract.Presenter mPresenter; //查找组件 @ViewById(R.id.til_login_account) TextInputLayout mAccountTil; @ViewById(R.id.et_login_account) EditText mAccountEt; @ViewById(R.id.til_login_password) TextInputLayout mPasswordTil; @ViewById(R.id.et_login_password) EditText mPasswordEt; @ViewById(R.id.tv_login) TextView mLoginTv; //依赖注入后设置StatusBar模式为沉浸式,并且实例化Presenter @AfterInject void afterInject() { StatusBarUtil.immersive(this); new LoginPresenter(this); } @Click(R.id.tv_login) void onLoginTvClick() { String account = mAccountEt.getText().toString().trim(); String password = mPasswordEt.getText().toString().trim(); if (PublicUtils.isEmpty(account)) { AnimationUtils.startShakeByViewIsNeedVibrate(mAccountTil, true); mAccountEt.setError("请输入账号"); return; } if (PublicUtils.isEmpty(password)) { AnimationUtils.startShakeByViewIsNeedVibrate(mPasswordTil, true); mPasswordEt.setError("请输入密码"); return; } mPresenter.login(account, password); } //View层方法,登录成功后在Presenter层会回调此方法 @Override public void loginSuccess() { MainActivity_.intent(getContext()).start(); finish(); } @Override public void onBackPressed() { if (RxTool.isFastClick(2000)) { TzcmApplication_.getInstance().exitApp(); } else { ToastUtils.show("再按一次退出程序"); } } //将Presenter绑定到View层 @Override public void setPresenter(LoginContract.Presenter presenter) { mPresenter = Null.checkNotNull(presenter); } //View层实现的获取上下文的方法 @Override public Activity getContext() { return this; } @Override protected void onDestroy() { super.onDestroy(); //在页面销毁时通过RxJava取消登录请求 RxApiManager.get().cancel(ApiInterface.API_LOGIN); }}
可以看到,在点击登录按钮时调用了mPresenter.login(account, password);
方法,这个方法在P层进行处理:
/** * @author: WuYang * @describe: 登录Presenter */public class LoginPresenter implements LoginContract.Presenter { private LoginContract.View mView; //绑定View层 public LoginPresenter(@NonNull LoginContract.View view) { mView = Null.checkNotNull(view); mView.setPresenter(this); } //继承自Presenter的方法,可以处理一些页面初始化的逻辑 @Override public void start() { } /** * 在View层调用login方法 * 通过Retrofit的subscribe观察者模式进行请求,使用上面的RxJava的请求框架OnSimpleRequestCallback进行请求 * 接收的UserInfoResponse是继承于BaseResponse的子类,根据业务逻辑具有自己的属性 * 请求成功后通过mView.loginSuccess();方法回调到View层进行处理 */ @Override public void login(String number, String password) { XPopupManager.showLoading(mView.getContext(), "正在登录", ApiInterface.API_LOGIN); Subscriber subscriber = new OnSimpleRequestCallback<UserInfoResponse>(mView.getContext()) { @Override public void onResponse(UserInfoResponse response) { UserStateUtil.getInstance().updateUser(response.data); XPopupManager.dismissLoading(); mView.loginSuccess(); } }; RxApiManager.get().add(ApiInterface.API_LOGIN, subscriber); RetrofitApi.getInstance().login(number, password) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(subscriber); }}
总结
通过这样的构建方式可以很好的将代码解耦,便于项目后期的维护和开发。接下来我会将Small插件化框架再融合进行,然后通过Gradle统一管理。后续我会将搭建的这个框架代码上传至github,应该就是这几天,要从qq空间和公众号转移过来的东西有点多。
长路漫漫,菜不是原罪,堕落才是原罪。
我的CSDN:https://blog.csdn.net/wuyangyang_2000
我的简书:https://www.jianshu.com/u/20c2f2c3560a
我的掘金:https://juejin.im/user/58009b94a0bb9f00586bb8a0
我的GitHub:https://github.com/wuyang2000
个人网站:http://www.xiyangkeji.cn
个人app(茜茜)蒲公英连接:https://www.pgyer.com/KMdT
我的微信公众号:茜洋 (定期推送优质技术文章,欢迎关注)
Android技术交流群:691174792
以上文章均可转载,转载请注明原创。
更多相关文章
- Android之Volley框架加载网络图片
- [置顶] android 框架
- 【Android多媒体】Android5.0 NuPlayer多媒体框架【1】
- 【开源项目2】Android推送框架 androidpn
- Retrofit2 ,Dagger2等常用框架注解功能介绍
- 【Android(安卓)- 框架】之可悬浮列表StickyHeadersRecyclerView
- Android缓存机制&一个缓存框架推荐
- 使用java语言中的注解生成器生成代码
- Android游戏框架libgdx——BitmapFont的构造