个人原因,此系列更新已停止,抱歉。
上一篇《看我开发干货集中营App(二) ~ APP初始化》中讲述了此APP所依赖的一些第三方库,包括Retrofit2,Okhttp3,RxJava,Gilde等,对于使用第三方依赖库,我自己一直都秉承着封装一层在使用的原则。这样做的好处除了方便实际使用外,就是万一需要替换对应依赖库不用满世界去修改代码,只需要在封装入口处修改就好了。关于使用第三方开源项目,大家可以参考下这篇文字:如何正确使用开源项目?
接下来介绍下上面几个库的基本封装使用,提醒下以下内容将会基于你已经了解过对应的开源项目,而且知道如何使用,所以如果还未了解过相关库的请自行Google了解一番再回来看哦。
OK,先预览下《干货精选》的目录结构:
关于Android App目录结构,个人比较推荐按模块分类。直接看看包名下的目录:
-
commonui
: 通用UI封装类存放目录 -
core
: 存放底层核心的封装类,如retrofit,rxjava等 -
modules
: 按模块存放文件目录,主要是mvp开发模式的文件,如Google Todo-Mvp分支,
Retrofit2封装
创建工具类:core/retofit/RetrofitHelper
RetrofitHelper特性:
- 单例形式创建Retrofit实例;
- 使用okhttp3作为请求客户端;
- 使用gson作为数据转换器;
- 使用RxJava优化异步请求流程;
- 开启数据缓存,无网络时可从缓存读取数据;
- 辅助类静态方法获取Retrofit Service实例。
节省篇幅,上代码看注释:
public class RetrofitHelper {
private volatile static Retrofit retrofitInstance = null;
/**
* 创建Retrofit请求Api
* @param clazz Retrofit Api接口
* @return api实例
*/
public static <T> T createApi(Class<T> clazz){
return getInstance().create(clazz);
}
// ===============================================================
// private methods =================================================
/**
* 获取Retrofit实例
* @return Retrofit
*/
private static Retrofit getInstance(){
if(null == retrofitInstance){
synchronized (Retrofit.class){
if(null == retrofitInstance){ // 双重检验锁,仅第一次调用时实例化
retrofitInstance = new Retrofit.Builder()
// baseUrl总是以/结束,@URL不要以/开头
.baseUrl(BuildConfig.API_SERVER_URL)
// 使用OkHttp Client
.client(buildOKHttpClient())
// 集成RxJava处理
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
// 集成Gson转换器
.addConverterFactory(buildGsonConverterFactory())
.build();
}
}
}
return retrofitInstance;
}
/**
* 构建OkHttpClient
* @return OkHttpClient
*/
private static OkHttpClient buildOKHttpClient(){
// 添加日志拦截器,非debug模式不打印任何日志
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(AppUtil.isDev() ? HttpLoggingInterceptor.Level.HEADERS : HttpLoggingInterceptor.Level.NONE) ;
return new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor) // 添加日志拦截器
//.addInterceptor(buildTokenInterceptor()) // 添加token拦截器
.addNetworkInterceptor(buildCacheInterceptor()) // 添加网络缓存拦截器
.cache(getCache()) // 设置缓存文件
.retryOnConnectionFailure(true) // 自动重连
.connectTimeout(15, TimeUnit.SECONDS) // 15秒连接超时
.readTimeout(20, TimeUnit.SECONDS) // 20秒读取超时
.writeTimeout(20, TimeUnit.SECONDS) // 20秒写入超时
.build();
}
/**
* 获取缓存对象
* @return Cache
*/
private static Cache getCache(){
// 获取缓存目标,SD卡
File cacheFile = new File(AppUtil.getContext().getCacheDir(), ResourceUtil.getString(R.string.app_name_en));
// 创建缓存对象,最大缓存50m
return new Cache(cacheFile, 1024*1024*20);
}
/**
* 构建缓存拦截器
* @return Interceptor
*/
private static Interceptor buildCacheInterceptor(){
return new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 无网络连接时请求从缓存中读取
if (!AppUtil.isNetworkConnected()) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
// 响应内容处理
// 在线时缓存5分钟
// 离线时缓存4周
Response response = chain.proceed(request);
if (AppUtil.isNetworkConnected()) {
int maxAge = 300;
response.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
.build();
}else {
// 无网络时,设置超时为4周
int maxStale = 60 * 60 * 24 * 28;
response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.removeHeader("Pragma")
.build();
}
return response;
}
};
}
/**
* 构建GSON转换器
* @return GsonConverterFactory
*/
private static GsonConverterFactory buildGsonConverterFactory(){
GsonBuilder builder = new GsonBuilder();
builder.setLenient();
// 注册类型转换适配器
builder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
@Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return null == json ? null : new Date(json.getAsLong());
}
});
Gson gson = builder.create();
return GsonConverterFactory.create(gson);
}
}
其中有几点需要说明下:
- AppUtil是另外一个工具类,封装了APP常用的一些方法,如判断网络状态,获取App版本、缓存路径等。
-
API_SERVER_URL
是定义在 build.gradle 中的一个常量(干货集中营api基本URL),实际产品研发中可以使用同样方法定义不同开发环境(本地开发、测试、正式环境)的服务器地址。 - 缓存设置如果服务器响应Header不支持缓存,貌似缓存不起作用,欢迎大家帮忙测试。
使用方法:
假设有Retrofit Service Api如下:
public interface ArticleServiceApi {
@GET("day/{year}/{month}/{day}")
Observable<List<Article>> fetchByDate(@Path("year") String year, @Path("month") String month,
@Path("day") String day);
}
执行api请求如下:
RetrofitHelper.createApi(ArticleServiceApi.class)
.fetchByDate("2016", "09", "17")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Article>>() {
@Override
public void onCompleted() {}
@Override
public void onError(Throwable e) {}
@Override
public void onNext(List<Article> articles) {}
});
是不是比原始的请求简单不少。接下来对RxJava封装一层,使上面的请求更加简单点。
RxJava封装
再Retrofit中使用RxJava,需要封装的地方仅有2个地方,
- 切换线程操作
- 响应结果处理:onComplete()方法可选,通用错误响应,保留onSuccess()供用户调用
从干货集中营API返回结果可知,响应格式如下:
{
error: false,
results: [object, object]
}
// 或者
{
error: false,
category: [String,String...],
results: {
"Android": [{}, {}],
"IOS": [{}, {}]
}
}
为此,首先创建响应结果的实体基类 core/response/ResponseData
:
public class ResponseData {
private boolean error; // 是否请求错误
public boolean isError() {
return error;
}
public void setError(boolean error) {
this.error = error;
}
}
然后建立干货的实体类: modules/articles/entity/Article
,以及API响应结果实体类modules/articles/entity/DailyArticles
。
基于以上响应实体类,我们创建RxJava订阅者的封装基类:core/rx/RxSubscriber
public abstract class RxSubscriber<T> extends Subscriber<T> {
@Override
public void onCompleted() {
// 忽略操作,需要可覆写该方法
}
@Override
public void onError(Throwable e) {
if(e instanceof HttpException){
String errNetwork = ResourceUtil.getString(R.string.err_network);
LogUtil.error(e, "onError: " + errNetwork);
ToastUtil.show(errNetwork);
}
// TODO: 处理其它通用错误
// 覆写此方法自行处理异常,通用异常使用super.onError(e)保留
e.printStackTrace();
}
@Override
public void onNext(T t) {
if(t instanceof ResponseData){
ResponseData response = (ResponseData) t;
// 判断是否请求错误,出错直接转到onError()
if(response.isError()){
Throwable e = new Throwable(ResourceUtil.getString(R.string.err_default));
this.onError(e);
return;
}
}
onSuccess(t);
}
/**
* 请求成功回调
* @param t 最终响应结果
*/
public abstract void onSuccess(T t);
}
这里封装并没有做太多处理,主要是对公共的异常做了一次处理,保留onSuccess()更多关注业务处理。这样封装感觉还不是很好,比如错误处理可以通过事件方式传递给View等,不过因为暂时对Android研究不深,还没有更好的想法。
接下来,对线程切换操作再做一个封装:core/rx/RxJavaHelper
。
对线程切换的封装需要用到RxJava的 compose()
方法,如果不太了解它的使用,可以参考下:给 Android 开发者的 RxJava 详解。
public class RxJavaHelper {
/**
* 切换线程操作
* @return Observable转换器
*/
public static <T> Observable.Transformer<T, T> observeOnMainThread() {
return new Observable.Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> tObservable) {
return tObservable
.subscribeOn(Schedulers.io()) // 在io线程中请求
.observeOn(AndroidSchedulers.mainThread()); // 请求完成后返回主线程处理
}
};
}
}
封装基本完毕,再来看看请求api代码:
RetrofitHelper.createApi(ArticleServiceApi.class)
.fetchByDate("2016", "09", "01")
.compose(RxJavaHelper.<DailyArticles>observeOnMainThread())
.subscribe(new RxSubscriber<DailyArticles>() {
@Override
public void onSuccess(DailyArticles dailyArticles) {
LogUtil.debug("onSuccess: " + dailyArticles.getCategory().toString());
}
});
是不是有所改善~~
来看看请求结果:
D/OkHttp: --> GET http://gank.io/api/day/2016/09/01 http/1.1
D/OkHttp: --> END GET
D/OpenGLRenderer: Use EGL_SWAP_BEHAVIOR_PRESERVED: true
[ 09-21 20:50:57.557 2870: 2870 D/ ]
HostConnection::get() New Host Connection established 0xa230c570, tid 2870
[ 09-21 20:50:57.625 2870: 2918 D/ ]
HostConnection::get() New Host Connection established 0xae427510, tid 2918
I/OpenGLRenderer: Initialized EGL, version 1.4
D/OkHttp: <-- 200 OK http://gank.io/api/day/2016/09/01 (250ms)
D/OkHttp: Server: nginx/1.4.6 (Ubuntu)
D/OkHttp: Date: Wed, 21 Sep 2016 12:50:57 GMT
D/OkHttp: Content-Type: application/json
D/OkHttp: Content-Length: 4945
D/OkHttp: Connection: keep-alive
D/OkHttp: <-- END HTTP
D/GankEssence: ╔════════════════════════════════════════════════════════════════════════════════════════
D/GankEssence: ║ RxSubscriber.onNext (RxSubscriber.java:51)
D/GankEssence: ║ MainActivity$1.onSuccess (MainActivity.java:23)
D/GankEssence: ╟────────────────────────────────────────────────────────────────────────────────────────
D/GankEssence: ║ onSuccess: [瞎推荐, Android, 福利, 休息视频, iOS]
D/GankEssence: ╚════════════════════════════════════════════════════════════════════════════════════════
OK,还可以,Retrofit2 + RxJava 封装就暂时这样了。如果使用过程有什么问题再修改修改!!
本来还想再把Logger以及Glide的封装再说说,不过发现篇幅好长啊,而且Glide本身应实在够好了,不需要太多封装,只需封装个工具类方便我们自己使用就好了,Logger也是一样。大家有需要可以直接看看源码:ImageUtil 和 LogUtil。
本篇文章主要是简单说了第三方开源库的封装使用,其它的没说。对于Activity、Fragment等各类View组件使用,后面肯定会有封装一些适当的基类,特别是RecyclerView,会在后面开发过程提到的。
预告,下一篇《看我开发干货集中营App(四)~ 首页开发》将开始构建APP应用,功能点有RecyclerView使用、下拉刷新、上拉加载更多、HTML内容的解析等等。
Tags: 看我开发 Android开发 干货集中营 开放api Retroft2封装 RxJava封装 Glide封装
系列文章: