package com.dl7.mvp.api; import android.support.annotation.NonNull; import android.text.TextUtils; import com.dl7.mvp.AndroidApplication; import com.dl7.mvp.api.bean.NewsDetailInfo; import com.dl7.mvp.api.bean.NewsInfo; import com.dl7.mvp.api.bean.PhotoInfo; import com.dl7.mvp.api.bean.PhotoSetInfo; import com.dl7.mvp.api.bean.SpecialInfo; import com.dl7.mvp.api.bean.WelfarePhotoInfo; import com.dl7.mvp.api.bean.WelfarePhotoList; import com.dl7.mvp.local.table.BeautyPhotoInfo; import com.dl7.mvp.local.table.VideoInfo; import com.dl7.mvp.utils.NetUtil; import com.dl7.mvp.utils.StringUtils; import com.orhanobut.logger.Logger; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import okhttp3.Cache; import okhttp3.CacheControl; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okio.Buffer; import retrofit2.Retrofit; import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; import rx.Observable; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Func1; import rx.schedulers.Schedulers; /** * Created by long on 2016/8/22. * 整个网络通信服务的启动控制,必须先调用初始化函数才能正常使用网络通信接口 */ public class RetrofitService { private static final String HEAD_LINE_NEWS = "T1348647909107"; //设缓存有效期为1天 static final long CACHE_STALE_SEC = 60 * 60 * 24 * 1; //查询缓存的Cache-Control设置,为if-only-cache时只查询缓存而不会请求服务器,max-stale可以配合设置缓存失效时间 private static final String CACHE_CONTROL_CACHE = "only-if-cached, max-stale=" + CACHE_STALE_SEC; //查询网络的Cache-Control设置 //(假如请求了服务器并在a时刻返回响应结果,则在max-age规定的秒数内,浏览器将不会发送对应的请求到服务器,数据由缓存直接返回) static final String CACHE_CONTROL_NETWORK = "Cache-Control: public, max-age=3600"; // 避免出现 HTTP 403 Forbidden,参考:http://stackoverflow.com/questions/13670692/403-forbidden-with-java-but-not-web-browser static final String AVOID_HTTP403_FORBIDDEN = "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11"; private static final String NEWS_HOST = "http://c.3g.163.com/"; private static final String WELFARE_HOST = "http://gank.io/"; private static INewsApi sNewsService; private static IWelfareApi sWelfareService; // 递增页码 private static final int INCREASE_PAGE = 20; private RetrofitService() { throw new AssertionError(); } /** * 初始化网络通信服务 */ public static void init() { // 指定缓存路径,缓存大小100Mb Cache cache = new Cache(new File(AndroidApplication.getContext().getCacheDir(), "HttpCache"), 1024 * 1024 * 100); OkHttpClient okHttpClient = new OkHttpClient.Builder().cache(cache) .retryOnConnectionFailure(true) .addInterceptor(sLoggingInterceptor) .addInterceptor(sRewriteCacheControlInterceptor) .addNetworkInterceptor(sRewriteCacheControlInterceptor) .connectTimeout(10, TimeUnit.SECONDS) .build(); Retrofit retrofit = new Retrofit.Builder() .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .baseUrl(NEWS_HOST) .build(); sNewsService = retrofit.create(INewsApi.class); retrofit = new Retrofit.Builder() .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .baseUrl(WELFARE_HOST) .build(); sWelfareService = retrofit.create(IWelfareApi.class); } /** * 云端响应头拦截器,用来配置缓存策略 * Dangerous interceptor that rewrites the server's cache-control header. */ private static final Interceptor sRewriteCacheControlInterceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); if (!NetUtil.isNetworkAvailable(AndroidApplication.getContext())) { request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build(); Logger.e("no network"); } Response originalResponse = chain.proceed(request); if (NetUtil.isNetworkAvailable(AndroidApplication.getContext())) { //有网的时候读接口上的@Headers里的配置,你可以在这里进行统一的设置 String cacheControl = request.cacheControl().toString(); return originalResponse.newBuilder() .header("Cache-Control", cacheControl) .removeHeader("Pragma") .build(); } else { return originalResponse.newBuilder() .header("Cache-Control", "public, " + CACHE_CONTROL_CACHE) .removeHeader("Pragma") .build(); } } }; /** * 打印返回的json数据拦截器 */ private static final Interceptor sLoggingInterceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { final Request request = chain.request(); Buffer requestBuffer = new Buffer(); if (request.body() != null) { request.body().writeTo(requestBuffer); } else { Logger.d("LogTAG", "request.body() == null"); } //打印url信息 Logger.w(request.url() + (request.body() != null ? "?" + _parseParams(request.body(), requestBuffer) : "")); final Response response = chain.proceed(request); return response; } }; @NonNull private static String _parseParams(RequestBody body, Buffer requestBuffer) throws UnsupportedEncodingException { if (body.contentType() != null && !body.contentType().toString().contains("multipart")) { return URLDecoder.decode(requestBuffer.readUtf8(), "UTF-8"); } return "null"; } /************************************ API *******************************************/ /** * 获取新闻列表 * @return */ public static Observable<NewsInfo> getNewsList(String newsId, int page) { String type; if (newsId.equals(HEAD_LINE_NEWS)) { type = "headline"; } else { type = "list"; } return sNewsService.getNewsList(type, newsId, page * INCREASE_PAGE) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(_flatMapNews(newsId)); } /** * 获取专题数据 * @param specialId * @return */ public static Observable<SpecialInfo> getSpecial(String specialId) { return sNewsService.getSpecial(specialId) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(_flatMapSpecial(specialId)); } /** * 获取新闻详情 * @param newsId 新闻ID * @return */ public static Observable<NewsDetailInfo> getNewsDetail(final String newsId) { return sNewsService.getNewsDetail(newsId) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(new Func1<Map<String, NewsDetailInfo>, Observable<NewsDetailInfo>>() { @Override public Observable<NewsDetailInfo> call(Map<String, NewsDetailInfo> newsDetailMap) { return Observable.just(newsDetailMap.get(newsId)); } }); } /** * 获取图集 * @param photoId 图集ID * @return */ public static Observable<PhotoSetInfo> getPhotoSet(String photoId) { return sNewsService.getPhotoSet(StringUtils.clipPhotoSetId(photoId)) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()); } /** * 获取图片列表 * @return */ public static Observable<List<PhotoInfo>> getPhotoList() { return sNewsService.getPhotoList() .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()); } /** * 获取更多图片列表 * @return */ public static Observable<List<PhotoInfo>> getPhotoMoreList(String setId) { return sNewsService.getPhotoMoreList(setId) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()); } /** * 获取美女图片 * 注: 因为网易这个原接口参数一大堆,我只传了部分参数,返回的数据会出现图片重复的情况,请不要在意这个问题- - * @return */ public static Observable<List<BeautyPhotoInfo>> getBeautyPhoto(int page) { return sNewsService.getBeautyPhoto(page * INCREASE_PAGE) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(_flatMapPhotos()); } /** * 获取福利图片 * @return */ public static Observable<WelfarePhotoInfo> getWelfarePhoto(int page) { return sWelfareService.getWelfarePhoto(page) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(_flatMapWelfarePhotos()); } /** * 获取视频列表 * @return */ public static Observable<List<VideoInfo>> getVideoList(String videoId, int page) { return sNewsService.getVideoList(videoId, page * INCREASE_PAGE / 2) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(_flatMapVideo(videoId)); } /******************************************* 转换器 **********************************************/ /** * 转换器,因为 Key 是动态变动,所以用这种不太合适 * @param <T> */ @Deprecated public static class FlatMapTransformer<T> implements Observable.Transformer<Map<String, List<T>>, T> { private String mMapKey; public FlatMapTransformer<T> setMapKey(String mapKey) { mMapKey = mapKey; return this; } @Override public Observable<T> call(Observable<Map<String, List<T>>> mapObservable) { return mapObservable.flatMap(new Func1<Map<String, List<T>>, Observable<T>>() { @Override public Observable<T> call(Map<String, List<T>> stringListMap) { if (TextUtils.isEmpty(mMapKey)) { return Observable.error(new Throwable("Map Key is empty")); } return Observable.from(stringListMap.get(mMapKey)); } }).subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()); } } /** * 类型转换 * @param typeStr 新闻类型 * @return */ private static Func1<Map<String, List<NewsInfo>>, Observable<NewsInfo>> _flatMapNews(final String typeStr) { return new Func1<Map<String, List<NewsInfo>>, Observable<NewsInfo>>() { @Override public Observable<NewsInfo> call(Map<String, List<NewsInfo>> newsListMap) { return Observable.from(newsListMap.get(typeStr)); } }; } /** * 类型转换 * @param typeStr 视频类型 * @return */ private static Func1<Map<String, List<VideoInfo>>, Observable<List<VideoInfo>>> _flatMapVideo(final String typeStr) { return new Func1<Map<String, List<VideoInfo>>, Observable<List<VideoInfo>>>() { @Override public Observable<List<VideoInfo>> call(Map<String, List<VideoInfo>> newsListMap) { return Observable.just(newsListMap.get(typeStr)); } }; } /** * 类型转换 * @param specialId 专题id * @return */ private static Func1<Map<String, SpecialInfo>, Observable<SpecialInfo>> _flatMapSpecial(final String specialId) { return new Func1<Map<String, SpecialInfo>, Observable<SpecialInfo>>() { @Override public Observable<SpecialInfo> call(Map<String, SpecialInfo> specialMap) { return Observable.just(specialMap.get(specialId)); } }; } /** * 类型转换 * @return */ private static Func1<Map<String, List<BeautyPhotoInfo>>, Observable<List<BeautyPhotoInfo>>> _flatMapPhotos() { return new Func1<Map<String, List<BeautyPhotoInfo>>, Observable<List<BeautyPhotoInfo>>>() { @Override public Observable<List<BeautyPhotoInfo>> call(Map<String, List<BeautyPhotoInfo>> newsListMap) { return Observable.just(newsListMap.get("美女")); } }; } /** * 类型转换 * @return */ private static Func1<WelfarePhotoList, Observable<WelfarePhotoInfo>> _flatMapWelfarePhotos() { return new Func1<WelfarePhotoList, Observable<WelfarePhotoInfo>>() { @Override public Observable<WelfarePhotoInfo> call(WelfarePhotoList welfarePhotoList) { if (welfarePhotoList.getResults().size() == 0) { return Observable.empty(); } return Observable.from(welfarePhotoList.getResults()); } }; } }