用到的jar包版本
compile "com.squareup.okhttp3:okhttp:3.9.0"
compile "com.squareup.okhttp3:logging-interceptor:3.9.0"
compile "com.squareup.retrofit2:retrofit:2.3.0"
compile "com.squareup.retrofit2:converter-gson:2.3.0"
compile "com.squareup.retrofit2:adapter-rxjava2:2.3.0"
compile "io.reactivex.rxjava2:rxjava:2.0.6"
compile "io.reactivex.rxjava2:rxandroid:2.0.6"
compile "com.trello.rxlifecycle2:rxlifecycle:2.2.1"
compile "com.trello.rxlifecycle2:rxlifecycle-components:2.2.1"
compile "com.github.VictorAlbertos.RxCache:runtime:1.8.1-2.x"
compile 'com.github.VictorAlbertos.Jolyglot:gson:0.0.3'
话不多说直接上代码
- 管理类
public class ApiManager {
private static ApiManager apiManager;
public GankApi gankApi;
public GankApiCacheProvider gankApiCacheProvider;
public static ApiManager getInstance() {
if (apiManager == null) {
synchronized (ApiManager.class) {
if (apiManager == null) {
apiManager = new ApiManager();
}
}
}
return apiManager;
}
private ApiManager() {
OkHttpClient okHttpClient = newOkHttpClient();
gankApi = new Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(ApiConfig.BASE_GANK_API_URL)
.build()
.create(GankApi.class);
File file = FsUtils.createOnNotFound(new File(App.getInstance().getCacheDir(), "RxCache"));
RxCache rxCache = new RxCache.Builder()
.setMaxMBPersistenceCache(50)
.persistence(file, new GsonTSpeaker());//解析缓存的一个类
gankApiCacheProvider = rxCache.using(GankApiCacheProvider.class);
}
private OkHttpClient newOkHttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cookieJar(new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(App.getContext())))//cookice设置
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.addInterceptor(new GzipRequestInterceptor())//使用gizp压缩数据,可以减少流量,也可以不加
.addInterceptor(new HeaderInterceptor());//头部信息拦截器,也可以不加
if (BuildConfig.DEBUG) {//请求日志
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(loggingInterceptor);
StethoUtils.addNetworkInterceptor(builder);
}
return builder.build();
}
}
import com.google.gson.Gson;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import io.victoralbertos.jolyglot.JolyglotGenerics;
import io.victoralbertos.jolyglot.Types;
/**
* Created by Xiong Ke on 2017/8/23.
*/
public class GsonTSpeaker implements JolyglotGenerics {
private final Gson gson;
public GsonTSpeaker(Gson gson) {
this.gson = gson;
}
public GsonTSpeaker() {
this.gson = new Gson();
}
/**
* {@inheritDoc}
*/
@Override
public String toJson(Object src) {
return gson.toJson(src);
}
/**
* {@inheritDoc}
*/
@Override
public String toJson(Object src, Type typeOfSrc) {
return gson.toJson(src, typeOfSrc);
}
/**
* {@inheritDoc}
*/
@Override
public <T> T fromJson(String json, Class<T> classOfT) throws RuntimeException {
Type genType = classOfT.getGenericSuperclass();
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
return gson.fromJson(json, params[0]);
}
/**
* {@inheritDoc}
*/
@Override
public <T> T fromJson(String json, Type typeOfT) throws RuntimeException {
Type[] params = ((ParameterizedType) typeOfT).getActualTypeArguments();
return gson.fromJson(json, params[0]);
}
/**
* {@inheritDoc}
*/
@Override
public <T> T fromJson(File file, Class<T> classOfT) throws RuntimeException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file.getAbsoluteFile()));
Type genType = classOfT.getGenericSuperclass();
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
T object = gson.fromJson(reader, params[0]);
reader.close();
return object;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException i) {
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public <T> T fromJson(File file, Type typeOfT) throws RuntimeException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file.getAbsoluteFile()));
Type[] params = ((ParameterizedType) typeOfT).getActualTypeArguments();
T object = gson.fromJson(reader, params[0]);
reader.close();
return object;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException i) {
}
}
}
}
@Override
public GenericArrayType arrayOf(Type componentType) {
return Types.arrayOf(componentType);
}
/**
* {@inheritDoc}
*/
@Override
public ParameterizedType newParameterizedType(Type rawType, Type... typeArguments) {
return Types.newParameterizedType(rawType, typeArguments);
}
}
package com.xk.gvido.app.model.net.interceptor;
import android.support.annotation.NonNull;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.Buffer;
import okio.BufferedSink;
import okio.GzipSink;
import okio.Okio;
/**
* Created by Xiong Ke on 2017/8/23.
*/
public class GzipRequestInterceptor implements Interceptor {
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
return chain.proceed(originalRequest);
}
Request compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method(), forceContentLength(gzip(originalRequest.body())))
.build();
return chain.proceed(compressedRequest);
}
private RequestBody forceContentLength(final RequestBody requestBody) throws IOException {
final Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
return new RequestBody() {
@Override
public MediaType contentType() {
return requestBody.contentType();
}
@Override
public long contentLength() {
return buffer.size();
}
@Override
public void writeTo(@NonNull BufferedSink sink) throws IOException {
sink.write(buffer.snapshot());
}
};
}
private RequestBody gzip(final RequestBody body) {
return new RequestBody() {
@Override
public MediaType contentType() {
return body.contentType();
}
@Override
public long contentLength() {
return -1; // We don't know the compressed length in advance!
}
@Override
public void writeTo(@NonNull BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
}
提几个我遇到过的错误的心得体会:
a. 项目cookice丢失
OkHttpClient 多次创建导致cookice丢失,一个项目只能实例化一个OkHttpClient
b. 提取缓存是报了一个转换异常
日志我就不贴了,加入GsonTSpeaker转换一下就行,(只针对json,别的数据格式不支持)
c. 缓存创建目录设置在app内部缓存里面,防止无法创建目录引起异常
d. rxcache主要就是防止多次刷新请求服务器,浪费资源
- 错误处理类
public class ApiError {
//HTTP错误
private static final int UNAUTHORIZED = 401;
private static final int FORBIDDEN = 403;
private static final int NOT_FOUND = 404;
private static final int REQUEST_TIMEOUT = 408;
private static final int INTERNAL_SERVER_ERROR = 500;
private static final int BAD_GATEWAY = 502;
private static final int SERVICE_UNAVAILABLE = 503;
private static final int GATEWAY_TIMEOUT = 504;
//解析错误
private static final int PARSE_ERROR = 1000;
//网络错误
private static final int NETWORK_ERROR = 1001;
//协议出错
private static final int HTTP_ERROR = 1002;
//服务器出错
private static final int RESULT_ERROR = 1003;
//未知错误
public static final int UNKNOWN = 1004;
//緩存错误
private static final int CACHE_ERROR = 1005;
public static ApiException handleThrowable(Throwable e) {
ApiException ex;
if (e instanceof HttpException) { //HTTP错误
HttpException httpException = (HttpException) e;
ex = new ApiException(e, HTTP_ERROR);
switch (httpException.code()) {
case UNAUTHORIZED:
case FORBIDDEN:
case NOT_FOUND:
case REQUEST_TIMEOUT:
ex.setCode(REQUEST_TIMEOUT);
ex.setDisplayMessage(StringUtils.getString(R.string.error_network_request_timeout));
case GATEWAY_TIMEOUT:
case INTERNAL_SERVER_ERROR:
case BAD_GATEWAY:
case SERVICE_UNAVAILABLE:
default:
ex.setDisplayMessage(StringUtils.getString(R.string.error_network_network_error));
break;
}
return ex;
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
ex = new ApiException(e, PARSE_ERROR);
ex.setDisplayMessage(StringUtils.getString(R.string.error_parsing_error));
} else if (e instanceof ConnectException) {
ex = new ApiException(e, HTTP_ERROR);
ex.setDisplayMessage(StringUtils.getString(R.string.error_network_connection_failure));
} else if (e instanceof ExecutionException
|| e instanceof InterruptedException) {
ex = new ApiException(e, UNKNOWN);
ex.setDisplayMessage(StringUtils.getString(R.string.error_network_get_image_failure));
} else if (e instanceof RxCacheException) {
ex = new ApiException(e, CACHE_ERROR);
ex.setDisplayMessage(StringUtils.getString(R.string.error_network_cache_failure));
} else if (e instanceof CompositeException) {
CompositeException compositeE = (CompositeException) e;
ex = new ApiException(e);
for (Throwable throwable : compositeE.getExceptions()) {
if (throwable instanceof SocketTimeoutException) {
ex.setCode(NETWORK_ERROR);
ex.setDisplayMessage(StringUtils.getString(R.string.error_network_network_error));
} else if (throwable instanceof ConnectException) {
ex.setCode(NETWORK_ERROR);
ex.setDisplayMessage(StringUtils.getString(R.string.error_network_network_error));
} else if (throwable instanceof UnknownHostException) {
ex.setCode(NETWORK_ERROR);
ex.setDisplayMessage(StringUtils.getString(R.string.error_network_network_error));
} else if (throwable instanceof RxCacheException) {
ex.setCode(CACHE_ERROR);
ex.setDisplayMessage(StringUtils.getString(R.string.error_network_network_error));
} else if (throwable instanceof MalformedJsonException) {
ex.setCode(PARSE_ERROR);
ex.setDisplayMessage(StringUtils.getString(R.string.error_parsing_error));
}
}
} else {
ex = new ApiException(e, UNKNOWN);
ex.setDisplayMessage(e.getMessage());
}
return ex;
}
public class ApiException extends RuntimeException {
private int code;
private String displayMessage;
public ApiException(Throwable throwable) {
super(throwable);
}
public ApiException(Throwable throwable, int code) {
super(throwable);
this.code = code;
}
public void setDisplayMessage(String displayMessage) {
this.displayMessage = displayMessage;
}
public String getDisplayMessage() {
return displayMessage;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
错误你可以自己加,缺少的字符串我就不贴了
- rx调度线程的工具类
public class RxSchedulersHelper {
public static <T> ObservableTransformer<T, T> io_main() {
return upstream -> upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io());
}
public static <T> ObservableTransformer<T, T> io_io() {
return upstream -> upstream.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io());
}
public static <T> ObservableTransformer<T, T> main_main() {
return upstream -> upstream.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io());
}
public static <T, C> ObservableTransformer<T, T> addDisposable(final C context) {
if (context instanceof BaseFragment) {
BaseFragment baseFragment = (BaseFragment) context;
return baseFragment.bindToLifecycle();
} else if (context instanceof BasePreferenceFragment) {
BasePreferenceFragment basePreferenceFragment = (BasePreferenceFragment) context;
return basePreferenceFragment.bindToLifecycle();
} else if (context instanceof BaseActivity) {
BaseActivity baseActivity = (BaseActivity) context;
return baseActivity.bindToLifecycle();
} else {
return upstream -> upstream;
}
}
public static <T> ObservableTransformer<T, T> errorResult() {
return upstream -> upstream.onErrorResumeNext((Function<Throwable, ObservableSource<? extends T>>)
throwable -> {
return Observable.error(ApiError.handleThrowable(throwable));
});
}
public static <T> Observable<T> createData(final T t) {
return Observable.create(observableEmitter -> {
if (!observableEmitter.isDisposed()) {
try {
observableEmitter.onNext(t);
observableEmitter.onComplete();
} catch (Exception e) {
observableEmitter.onError(e);
}
}
});
}
}
说明
a.
io_main
,io_io
,main_main
线程的调度方法
b.addDisposable
这个方法很重要,为了防止请求完成 界面销毁了,造成的内存泄漏需要说明的是BaseFragment
要继承RxFragment
,BasePreferenceFragment
,BaseActivity
也都要继承RxPreferenceFragment
,RxAppCompatActivity
c.errorResult
发生错误将会发送一个消息到error方法
d.createData
创建一个Observable
对象
- 数据结果处理类
public class ApiResultHandler {
public static <T> ObservableTransformer<ServerGanHuoResponse<T>, T> handleGanHuoResult() {
return upstream -> upstream.flatMap((Function<ServerGanHuoResponse<T>, ObservableSource<T>>) ganHuoResponse -> {
if (!ganHuoResponse.isError()) {
return RxSchedulersHelper.createData(ganHuoResponse.getResults());
} else {
return Observable.error(new Throwable("服务器返回error"));
}
});
}
}
- 接口API
public interface GankApi {
/**
* 福利列表
*/
@GET("data/福利/{count}/{pageIndex}")
Observable<ServerGanHuoResponse<List<WelfareBean>>> getGanHuoWelfareApiCall(@Path("count") @IntRange(from = 1) int count,
@Path("pageIndex") @IntRange(from = 1) int pageIndex);
/**
* 休息视频
*/
@GET("data/休息视频/{count}/{pageIndex}")
Observable<ServerGanHuoResponse<List<VideoBean>>> getGanHuoVideoApiCall(@Path("count") @IntRange(from = 1) int count,
@Path("pageIndex") @IntRange(from = 1) int pageIndex);
}
6.缓存API
public interface GankApiCacheProvider {
/**
* 福利列表
*
* @param observable
* @param page 緩存key
* @param evictProvider 清楚缓存
* @return
*/
@Expirable(value = false)
@LifeCache(duration = 6, timeUnit = TimeUnit.HOURS)
Observable<ServerGanHuoResponse<List<WelfareBean>>> getGanHuoWelfareApiCall(Observable<ServerGanHuoResponse<List<WelfareBean>>> observable, DynamicKey page, EvictProvider evictProvider);
/**
* 休息视频
*
* @param observable
* @param page 緩存key
* @param evictProvider 清楚缓存
* @return
*/
@Expirable(value = false)
@LifeCache(duration = 6, timeUnit = TimeUnit.HOURS)
Observable<ServerGanHuoResponse<List<VideoBean>>> getGanHuoVideoApiCall(Observable<ServerGanHuoResponse<List<VideoBean>>> observable, DynamicKey page, EvictProvider evictProvider);
}
7.处理类
public class GankApiHelper {
public static <T> Observable<List<WelfareBean>> getGanHuoWelfareApiCall(final T context, @IntRange(from = 1) int count,
@IntRange(from = 1) int pageIndex, boolean isClearCache) {
return ApiManager.getInstance().gankApiCacheProvider
.getGanHuoWelfareApiCall(ApiManager.getInstance().gankApi.getGanHuoWelfareApiCall(count, pageIndex),
new DynamicKey(pageIndex), new EvictDynamicKey(isClearCache))
.compose(ApiResultHandler.handleGanHuoResult())
.compose(RxSchedulersHelper.addDisposable(context))
.compose(RxSchedulersHelper.errorResult())
.compose(RxSchedulersHelper.io_main());
}
public static <T> Observable<List<VideoBean>> getGanHuoVideoApiCall(final T context, @IntRange(from = 1) int count,
@IntRange(from = 1) int pageIndex, boolean isClearCache) {
return ApiManager.getInstance().gankApiCacheProvider
.getGanHuoVideoApiCall(ApiManager.getInstance().gankApi.getGanHuoVideoApiCall(count, pageIndex),
new DynamicKey(pageIndex), new EvictDynamicKey(isClearCache))
.compose(ApiResultHandler.handleGanHuoResult())
.compose(RxSchedulersHelper.addDisposable(context))
.compose(RxSchedulersHelper.errorResult())
.compose(RxSchedulersHelper.io_main());
}
}
说明
由于我使用了rxcache所以为了方便使用写了一个helper类做一下处理工作,如果你的项目里没用rxcache就可以删掉
GankApiCacheProvider
8.调用
Observable.zip(GankApiHelper.getGanHuoVideoApiCall(this, REQ_COUNT, currentPage, isClearCache),
GankApiHelper.getGanHuoWelfareApiCall(this, REQ_COUNT, currentPage, isClearCache),
(videoBeen, welfareDatas) -> {
List<GanHuoBean> ganHuoBeanList = new ArrayList<>();
if (videoBeen.size() == REQ_COUNT && welfareDatas.size() == REQ_COUNT) {
for (int i = 0; i < REQ_COUNT; i++) {
ganHuoBeanList.add(GanHuoBean.newInstance(videoBeen.get(i),
welfareDatas.get(i)));
}
}
return ganHuoBeanList;
}).subscribe(new RxSubscriber<List<GanHuoBean>>() {
@Override
public void onSubscribe(Disposable d) {
super.onSubscribe(d);
if (isRefresh) {
swipeRefreshLayout.setRefreshing(true);
}
}
@Override
public void rxOnNext(List<GanHuoBean> ganHuoBeans) {
gankPictureAdapter.loadMoreComplete();
if (ganHuoBeans.size() < REQ_COUNT) {
gankPictureAdapter.loadMoreEnd();
}
if (isRefresh) {
gankPictureAdapter.setNewData(ganHuoBeans);
swipeRefreshLayout.setRefreshing(false);
} else {
gankPictureAdapter.addData(ganHuoBeans);
}
if (gankPictureAdapter.getData().isEmpty()) {
loadingLayout.showEmpty();
} else {
loadingLayout.showContent();
}
saveMeiZiInDb(ganHuoBeans);
}
@Override
public void rxOnError(ApiException apiException) {
if (gankPictureAdapter.getData().isEmpty()) {
loadingLayout.setErrorText(apiException.getDisplayMessage());
loadingLayout.showError();
} else {
gankPictureAdapter.loadMoreFail();
}
swipeRefreshLayout.setRefreshing(false);
}
});
说明
我是将两个请求的结果转换成一个新的数据结构,所以是这样写的