Retrofit
1.1、Retrofit简介

RetrofitSquare公司开发的针对JavaAndroidHTTP开源库。

Retrofit底层默认是使用OKHttp处理HTTP请求的。

Retrofit的作者有两个:Bob LeeJakeWharton

Bob Lee曾是Square的第13名员工, 在2010年从Google离职加入Square担任Android应用开发工作,一年后升任CTO。 2014年05月19日,Bob LeeSquare离职。

Bob LeeGoogle工作的时候,开发了鼎鼎有名的轻量级JEE IOC框架Guice, 你如果看过Guice的源码的话,你就会知道, 他对Java中的范型和反射、注解、设计模式运用的炉火纯青。似乎他对这些都情有独钟,而Retrofit框架也是利用了这些技术, 所以Retrofit使用起来非常优雅。

JakeWharton是绝对牛逼的技术大神,项目主要集中在Android版本兼容,ViewPager及开发工具上。

JakeWhartonGitHub地址:https://github.com/JakeWharton

Retrofit官网:http://square.github.io/retrofit

RetrofitGitHub上的网址:https://github.com/square/retrofit

1.2、在build.gradle里面添加依赖
compile 'com.squareup.retrofit2:retrofit:2.2.0'

具体的版本号,请查看Retrofit在GitHub上的网址

1.3、Retrofit

Retrofit没有公开的构造方法。必须使用Retrofit.Builder进行构造。

1.4、Retrofit.Builder

示例:

HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);

OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)//设置超时时间
                .readTimeout(10, TimeUnit.SECONDS)   //设置读取超时时间
                .writeTimeout(10, TimeUnit.SECONDS)  //设置写入超时时间
                .retryOnConnectionFailure(true)      //设置失败后重试
                .cache(new Cache(context.getCacheDir(), 10 * 1024 * 1024))//设置缓存目录和缓存大小:10M
                .addInterceptor(interceptor)
                .authenticator(new Authenticator() {
                    @Override
                    public Request authenticate(Route route, Response response) throws IOException {
                        Log.i(TAG, "authenticate()");
                        String username = "";
                        String password = "";
                        String credential = Credentials.basic(username, password);
                        return response.request().newBuilder().header("Authorization", credential).build();
                    }
                })
                .build();

Retrofit retrofit = new Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

我们通常不使用默认的OkHttpClient对象,而是自己设置一个,用来设置3种超时时间、失败后的重试、打印日志、Cookie的处理等。

这里使用了HttpLoggingInterceptor,必须在build.gradle中加入如下依赖:

compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'

这里使用了GsonConverterFactory,必须在build.gradle中加入如下依赖:

compile 'com.squareup.retrofit2:converter-gson:2.0.2'
1.5、retrofit2.Call<T>

retrofit2.Call<T>类似于okhttp3.Call,只是,此类支持范型。

1.6、@GET

@GET表示GET请求。

@GET的源码如下:

package retrofit2.http;

@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
    String value() default "";
}

@GET的定义可以看出,@GET只能用在方法上并且可以设置一个字符串值参数,参数可省略。

假设,我们有如下的HTTP接口:

GET /updateApp HTTP/1.1

响应体如下:

{
    "businessId": "updateApp",
    "statusCode": 0,
    "message": "success",
    "data": {
        "versionName": "1.1.0",
        "text": "修改了啥就是不告诉你",
        "apkUrl": "http://blog.fpliu.com/xx.apk",
        "force": true
    }
}

我们根据响应体使用AndroidStudio的插件GsonFormat自动生成实体类,如下:

public class UpdateAppBean {
    private String businessId;
    private int statusCode;
    private String message;
    private DataBean data;

    public String getBusinessId() {
        return businessId;
    }

    public void setBusinessId(String businessId) {
        this.businessId = businessId;
    }

    public int getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public DataBean getData() {
        return data;
    }

    public void setData(DataBean data) {
        this.data = data;
    }

