在Android上寫(xiě)過(guò)網(wǎng)絡(luò)請(qǐng)求的小伙伴對(duì)Retrofit應(yīng)該不陌生,這里我們先來(lái)簡(jiǎn)單回顧一下Retrofit。
Retrofit
Retrofit 是由Square公司開(kāi)源的適用于Android和java的類(lèi)型安全的Http客戶(hù)端。
使用方法相當(dāng)簡(jiǎn)單,首先將需要的http請(qǐng)求以接口的形式定義出來(lái):
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
接著使用Retrofit生成GitHubService接口的實(shí)例。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
接著只需要調(diào)用對(duì)應(yīng)接口即可:
Call<List<Repo>> repos = service.listRepos("octocat");
以上調(diào)用會(huì)使用get方式請(qǐng)求https://api.github.com/users/octocat/repos
分析實(shí)現(xiàn)原理
如果只是簡(jiǎn)單看上面的demo,會(huì)發(fā)現(xiàn) Retrofit 似乎給我們變了一個(gè)小魔術(shù),讓我們的接口直接就能做網(wǎng)絡(luò)請(qǐng)求了。但魔術(shù)終歸只是障眼法,我們來(lái)分析一下魔術(shù)背后的原理。
首先,為什么定義了接口就能直接用了,其實(shí)這在 java 中也不是什么新鮮事了,有很多手段可以實(shí)現(xiàn)這個(gè)。其中最簡(jiǎn)單的就是 Proxy 的 newProxyInstance() 方法,它可以動(dòng)態(tài)生成一個(gè)接口的實(shí)例。
接著就是get網(wǎng)絡(luò)請(qǐng)求 url:https://api.github.com/users/octocat/repos 是如何得到的?這個(gè)其實(shí)也很簡(jiǎn)單,Retrofit 使用 Get 這個(gè)注解來(lái)標(biāo)明這是一個(gè) get 請(qǐng)求,然后使用 Path 來(lái)表示請(qǐng)求中需要被替換的 path,最后使用將 baseUrl 中拿到的 url 和 Get 中的路徑拼接,就能得到最后需要的 url 了。
魔術(shù)背后的原理都知道了,接下來(lái)就看看怎么進(jìn)行表演網(wǎng)絡(luò)請(qǐng)求這個(gè)魔術(shù)了。
實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的 Get 請(qǐng)求
接下來(lái)我們實(shí)現(xiàn)這么一個(gè)簡(jiǎn)單的請(qǐng)求百度首頁(yè)的功能:
interface SimpleRequest{
@GET
String goBaidu(@Url String url);
}
SimpleRequest simpleRequest = new Retro.
Builder().
build().
create(SimpleRequest.class);
println(simpleRequest.goBaidu("http://www.baidu.com"));
上面我們定義了一個(gè) SimpleRequest 的接口,定義了 goBaidu 這個(gè)方法,使用 Get 來(lái)表示它是一個(gè) get 請(qǐng)求,用 Url 來(lái)表示參數(shù) url 就是這個(gè)請(qǐng)求的完整的地址。
接著使用 Retro 這個(gè)類(lèi)來(lái)生成 SimpleRequest 的實(shí)例,最后使用 println 來(lái)打印出 goBaidu("http://www.baidu.com") 這個(gè)調(diào)用的結(jié)果。
定義 GET 和 Url 注解
首先定義 GTE 和 Url 這兩個(gè)注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GET {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Url {
}
這兩個(gè)注解比較簡(jiǎn)單,不多說(shuō)什么。
定義 Retro
接著定義Retro這個(gè)類(lèi):
public class Retro {
@SuppressWarnings("unchecked")
public <T> T create(final Class<T> server){
return (T)Proxy.newProxyInstance(server.getClassLoader(), new Class<?>[]{server}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
ServiceMethod.Builder builder = new ServiceMethod.Builder(Retro.this,method,args);
ServiceMethod serviceMethod = builder.build();
return serviceMethod.call();
}
});
}
}
目前Retro這個(gè)類(lèi)只有一個(gè)方法 create 方法,其接受一個(gè) Class 對(duì)象,并返回這個(gè) Class 所對(duì)應(yīng)的實(shí)例。由于使用的是 Proxy 的 newProxyInstance 方法,所有的方法都會(huì)被 InvocationHandler 對(duì)象的 invoke 方法攔截,也可以說(shuō)是被代理了。
接著我們?cè)?invoke 方法中看到了 ServiceMethod 這個(gè)類(lèi),它是這里的關(guān)鍵,所有請(qǐng)求相關(guān)的處理都是在其中被完成。接著我們就來(lái)看一下 ServiceMethod 應(yīng)該怎么編寫(xiě)。
定義 ServiceMethod
由于需要在 ServiceMethod 處理相關(guān)數(shù)據(jù),所以需要將 Retro,method,args 這幾個(gè)對(duì)象傳遞給它。其中 Retro 用于獲得一些全局的變量,這里暫時(shí)用不到。 method 用于獲得 http 的請(qǐng)求方法和 Url 注解所標(biāo)注的 url。 args 則是 Url 注解所標(biāo)注的實(shí)際參數(shù)。
知道了這些以后就可以編寫(xiě)代碼了:
public static final class Builder{
private String baseUrl;
private Object args[];
private String httpMethod ;
private Class<?>[] methodParamsTypes;
private final Annotation[] methodAnnotations;
private final Annotation[][] methodParamsAnnotations;
public Builder(Retro retro, Method method, Object[] args){
this.args = args;
this.methodAnnotations = method.getAnnotations();
this.methodParamsAnnotations = method.getParameterAnnotations();
this.methodParamsTypes = method.getParameterTypes();
}
public ServiceMethod build(){
for(int i = 0;i<methodAnnotations.length;i++){
parseMethodAnnotation(methodAnnotations[i]);
}
for(int i = 0;i<methodParamsAnnotations.length;i++){
parseMethodParamsAnnotation(methodParamsAnnotations[i],args[i],methodParamsTypes[i]);
}
return new ServiceMethod(this);
}
private void parseMethodAnnotation(Annotation annotation){
if(annotation instanceof GET){
parseHttpMethodAndPath("GET",((GET)annotation).value(),false);
}
}
private void parseMethodParamsAnnotation(Annotation[] annotations,Object value,Class<?> type){
if(annotations.length == 1){
Annotation annotation = annotations[0];
if(annotation instanceof Url){
baseUrl = String.valueOf(value);
}
}
}
private void parseHttpMethodAndPath(String httpMethod,String value,boolean hasBody){
this.httpMethod = httpMethod;
}
}
ServiceMethod 還是使用 Builder 來(lái)構(gòu)建,看 build 方法,首先對(duì)方法的注解進(jìn)行處理,即需要得到這里的 GET 注解,看 parseMethodAnnotation 方法,
private void parseMethodAnnotation(Annotation annotation){
if(annotation instanceof GET){
parseHttpMethodAndPath("GET",((GET)annotation).value(),false);
}
}
這里過(guò)濾我們的 GET 注解,讓后調(diào)用 parseHttpMethodAndPath 方法對(duì)其進(jìn)行處理:
private void parseHttpMethodAndPath(String httpMethod,String value,boolean hasBody){
this.httpMethod = httpMethod;
}
這里我們就得到了 http 的請(qǐng)求方法,即 GET 。
接著看 parseMethodParamsAnnotation 這個(gè)方法,用于解析方法參數(shù)的標(biāo)注,即這里的 Url 標(biāo)注。
private void parseMethodParamsAnnotation(Annotation[] annotations,Object value,Class<?> type){
if(annotations.length == 1){
Annotation annotation = annotations[0];
if(annotation instanceof Url){
baseUrl = String.valueOf(value);
}
}
}
這個(gè)方法會(huì)遍歷所有參數(shù)的標(biāo)注,如果有 Url 標(biāo)注,就拿到對(duì)應(yīng)參數(shù)的值,將其賦值給 baseUrl。
最后返回 ServiceMethod 實(shí)例。
下面看一下 ServiceMethod 的主要代碼:
public class ServiceMethod{
private final String mBaseUrl;
private final String mHttpMethod;
ServiceMethod(Builder builder){
mBaseUrl = builder.baseUrl;
mHttpMethod = builder.httpMethod;
}
public String call(){
try {
URL url = new URL(mBaseUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.setDoOutput(true);
//set heep method
connection.setRequestMethod(mHttpMethod);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
InputStream inputStream = connection.getInputStream();
int len ;
byte[] buffer = new byte[1024];
while ((len = inputStream.read(buffer))>0){
bos.write(buffer,0,len);
}
String body = new String(bos.toByteArray());
connection.disconnect();
return body;
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
...
我們可以看到在 ServiceMethod 的構(gòu)造方法中,拿到了 Builder 中的 baseUrl 和 httpMethod,有了這兩個(gè)標(biāo)量其實(shí)就能做實(shí)際的 http 請(qǐng)求了。
接著看 call 這個(gè)方法,魔術(shù)的最終一環(huán)就是在這里完成的,單看 call 這個(gè)方法中的內(nèi)容很簡(jiǎn)單,就是使用 URL 進(jìn)行一次 http 請(qǐng)求。請(qǐng)求方法為 mHttpMethod,url 為 mBaseUrl,即一開(kāi)始的 "http://www.baidu.com" 。
最后將請(qǐng)求到的數(shù)據(jù)轉(zhuǎn)換成 String 對(duì)象返回,即對(duì)應(yīng)下面的 serviceMethod.call() 。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
ServiceMethod.Builder builder = new ServiceMethod.Builder(Retro.this,method,args);
ServiceMethod serviceMethod = builder.build();
return serviceMethod.call();
}
運(yùn)行結(jié)果
自后我們跑一下下面的方法:
@Test
public void testAnnotation_Url(){
SimpleRequest simpleRequest = new Retro().
create(SimpleRequest.class);
println(simpleRequest.goBaidu("http://www.baidu.com"));
}

總結(jié)
到這里一個(gè)簡(jiǎn)單的類(lèi)似 Retrofit 的 http 客戶(hù)端就完成了,它可以完成最簡(jiǎn)單 http 的 get 請(qǐng)求。
當(dāng)然這篇主要講的是 Retro 的一個(gè)大體結(jié)構(gòu),用于了解類(lèi)似 Retrofit 這類(lèi)第三方庫(kù)的實(shí)現(xiàn)思路。后面還會(huì)推出一系列文章,用于講解如何實(shí)現(xiàn) GET 注解自定義 Path,POST 注解及表單處理,返回類(lèi)型轉(zhuǎn)換,錯(cuò)誤處理等功能。