Retrofit 初体验

本贴最后更新于 2101 天前,其中的信息可能已经时移世异

背景

如果看到了 OkHttp, RxJava 相关文章的话,一定会同时看到 Retrofit。

OkHttp 是对 http 客户端进行了封装,RxJava 是反应式宣言,那 Retrofit 是什么呢?

于是这便是本文的写作背景。

第一个例子

Retrofit 的一个用法是直接把 Http 请求封装为 Java 对象。举个例子就明白了。

封装请求参数

原始的 url 为:

curl -s "http://ip.taobao.com/service/getIpInfo2.php?ip=119.75.213.61"
{"code":0,"data":{"ip":"119.75.213.61","country":"中国","area":"","region":"北京","city":"北京","county":"XX","isp":"电信","country_id":"CN","area_id":"","region_id":"110000","city_id":"110100","county_id":"xx","isp_id":"100017"}}

下面开始变魔术,将 url 网络调用变为 java 方法调用,调用方对网络基本无感知。

IPService.java

package testRetrofit.ip;

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface IPService {
    @GET("service/getIpInfo2.php")
    Call<ResponseBody> getip(@Query("ip") String ip);

}

MainIp.java

package testRetrofit.ip;

import java.io.IOException;

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Retrofit;

public class MainIp {
    public static void main(String[] args) throws IOException {
        Retrofit retrofit = new Retrofit.Builder().baseUrl("http://ip.taobao.com/").build();
        IPService service = retrofit.create(IPService.class);

        // 这里是调用方, 仅仅能看到execute()了一下
        Call<ResponseBody> repos = service.getip("119.75.213.61");
        ResponseBody repo = repos.execute().body();
        System.out.println(repo.string());

    }
}

运行结果

{"code":0,"data":{"ip":"119.75.213.61","country":"中国","area":"","region":"北京","city":"北京","county":"XX","isp":"电信","country_id":"CN","area_id":"","region_id":"110000","city_id":"110100","county_id":"xx","isp_id":"100017"}}

在本例中,通过接口注解,调用 java 方法,Retrofit 自动将其转化为 http 请求参数,去请求服务器,并返回结果。

本例主要演示参数的封装。

封装运行结果

上面的例子中,返回的结果为 ResponseBody,还需要自己处理。在本例中,通过定义一个 IP 类,来直接返回 IP 类对象。具体如下:

IPService2.java

package testRetrofit.ip2;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface IPService2 {
    @GET("service/getIpInfo2.php")
    Call<Result<IP>> getip(@Query("ip") String ip);
}

Result.java

package testRetrofit.ip2;

public class Result<T> {
    public int code;
    public T data;

    @Override
    public String toString() {
        return "Result [code=" + code + ", data=" + data + "]";
    }
}

IP.java

package testRetrofit.ip2;

public class IP {
    public String ip;
    public String country;
    public String area;
    public String region;
    public String city;

    public String county;
    public String isp;
    public String country_id;
    public String area_id;
    public String region_id;

    public String city_id;
    public String county_id;
    public String isp_id;

    @Override
    public String toString() {
        return "IP [ip=" + ip + ", country=" + country + ", area=" + area + ", region=" + region + ", city=" + city
                + ", county=" + county + ", isp=" + isp + ", country_id=" + country_id + ", area_id=" + area_id
                + ", region_id=" + region_id + ", city_id=" + city_id + ", county_id=" + county_id + ", isp_id="
                + isp_id + "]";
    }
}

MainIp2.java

package testRetrofit.ip2;

import java.io.IOException;

import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class MainIp2 {
    public static void main(String[] args) throws IOException {
        Retrofit retrofit = new Retrofit.Builder().baseUrl("http://ip.taobao.com/")
                .addConverterFactory(GsonConverterFactory.create()).build();

        IPService2 service = retrofit.create(IPService2.class);

        // 这里是调用方, 仅仅能看到execute()了一下,返回了IP对象结果。
        Call<Result<IP>> repos = service.getip("119.75.213.61");
        Result<IP> repo = repos.execute().body();
        System.out.println(repo.toString());
    }
}

运行结果

Result [code=0, data=IP [ip=119.75.213.61, country=中国, area=, region=北京, city=北京, county=XX, isp=电信, country_id=CN, area_id=, region_id=110000, city_id=110100, county_id=xx, isp_id=100017]]

到此为止,完成了一个入门使用说明。但是实际情况中,对于输入和输出可能有更复杂的情况。这时,怎么用 retrofit 来处理呢?

输入和输出

输入和输出可能有多种情况。

输入

输入参数的指定方法,可以通过 url,可以通过 body,可以通过表单、分块表单,可以通过 Header 等。

这里仅罗列一下,需要的时候可以去[1]中来找示例,都不复杂。

请求方法:

  • @GET
  • @POST
  • @PUT
  • @DELETE
  • @PATCH
  • @HEAD
  • @OPTIONS
  • @HTTP

请求标记:

  • @FormUrlEncoded: 表单中的 field
  • @Multipart: 分传上传
  • @Streaming: 流形式返回