    public static class DataBean {
        private String versionName;
        private String text;
        private String apkUrl;
        private boolean force;

        public String getVersionName() {
            return versionName;
        }

        public void setVersionName(String versionName) {
            this.versionName = versionName;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public String getApkUrl() {
            return apkUrl;
        }

        public void setApkUrl(String apkUrl) {
            this.apkUrl = apkUrl;
        }

        public boolean isForce() {
            return force;
        }

        public void setForce(boolean force) {
            this.force = force;
        }
    }
}

定义一个Java接口与HTTP接口对应,如下:

public interface HttpAPI {
    @GET("/updateApp")
    retrofit2.Call<UpdateAppBean> updateApp();
}

要注意的是方法的返回值必须是retrofit2.Call<T>,不要与okhttp3.Call混淆了。

@GET("/updateApp")表示这是一个GET请求的URLpathupdateApp

接下来就是请求了,如下:

RetrofitRequest.getRetrofit("http://192.168.1.101").create(HttpAPI.class).updateApp().enqueue(new retrofit2.Callback<UpdateAppBean>() {
    @Override
    public void onResponse(retrofit2.Call<UpdateAppBean> call, retrofit2.Response<UpdateAppBean> response) {
        UpdateAppBean updateAppBean = response.body();
        //TODO
    }

    @Override
    public void onFailure(retrofit2.Call<UpdateAppBean> call, Throwable t) {
        Log.e(TAG, "onFailure()", t);
    }
});

你会发现,使用Retrofit竟然是如此的优雅,他将HTTP接口与Java接口对应起来了。 这主要是使用了JDK1.4中加入的动态代理(ProxyInvocationHandler) 和JDK1.5中加入的注解(Annotation)这两个特性实现的。JDK1.4中加入的动态代理只能代理接口(interface),不能代理类(class), 所以,我们每一个HTTP API必须对应一个Java interface。 这个HTTP API的其他内容通过接口方法的注解获得。

1.7、@POST、@PUT、@DELETE、@HEAD

这些分别表示对应的HTTP请求方法,与@GET用法完全一样。

1.8、@Url

@GET等注解有一个参数,实际上,这个参数可以使用默认的,然后动态的传入,就是通过此注解实现的。

@Url的源码如下:

package retrofit2.http;

@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Url {
}

@Url的定义可以看出,@Url只能用在方法的参数上,你可以这么理解他,他就是一个标志, 说明这个参数是请求的URL

示例:

public interface HttpAPI {
    @GET()
    retrofit2.Call<UpdateAppResultBean> updateApp(@Url String url);
}
1.9、@Path

@Path的源码如下:

package retrofit2.http;

@Documented
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface Path {
    String value();
    boolean encoded() default false;
}

@Path的定义可以看出,@Path只能用在方法的参数上,而且value参数必须要设置, 这个意思就是说,指定的参数是Path的value值。

示例:

public interface HttpAPI {
    @GET("/user/{id}")
    retrofit2.Call<UpdateAppResultBean> updateApp(@Path("id") String id);
}

注意URL中的变量用{}扩起来。

1.9、@Query

@Query的用法与@Path差不多,只是他不需要在URL中设置变量了。@Query是传统的使用方式,而@Path用来实现现在非常流行的Restful API

举例:GET /user/1GET /user?id=1这就是两者的不同。

示例:

public interface HttpAPI {
    @GET("/user")
    retrofit2.Call<UpdateAppResultBean> updateApp(@Query("id") String id);
}
1.10、@Body

@Body的源码如下:

package retrofit2.http;

@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Body {
}

@Body的定义可以看出,@Body只能用在方法的参数上,你可以这么理解他,他就是一个标志, 说明这个参数是请求体。而且被他标志的参数必须是自定义Java类、或者是RequestBody类。 如果是自定义Java类,是通过new Retrofit.Builder().addConverterFactory()这是的转换器转换成对应的格式。

示例:

public interface HttpAPI {
    @POST("/login")
    retrofit2.Call<LoginBean> login(@Body User user);

