之前一直在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插件化和此框架进行融合。
注解(Annotation)也叫元数据。是一种代码级别的说明。它与类、接口、枚举在同一个层次。可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。说白了就是一种标记,可以用自己的注解解释器在编译时进行处理(编译时注解),或者在运行时通过反射对其进行处理(运行时注解)。注解框架有很多,常用的有ButterKnife、AndroidAnnotations、XUtils等。
ButterKnfie出自Jakewharton大神,是使用非常多的一个注解框架。
ButterKnife通过反射在编译时生成一个ViewBinder对象,类似于:
MainActivity$ViewBinder implements ButterKnife.ViewBinder
在这个类中就有使用注解声明的一些组件和监听。
因为ButterKnife在编译时通过反射一次性生成了这些需要使用到的类,所以对运行时的速度是没有什么影响的。
XUtils和ButterKnife类似,都使用了反射来对注解声明的字段或者对象进行赋值。
区别在于XUtils是在运行时进行反射,众所周知反射的效率相对于原生代码是较慢的。
虽然设备性能在提升,但是在组件、事件过多的情况下还是造成初始化速度降低。
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需要导入:
//AndroidAnnotations
implementation '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
这张图片引用自CSDN,Annotations使用的复杂度稍高于另外两个框架,并不是指接入特别困难。
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是一个很火的线程调度框架,这样描述或许不太准确,想要深入了解的可以打开传送门:
《给 Android 开发者的 RxJava 详解》
《RxJava 与 Retrofit 结合的最佳实践》
引入:
implementation 'io.reactivex:rxandroid:1.0.1' //RxAndroid
implementation 'io.reactivex.rxjava2:rxjava:2.1.1' //RxJava
使用AndroidAnnotations和Retrofit+Rxjava2+okhttp3都简化了代码的逻辑,降低了代码的耦合度,再引入MVP的架构,则会让项目更加清晰明了。MVP的概念已经很早就诞生了,经过了很长时间的发展,依然被很多人采用,存在即是合理,接下来就整个框架进行展示。
首先,需要对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:
@EApplication
public class XXXXApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initRetrofit();
}
private void initRetrofit() {
RetrofitSingleton.init();
}
}
定义接口BaseView和BasePresenter。
public interface BaseView<T extends BasePresenter> {
//设置Presenter
void setPresenter(T presenter);
//获取上下文
Activity getContext();
//可以添加其余在View层的方法
... ...
}
public interface BasePresenter {
//提供一个公用的开始方法
void start();
//可以添加一些其余公用的方法,如showLoading(),dismissLoading()等。
... ...
}
/**
* @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;
}
}
到这里,框架需要的基本东西已经搭建完成了。接下来使用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
以上文章均可转载,转载请注明原创。