请求参数:

  • @Headers: Header 控制
  • @Header: Header 控制
  • @HeaderMap: Header 控制
  • @Body: HTTP 请求 body
  • @Field: 表单中的 field
  • @FieldMap: 表单中的 field
  • @Part: 分传上传
  • @PartMap: 分传上传
  • @Path: 通过路径传递参数,如 "users/{user}/repos"
  • @Query:
  • @QueryMap:
  • @Url:

输出

输出的结果可以是 Json 格式的,可以是 Protobuf 格式的,可以是 XML 格式的,可以是 Scalars 格式的,也可以是用户自定义格式的。

前面的例子即是 Json 格式的。

  • Json 适配器:提供了 Gson, Jackson 和 Moshi 三种。
  • Protobuf 适配器:提供了 Protobuf 和 Wire 两种。
  • XML 适配器:Simple XML

自定义 Converter

如果返回的结果不是标准的 Json 格式,而是自定义的格式,又想返回成 Java 对象直接使用,怎么办呢?

这时可以通过自定义 Converter,来实现定制输出结果类。

下面举个自定义格式的例子:

通过 IPResultConverterIPResultConverterFactory 来解析返回的 IP Json,输出{国家}{省}{市}

IPResultConverter.java

package testRetrofit.ip3;

import java.io.IOException;
import java.util.Map;

import com.google.gson.Gson;

import okhttp3.ResponseBody;
import retrofit2.Converter;

public class IPResultConverter implements Converter<ResponseBody, String> {

	@Override
	public String convert(ResponseBody value) throws IOException {
	    // 将responseBody转换为了string
		Map result = new Gson().fromJson(value.string(), Map.class);
		if (result.containsKey("data")) {
			Map dataMap = (Map) result.get("data");
			return dataMap.get("country") + "_" + dataMap.get("region") + "_" + dataMap.get("city");
		}
		return null;
	}
}

IPResultConverterFactory.java

package testRetrofit.ip3;

import java.io.IOException;
import java.util.Map;

import com.google.gson.Gson;

import okhttp3.ResponseBody;
import retrofit2.Converter;

public class IPResultConverter implements Converter<ResponseBody, String> {

	@Override
	public String convert(ResponseBody value) throws IOException {
		Map result = new Gson().fromJson(value.string(), Map.class);
		if (result.containsKey("data")) {
			Map dataMap = (Map) result.get("data");
			return dataMap.get("country") + "_" + dataMap.get("region") + "_" + dataMap.get("city");
		}
		return null;
	}
}

IPService3.java

package testRetrofit.ip3;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface IPService3 {
	@GET("service/getIpInfo2.php")
	Call<String> getip(@Query("ip") String ip);

}

MainIp3.java

package testRetrofit.ip3;

import java.io.IOException;

import retrofit2.Call;
import retrofit2.Retrofit;

public class MainIp3 {
	public static void main(String[] args) throws IOException {
		Retrofit retrofit = new Retrofit.Builder().baseUrl("http://ip.taobao.com/")
				.addConverterFactory(new IPResultConverterFactory()).build();

		IPService3 service = retrofit.create(IPService3.class);

		Call<String> repos = service.getip("119.75.213.61");
		String repo = repos.execute().body();
		System.out.println(repo);
	}
}

结果

中国_北京_北京

自定义 CallAdapter

自定义 CallAdapter 是做什么的呢?

默认情况下,我们返回的是一个 Call<?> 的结果。如果我们不想要 Call<?>,而是想要 Observable<?>,或者想要 LiveData<?> 呢?

retrofit 允许通过自定义 CallAdapter,来对 Call<?> 再做一次转换。

如果看了前面的反应式宣言的话,这里这么做的目的就很好理解了。

同样举个例子,其中没有详细来写,仅做示例:

IPService4.java

package testRetrofit.ip4;

import okhttp3.ResponseBody;
import retrofit2.http.GET;
import retrofit2.http.Query;
import rx.Observable;

public interface IPService4 {
	@GET("service/getIpInfo2.php")
	Observable<ResponseBody> getip(@Query("ip") String ip);

}

MainIp4.java

package testRetrofit.ip4;

import java.io.IOException;

import okhttp3.ResponseBody;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import rx.Observable;
import rx.Subscriber;
import rx.schedulers.Schedulers;

public class MainIp4 {
	public static void main(String[] args) throws IOException {
		Retrofit retrofit = new Retrofit.Builder().baseUrl("http://ip.taobao.com/")
				.addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();

		IPService4 service = retrofit.create(IPService4.class);

		Observable<ResponseBody> repos = service.getip("119.75.213.61");

		repos.observeOn(Schedulers.io()).subscribe(new Subscriber<ResponseBody>() {

			@Override
			public void onCompleted() {
			}

			@Override
			public void onError(Throwable error) {
			}

			@Override
			public void onNext(ResponseBody body) {
				try {
					System.out.println(body.string());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});

		try {
			Thread.sleep(10000L);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

结果

{"code":0,"data":{"ip":"119.75.213.61","country":"中国","area":"","region":"北京","city":"北京","county":"XX","isp":"电信","country_id":"CN","area_id":"","region_id":"110000","city_id":"110100","county_id":"xx","isp_id":"100017"}}

参考

  1. Retrofit 官方文档
  2. 你真的会用 Retrofit2 吗?Retrofit2 完全教程
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3168 引用 • 8207 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...