    @POST("/login")
    retrofit2.Call<LoginBean> login(@Body RequestBody requestBody);
}

也就是说,@Body能够处理实体类,让转换器自动转换为相应的数据结构,它也允许我们自己构造RequestBody,这样的灵活性大大增加。

下面是构造RequestBody的示例:

RequestBody body = RequestBody.create(MediaType.parse("application/json;charset=UTF-8"), "xxxxx");
1.10、@FormUrlEncoded与@Field

@FormUrlEncoded的源码如下:

package retrofit2.http;

@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface FormUrlEncoded {
}

@FormUrlEncoded的定义可以看出,@FormUrlEncoded只能用在方法上,你可以这么理解他,他就是一个标志, 说明这个请求的请求体是Form

@Field的源码如下:

package retrofit2.http;

@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Field {
    String value();
    boolean encoded() default false;
}

@Field的定义可以看出,@Field只能用在方法的参数上。

示例:

public interface HttpAPI {
    @POST("/login")
    @FormUrlEncoded
    retrofit2.Call<LoginResultBean> login(@Field("username") String username, @Field("password") String password);
}
1.11、@Multipart与@Part

@Multipart的源码如下:

package retrofit2.http;

@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface Multipart {
}

@Multipart的定义可以看出,@Multipart只能用在方法上,你可以这么理解他,他就是一个标志, 说明这个请求的请求体是Multipart

@Part的源码如下:

package retrofit2.http;

@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Part {
  /**
   * The name of the part. Required for all parameter types except
   * {@link okhttp3.MultipartBody.Part}.
   */
  String value() default "";
  /** The {@code Content-Transfer-Encoding} of this part. */
  String encoding() default "binary";
}

@Part的定义可以看出,@Part只能用在方法的参数上。

示例:

public interface HttpAPI {
    @POST("/upload")
    @Multipart
    retrofit2.Call<UploadResultBean> upload(@Part("file") MultipartBody.Part filePart, @Part("meta") MultipartBody.Part metaPart);
}
1.12、@Header

@Header的源码如下:

package retrofit2.http;

@Documented
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface Header {
    String value();
}

@Header的定义可以看出,@Header只能用在方法的参数上,说明这个参数是指定的请求头的值。

示例:

public interface HttpAPI {
    @GET("/")
    retrofit2.Call<IndexResultBean> index(@Header("Authorization") String authorization);
}
1.13、总结

1、从上面的使用来看,Retrofit大大简化了OKHttp的使用, 并且更优雅了,不过,他仍然有一个死结,就是回掉函数运行在工作线程中,而我们在回掉函数中通常会更新UI, 所以,我们通常会配合上RxJava进行使用,这样就完美了。

2、RetrofitRestful API友好的。对于POST一些简单的JSON数据, 还需要创建一个Java类,然后通过@Body注解使用,这有点小题大做了。你想想,为了一两个属性而去造一个类, 最关键的是,这个类的存在就是为了给Retrofit传递数据的,如果,这样的接口有很多,我们的开发效率必然很低。因为对于这种简单的JSON数据,我们使用的时候,直接传入基本数据类型的字段,让程序自己组装出符合JSON的数据即可了。这样使用起来才方便嘛。

关于Retrofit支持简单JSON,我已经发起了Pull Request,请看https://github.com/square/retrofit/pull/2346/commits/28f71b68587ea53d7065fc71165f7991b86dfc6a

我修改后的源码在:https://github.com/leleliu008/retrofit

使用方法:

public interface HttpAPI {
    @POST("/login")
    @SimpleJSON
    retrofit2.Call<LoginBean> login(@SimpleJSONField("username") String username, @SimpleJSONField("password") String password);
}

这样,不用为了发送usernamepassword而创建一个类了。