前言
Http請(qǐng)求是做Android應(yīng)用開發(fā)工作幾乎必須要用到的東西扛门。做Android開發(fā)這幾年帆离,從最開始仿照網(wǎng)上代碼自己使用apache的DefaultHttpClient封裝網(wǎng)絡(luò)請(qǐng)求工具類私沮,到后面開始使用GitHub上面的一些http框架缝左,Afinal,xUtils到Volley,AsyncHttpClient等碎税,網(wǎng)上這些http框架大多都還比較易用歪泳,但是做實(shí)際業(yè)務(wù)中還是感覺到業(yè)務(wù)和界面代碼與Http請(qǐng)求的代碼還是耦合性過高,特別是在服務(wù)器接口比較多的時(shí)候。所以自己在以前的項(xiàng)目中也一直在嘗試做一些封裝解耦沸呐,但是一直感覺達(dá)不到自己想要的效果,直到看到Retrofit這個(gè)類庫(kù)呢燥。
在斷斷續(xù)續(xù)看了幾個(gè)月的OkHttpClient和Retrofit源碼崭添,我終于決定嘗試著封裝一個(gè)自己的框架:httplite
Github:https://github.com/alexclin0188/httplite
類庫(kù)主要特性介紹
httplite類庫(kù)主要實(shí)現(xiàn)了以下特性
- 1.隔離了底層http實(shí)現(xiàn),http實(shí)現(xiàn)可替換
雖然okhttpclient的實(shí)現(xiàn)很好叛氨,但是有時(shí)候也會(huì)因?yàn)轫?xiàng)目包大小等原因需要使用系統(tǒng)UrlConection來實(shí)現(xiàn) - 2.建造者模式的流式調(diào)用和結(jié)果解析的解耦
使用Request.url().param().header().***.async(Callback<T>)方式調(diào)用呼渣,可自定義多重ResponseParser來實(shí)現(xiàn)不同的http返回結(jié)果(json,protocolbuf等) - 3.支持使用類似Retrofit的方式棘伴,使用java接口類來定義后后臺(tái)API接口,并且支持RxJava屁置,支持自定義注解
目前類庫(kù)底層的http實(shí)現(xiàn)提供了okhttp2.x/okhttp3.x/URLConnection三種可選.
類庫(kù)使用指南
一焊夸、添加依賴
- Gradle
使用okhttp 2.7.5作為http實(shí)現(xiàn)
compile 'alexclin.httplite:httplite-okhttp2:1.1.1'
compile 'com.squareup.okhttp:okhttp:2.7.5'
使用okhttp 3.2.0作為http實(shí)現(xiàn)
compile 'alexclin.httplite:httplite-okhttp3:1.1.1'
compile 'com.squareup.okhttp3:okhttp:3.2.0'
使用系統(tǒng)URLConnection作為http實(shí)現(xiàn)
compile 'alexclin.httplite:httplite-url:1.1.1'
如需Rx擴(kuò)展則還需要
compile 'alexclin.httplite:retrofit-rx:1.1.1'
compile 'io.reactivex:rxjava:1.1.1'
或者直接使用releaselib中的jar包
1 okhttp2: httplite1.1.1.jar+httplite-ok2lite1.1.1.jar+okhttp 2.x.x版本jar包
2 okhttp3: httplite1.1.1.jar+httplite-ok3lite1.1.1.jar+okhttp 3.x.x版本jar包
3 url: httplite1.1.1.jar+httplite-urlite1.1.1.jar
4 使用rx擴(kuò)展:httplite-retrofit-rx1.1.1.jar+rx1.x.x版本jar包
二、類庫(kù)初始化
或者也可以直接使用jar包
首先創(chuàng)建HttpLiteBuilder進(jìn)行配置蓝角,目前有三種HTTP實(shí)現(xiàn)可選
//使用OkHttp2.x作為Http實(shí)現(xiàn)
HttpLiteBuilder builder = Ok2Lite.create();
//使用OkHttp3.x作為Http實(shí)現(xiàn)
HttpLiteBuilder builder = Ok3Lite.create();
//使用系統(tǒng)URLConnection作為http實(shí)現(xiàn)
HttpLiteBuilder builder = URLite.create();
對(duì)Builder進(jìn)行配置
builder = builder.setConnectTimeout(10, TimeUnit.SECONDS) //設(shè)置連接超時(shí)
.setWriteTimeout(10, TimeUnit.SECONDS) //設(shè)置寫超時(shí)
.setReadTimeout(10, TimeUnit.SECONDS) //設(shè)置讀超時(shí)
.setMaxRetryCount(2) //設(shè)置失敗重試次數(shù)
.setFollowRedirects(true) //設(shè)置是否sFollowRedirects,默認(rèn)false
.setFollowSslRedirects(true) //設(shè)置是否setFollowSslRedirects
.addResponseParser(new GsonParser())
.baseUrl("http://192.168.99.238:10080/")//BaseUrl
.setProxy(...)//
.setProxySelector(...)//
.setSocketFactory(...)//
.setSslSocketFactory(...)//
.setHostnameVerifier(..)//
.useCookie(...) //設(shè)置CookieStore,設(shè)置則啟用Cookie,不設(shè)置則不啟用
.addCallAdapter(new RxCallAdapter());//添加Rx支持
創(chuàng)建HttpLite實(shí)例
HttpLite httpLite = builder.build();
另外提供mock支持阱穗,需傳入MockHandler
httpLite = builder.mock(new MockHandler() {
@Override
public <T> void mock(Request request, Mock<T> mock) throws Exception {
//模擬完整的http返回結(jié)果輸入流
mock.mock(int code,String msg,Map<String, List<String>> headers, final InputStream stream,MediaType mediaType);
//直接模擬結(jié)果
mock(T result, Map<String, List<String>> headers);
//模擬Json格式的結(jié)果
mock.mockJson(....);
//以文件內(nèi)容作為Http返回結(jié)果輸入流
mock.mock(new File("...."))使鹅;
}
@Override
public boolean needMock(Request request) {
//TODO 判斷該請(qǐng)求是否需要Mock
return true;
}
});
二揪阶、普通方式發(fā)起http請(qǐng)求
發(fā)起普通GET請(qǐng)求
mHttpLite.url(url).header("header","not chinese").header("test_header","2016-01-06")
.header("double_header","header1").addHeader("double_header","head2")
.param("type","json").param("param2","You dog").param("param3", "中文")
.get().async(new Callback<Result<List<FileInfo>>>() {
@Override
public void onSuccess(Request req, Map<String, List<String>> headers,Result<List<FileInfo>> result) {
//TODO
}
@Override
public void onFailed(Request req, Exception e) {
//TODO
}
});
發(fā)起post請(qǐng)求,監(jiān)聽進(jìn)度
//multipart上傳文件
MediaType type = mHttpLite.parse(MediaType.MULTIPART_FORM+";charset=utf-8");
RequestBody body = mHttpLite.createRequestBody(mHttpLite.parse(MediaType.APPLICATION_STREAM),file);
mHttpLite.url("/").multipartType(type).multipart("早起早睡","身體好").multipart(info.fileName,info.hash).multipart(info.fileName,info.filePath,body)
.onProgress(new ProgressListener() {
@Override
public void onProgressUpdate(boolean out, long current, long total) {
LogUtil.e("是否上傳:"+out+",cur:"+current+",total:"+total);
}
})
.post().async(new Callback<Result<String>>() {
@Override
public void onSuccess(Request req,Map<String, List<String>> headers,Result<String> result) {
LogUtil.e("Result:"+result);
}
@Override
public void onFailed(Request req, Exception e) {
LogUtil.e("onFailed:"+e);
e.printStackTrace();
}
});
//post json
mHttpLite.url("/").post(MediaType.APPLICATION_JSON, JSON.toJSONString(info)).async(new Callback<String>() {
@Override
public void onSuccess(Request req,Map<String, List<String>> headers,String result) {
LogUtil.e("Result:" + result);
}
@Override
public void onFailed(Request req, Exception e) {
LogUtil.e("E:" + e.getLocalizedMessage());
e.printStackTrace();
}
});
//post form表單
mHttpLite.url("/").form("&test1","name&1").form("干撒呢","whatfuck").formEncoded(Uri.encode("test&2"),Uri.encode("name&2")).post().async(new Callback<String>() {
@Override
public void onSuccess(Request req,Map<String, List<String>> headers,String result) {
LogUtil.e("Result:" + result);
}
@Override
public void onFailed(Request req, Exception e) {
LogUtil.e("E:" + e.getLocalizedMessage());
e.printStackTrace();
}
});
下載文件
mHttpLite.url(url).intoFile(dir,name,true,true)
.onProgress(new ProgressListener() {
@Override
public void onProgressUpdate(boolean out, long current, long total) {
//TODO
}
})
.download(new Callback<File>() {
@Override
public void onSuccess(Request req, Map<String, List<String>> headers, File result) {
//TODO
}
@Override
public void onFailed(Request req, Exception e) {
//TODO
}
});
三并徘、使用java接口定義API接口(類似Retrofit的功能)
1.基礎(chǔ)使用
//生成API接口實(shí)例
final SampleApi api = mHttplite.retrofit(SampleApi.class);
//調(diào)用異步方法
api.login("user", "pass", "token", new Callback<Result<UserInfo>>() {
@Override
public void onSuccess(Request req, Map<String, List<String>> headers, Result<UserInfo> result) {
//TODO
}
@Override
public void onFailed(Request req, Exception e) {
//TODO
}
});
//調(diào)用異步方法
new Thread(){
@Override
public void run() {
//獲取知乎主頁數(shù)據(jù)
try {
ZhihuData data = api.syncZhihu();
//TODO
} catch (Exception e) {
//TODO
}
}
}.start();
//生成Call
final Call call = api.zhihuCall();
//異步調(diào)用Call
call.async(new Callback<ZhihuData>() {
@Override
public void onSuccess(Request req, Map<String, List<String>> headers, ZhihuData result) {
//TODO
}
@Override
public void onFailed(Request req, Exception e) {
//TODO
}
});
//或者同步調(diào)用Call
new Thread(){
@Override
public void run() {
//獲取知乎主頁數(shù)據(jù)
try {
ZhihuData data = call.sync(new Clazz<ZhihuData>(){});
//TODO
} catch (Exception e) {
//TODO
}
}
}.start();
2.RxJava的支持
支持RxJava需要在配置HttpLiteBuilder時(shí)添加RxCallAdapter
HttpLiteBuilder builder = ....
.....
builder.addCallAdapter(new RxCallAdapter());
.....
定義返回Obserable的API函數(shù)
@GET("http://news-at.zhihu.com/api/4/news/latest")
Observable<ZhihuData> testZhihu();
使用返回的Obserable
Observable<ZhihuData> observable = apiService.testZhihu();
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ZhihuData>() {
@Override
public void onCompleted() {
LogUtil.e("onCompleted");
}
@Override
public void onError(Throwable e) {
LogUtil.e("Onfailed", e);
}
@Override
public void onNext(ZhihuData zhihuData) {
LogUtil.e("Result:" + zhihuData);
LogUtil.e("Result:" + (Thread.currentThread()== Looper.getMainLooper().getThread()));
}
});
3.自定義注解的使用
自定義注解支持方法注解和參數(shù)注解
只需定義自己的注解遣钳,在HttpLite的Retrofit實(shí)例中添加對(duì)應(yīng)注解的處理器即可
定義注解和注解處理器,此處只列出GsonFieldProcesscor代碼,詳細(xì)參考Demo
public class GsonFieldProcesscor implements ParamMiscProcessor {
public static final String BODY_TYPE = "gson_json_body";
@Override
public void process(Request request, Annotation[][] annotations, List<Pair<Integer, Integer>> list, Object... args) {
//處理所有帶有Gson注解的參數(shù)麦乞,list中存儲(chǔ)的是所有Gson注解的位置
JsonObject object = new JsonObject();
for(Pair<Integer,Integer> pair:list){
int argPos = pair.first;
int annotationPos = pair.second;
if(args[argPos]==null) continue;
GsonField annotation = (GsonField) annotations[argPos][annotationPos];
String key = annotation.value();
if(args[argPos] instanceof String){
object.addProperty(key,(String)args[argPos]);
}else if(args[argPos] instanceof JsonElement){
object.add(key,(JsonElement)args[argPos]);
}else if(args[argPos] instanceof Boolean){
object.addProperty(key,(Boolean)args[argPos]);
}else if(args[argPos] instanceof Number){
object.addProperty(key,(Number)args[argPos]);
}
}
request.body(MediaType.APPLICATION_JSON,object.toString());
}
@Override
public boolean support(Annotation annotation) {
return annotation instanceof GsonField;
}
@Override
public void checkParameters(Method method, Annotation annotation, Type parameterType) throws RuntimeException {
//在此函數(shù)中檢查參數(shù)類型是否定義正確
if(!gsonSupportType(parameterType)){
throw Util.methodError(method,"Annotation @GsonField only support parameter type String/JsonElement/Boolean/Number/int/long/double/short");
}if(TextUtils.isEmpty(((GsonField)annotation).value())){
throw Util.methodError(method,"The annotation {@GsonField(value) value} must not be null");
}
}
private boolean gsonSupportType(Type type){
return type==String.class || Util.isSubType(type,JsonElement.class) || type == int.class || type == long.class || type == double.class
|| type == short.class || Util.isSubType(type,Number.class) || type == boolean.class || type == Boolean.class;
}
}
@BaseURL("http://192.168.99.238:10080/")
public interface CustomApi {
@GET("/login")
void login(
@Query("username") String userName,
@Query("password") String password,
@Query("token") String token,
@Tag Object tag,
Callback<Result<UserInfo>> callback
);
@POST("/test")
void testPost(@GsonField("param1") String param1,
@GsonField("param1")String param2,
Callback<Result<RequestInfo>> callback);
}
//添加自定義注解處理器
//普通的參數(shù)注解處理ParamterProcessor
Retrofit.registerParamterProcessor(new QueryProcessor());
//對(duì)個(gè)參數(shù)組合到一起的參數(shù)注解處理ParamMiscProcessor蕴茴,如將多個(gè)參數(shù)組合成一個(gè)json字符串作為請(qǐng)求的BODY
Retrofit.registerParamMiscProcessor(new GsonFieldProcesscor());
//當(dāng)注解處理的參數(shù)是用作Body時(shí),還需要注冊(cè)Body類型
Retrofit.basicAnnotationRule().registerBodyAnnotation(GsonField.class,
GsonFieldProcesscor.BODY_TYPE,true);
//創(chuàng)建實(shí)例
CustomApi api = mHttplite.retrofit(CustomApi.class);
//發(fā)起請(qǐng)求
Object tag = new Object();
api.login("user", "pass", "token", tag, new Callback<Result<UserInfo>>() {
@Override
public void onSuccess(Request req, Map<String, List<String>> headers, Result<UserInfo> result) {
//TODO
}
@Override
public void onFailed(Request req, Exception e) {
//TODO
}
});
api.testPost("test1", "test2", new Callback<Result<RequestInfo>>() {
@Override
public void onSuccess(Request req, Map<String, List<String>> headers, Result<RequestInfo> result) {
//TODO
LogUtil.e("Result:"+result);
}
@Override
public void onFailed(Request req, Exception e) {
//TODO
LogUtil.e("onFailed",e);
}
});
}
4.RequestListener和MethodFilter的使用
HttpLite支持在創(chuàng)建API接口實(shí)例時(shí)傳入RequestListener和MethodFilter
SampleApi api = mHttplite.retrofit(SampleApi.class,listener,filter);
- RequestListener主要用于監(jiān)聽接口中的請(qǐng)求姐直,或者為請(qǐng)求添加一些通用參數(shù)
RequestListener listener = new RequestListener() {
@Override
public void onRequest(HttpLite lite, Request request, Type resultType) {
LogUtil.e("RequestUrl:"+request.rawUrl());
//添加通用參數(shù)
request.param("commonParam","1234");
}
};
- MethodFilter主要用于給某些請(qǐng)求加一些前置操作
MethodFilter filter = new MethodFilter() {
@Override
public Object onMethod(HttpLite lite, final MethodInvoker invoker, final Object[] args) throws Throwable {
//此處僅以同步請(qǐng)求方法為例
String publicKey = ......
if(TextUtils.isEmpty(publicKey)){
LogUtil.e("methodFilter:"+invoker);
String publicKey = ......
if(TextUtils.isEmpty(publicKey)){
//獲取key
......
return invoker.invoke(args);
}else{
return invoker.invoke(args);
}
}else{
return invoker.invoke(args);
}
}
};
四倦淀、配置ResponseParser
默認(rèn)支持String的解析,但是類對(duì)象結(jié)果的解析需要使用httpLite.addResponseParser()添加支持該類型的解析器ResponseParser声畏,可添加多個(gè)以便支持多種不同的結(jié)果解析
ResponseParser接口定義如下:
public interface ResponseParser {
boolean isSupported(Type type);
<T> T praseResponse(Response response, Type type) throws Exception;
}
可以通過實(shí)現(xiàn)此接口解析Json,XML或者二進(jìn)制流為對(duì)象的功能
demo模塊app中分別有使用Jackson,FastJson,Gson實(shí)現(xiàn)Json解析撞叽,通過繼承StringParser實(shí)現(xiàn)。
public abstract class StringParser implements ResponseParser{
@Override
public final <T> T praseResponse(Response response, Type type) throws Exception{
return praseResponse(HttpCallback.decodeResponseToString(response),type);
}
public abstract <T> T praseResponse(String content, Type type) throws Exception;
}