前言
Retrofit 是一個贊譽無數的 Android 網絡框架,它的優點和強大無需我們多說。今天我們主要通過實現一個簡單的 Retrofit 框架(只能夠進行 Get 請求)來了解它內部核心運作機制。
注解抽取
Retrofit 中有多達 25 個注解,由于我們只是實現 Retrofit 的 Get 功能,因此我們只用抽取其中的 Get 、Field 二個注解接口即可。
**Get.java **
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
String value() default "";
}
**Field.java **
@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Field {
String value();
}
實現 Retrofit 類
Retrofit 類是 Retrofit 框架最核心的工作類,它通過 Builder 模式來構建各種請求參數。它的作用非常簡單,通過動態代理來獲取到我們自定義的方法接口實例,然后在解析該方法接口的注解參數以后,再調用Okhttp框架去執行網絡請求。
Retrofit.java
public final class Retrofit {
/**
* 該map的作用用于緩存Retrofit解析以后的方法,因為注解解析是相對耗時的。
*/
private final Map<Method, ServiceMethod> serviceMethodCache = new ConcurrentHashMap<>();
private String baseUrl;
public Retrofit(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getBaseUrl() {
return baseUrl;
}
/**
* 框架核心方法,用于構建接口實例
*
* @param service
* @param <T>
* @return
*/
public <T> T create(final Class<T> service) {
// 動態代理
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1 驗證是否是接口
if (!service.isInterface()) {
throw new IllegalArgumentException("API declarations must be interfaces.");
}
//2 進行構建接口實例,同時進行方法注解、方法參數注解解析,這些參數解析完成以后,我們就可以把注解解析后的url進行拼接
ServiceMethod serviceMethod =
loadServiceMethod(method, args);
//3 調用 http 框架執行網絡請求
OkHttpCall okHttpCall = new OkHttpCall(serviceMethod);
return okHttpCall;
}
}
);
}
/**
* 進行構建接口實例
*
* @param method 調用的接口方法
* @param args 方法參數內容
* @return
*/
private ServiceMethod loadServiceMethod(Method method, Object[] args) {
ServiceMethod result = serviceMethodCache.get(method);
if (result != null)
return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder(this, method, args).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
/**
* Retrofit 的構建類,除了 baseUrl 以外,其他的配置參數都是可選的,在這里我們只添加 baseUrl。
*/
public static final class Builder {
private String baseUrl;
public Builder baseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
return new Retrofit(baseUrl);
}
}
}
方法、方法參數注解解析
在 Retrofit 類中的 create 方法中,我們提到Retrofit 的工作原理分為三步:
- 一 校驗接口
- 二 構建接口實例,解析注解內容
- 三 拼接 Url,執行網絡請求
接下來,我們來進行編寫第二步。
ServiceMethod.java
public class ServiceMethod {
private Builder mBuilder;
public ServiceMethod(Builder builder) {
this.mBuilder = builder;
}
/**
* 獲取具體網絡請求類型
*
* @return methodName
*/
public String getMethodName() {
return mBuilder.methodName;
}
/**
* @return 請求url,這里GET請求我們需要進行url參數拼接,在實際源碼中我們其實是在Okhttpcall中調用requestbuilder進行統一拼接的。
*/
public String getBaseUrl() {
if (mBuilder.methodName.equals("GET")) {
StringBuffer sb = new StringBuffer();
sb.append(mBuilder.retrofit.getBaseUrl())
.append(mBuilder.relativeUrl);
Map<String, Object> parameterMap = getParameter();
if (parameterMap != null) {
Set<String> keySet = parameterMap.keySet();
if (keySet.size() > 0) {
sb.append("?");
}
for (String key : keySet) {
sb.append(key).append("=").append(parameterMap.get(key)).append("&");
}
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
return mBuilder.retrofit.getBaseUrl();
}
public Map<String, Object> getParameter() {
return mBuilder.parameterMap;
}
/**
* 用于解析注解
*/
static final class Builder {
final Retrofit retrofit;
final Method method;
final Annotation[] methodAnnotations;
final Annotation[][] parameterAnnotationsArray;
private Map<String, Object> parameterMap = new HashMap<>();
private Object[] args;
private String methodName;
private String relativeUrl;
Builder(Retrofit retrofit, Method method, Object[] args) {
this.retrofit = retrofit;
this.method = method;
// 方法注解列表
this.methodAnnotations = method.getAnnotations();
// 方法參數注解列表
this.parameterAnnotationsArray = method.getParameterAnnotations();
// 方法參數內容列表
this.args = args;
}
public ServiceMethod build() {
// 遍歷方法注解
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
// 遍歷方法參數注解
int parameterCount = parameterAnnotationsArray.length;
for (int p = 0; p < parameterCount; p++) {
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
parseParameter(p, parameterAnnotations);
}
return new ServiceMethod(this);
}
/**
* 解析方法注解,獲取方法注解中的值,用于后續拼接url地址
*
* @param annotation
*/
private void parseMethodAnnotation(Annotation annotation) {
if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value());
}
}
private void parseHttpMethodAndPath(String httpMethod, String value) {
if (httpMethod.equals("GET")) {
methodName = "GET";
this.relativeUrl = value;
}
}
/**
* 解析方法參數注解
*
* @param p 方法參數值的index
* @param parameterAnnotations 方法參數注解數組
*/
private void parseParameter(int p, Annotation[] parameterAnnotations) {
// 方法參數值
Object value = args[p];
// 遍歷參數注解
for (Annotation annotation : parameterAnnotations) {
//首先需要判斷參數注解類型
if (annotation instanceof Field) {
Field field = (Field) annotation;
// 參數名稱(接口參數名稱)
String key = field.value();
parameterMap.put(key, value);
}
}
}
}
}
執行網絡請求
在解析了注解內容,拼接了 Url 地址以后,我們的簡單框架就只剩下執行網絡請求這一步了,這一步難度不大,所以直接上代碼:
Call.java
/**
* 網絡請求接口
*/
public interface Call extends Cloneable {
/**
* 同步請求
* @return
* @throws IOException
*/
String execute() throws IOException;
/**
* 異步請求
* @param callback
*/
void enqueue(Callback callback);
}
OkHttpCall.java
public class OkHttpCall implements Call {
private ServiceMethod mServiceMethod;
private static OkHttpClient client;
static {
client = new OkHttpClient();
}
public OkHttpCall(ServiceMethod serviceMethod) {
this.mServiceMethod = serviceMethod;
}
@Override
public String execute() throws IOException {
if (mServiceMethod.getMethodName().equals("GET")) {
Request request = new Request.Builder()
.url(mServiceMethod.getBaseUrl())
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
return null;
}
@Override
public void enqueue(final Callback callback) {
if (mServiceMethod.getMethodName().equals("GET")) {
Request request = new Request.Builder()
.url(mServiceMethod.getBaseUrl())
.build();
client.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
callback.onFailure(e);
}
@Override
public void onResponse(okhttp3.Call call, Response response) throws IOException {
callback.onResponse(response.body().string());
}
});
}
}
}
測試
我們的簡單框架到了這里就已經編寫完成了,我們開始進行測試,看看有無問題。
AppService.java
public interface AppService {
//http://android.secoo.com/appservice/cartAndBrand.action?v=2.0
@GET("/appservice/cartAndBrand.action")
OkHttpCall getCartAndBrand(@Field("v") String v);
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://android.secoo.com").build();
AppService appService = retrofit.create(AppService.class);
final OkHttpCall okHttpCall = appService.getCartAndBrand("2.0");
okHttpCall.enqueue(new Callback() {
@Override
public void onResponse(String response) {
Log.e("response data",response);
}
@Override
public void onFailure(Throwable t) {
Log.e("error",t.getMessage());
}
});
}
}
按照代碼執行一下,最后控制臺日志成功輸出 Json 字符串,代表我們的測試是成功的。
Paste_Image.png