Retrofit 2使用方法举例
Retrofit是Square推出的一个“type-safe HTTP client for Android and Java”,通过将HTTP(REST) API转换为Java接口,极大地简化了HTTP API的使用。下面通过一个例子介绍Retrofit的基本使用,通过Retrofit向TMDb请求流行电影的信息,得到JSON格式的响应,由GSON解析后,以列表的形式显示出来。
Contents
0. The Movie Database
下面的例子中使用了The Movie Database(TMDb)的API来获取当前流行电影的信息。要使用TMDb的API,首先要获取一个API key,注册TMDb后,在这里申请API Key,然后点击账号名称,在左边栏的“API” / “Details”下查看自己的API key,如图1所示。
下面例子中使用“/discover/movie”端点来获取一系列电影的信息,如:
http://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=[YOUR API KEY]
可以得到以流行度(popularity)降序(desc)排列的一系列电影,注意把[YOUR API KEY] 替换为你自己的API key。将请求粘贴到浏览器,成功的话会得到一堆挤在一起的JSON文本,为了方便阅读,可以使用一些工具来将它格式化一下,格式化后结果可以参考这里。
1. 获取Retrofit
新建工程,在/app/build.gradle中dependencies下加入retrofit:
dependencies { ... compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' }
然后执行“Tools” / “Android” / “Sync Project with Gradle Files”,Retrofit就会被下载并添加到工程中。
不要忘了在AndroidManifest.xml中添加INTERNET权限:
<uses-permission android:name="android.permission.INTERNET" />
2.创建POJO
POJO即Plain Old Java Object,它类似于JavaBean,但可以不遵守JavaBean的一些约定。我们从服务器得到JSON格式的响应,使用GSON反序列化到POJO中。
首先来看一下TMDb返回的JSON,截取开头部分如下:
{ "page":1, "results":[ { "poster_path":"\/fYzpM9GmpBlIC893fNjoWCwE24H.jpg", "adult":false, "overview":"Thirty years after defeating the Galactic Empire, Han Solo and his allies face a new threat from the evil Kylo Ren and his army of Stormtroopers.", ... }, { "poster_path":"\/D6e8RJf2qUstnfkTslTXNTUAlT.jpg", "adult":false, "overview":"Armed with the astonishing ability to shrink in scale but increase in strength, con-man Scott Lang must embrace his inner-hero and help his mentor, Dr. Hank Pym, protect the secret behind his spectacular Ant-Man suit from a new generation of towering threats. Against seemingly insurmountable obstacles, Pym and Lang must plan and pull off a heist that will save the world.", ... }, ...
“page”:1 说明当前位于第一页,“results” 后是一个长度为20的列表,列表中的每个元素描述了一部电影的信息,包括“poster_path” 、“adult” 、“overview” 等。我们需要创建两个POJO类,一个对应包括“poster_path” 、“adult” 、“overview” 等的电影信息本身Movie类,一个对应包括“page” 和“results” 的整个响应MovieResponse类。
2.1. 创建Movie
根据JSON中对电影的描述,创建Movie类,Movie类中包含若干成员变量,与JSON中描述电影的字段一一对应。比如对应“adult” 和“overview” ,定义:
private boolean adult; private String overview;
注意这里的成员变量名要和JSON中字段的名称一致(之后要使用Gson对JSON进行格式化)。
当然成员变量也可以使用不同的名称,如对应“poster_path” ,定义:
@SerializedName("poster_path") private String posterPath;
这里成员变量名称为posterPath,通过@SerializedName(“poster_path”),告诉GSON它对应了”poster_path”字段。使用@SerializedName 需要在gradle中dependencies下添加:
compile 'com.google.code.gson:gson:2.5'
并在Movie.java中导入:
import com.google.gson.annotations.SerializedName;
完整文件可以参考这里。
2.2. 创建MovieResponse
对应JSON中的页数和电影列表,创建MovieResponse类,MovieResponse中包含“page” 和“results” ,“results” 是一个Movie列表:
public class MovieResponse { private long page; @SerializedName("results") private List<Movie> movies; public List<Movie> getMovies() { return movies; } }
完整文件可以参考这里。
3. 定义接口
接下来根据TMDb的API,使用Retrofit提供的annotation定义对应的接口。如前所述,这里使用了TMDb提供的“/discover/movie”端点来获取一系列电影的信息,参考文档,我们只使用sort_by 一个参数,如前面给出的:
http://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=[YOUR API KEY]
据此定义接口:
public interface MovieService { String SORT_BY_POPULARITY_DESC = "popularity.desc"; String SORT_BY_VOTE_AVERAGE_DESC = "vote_average.desc"; String SORT_BY_VOTE_COUNT_DESC = "vote_count.desc"; @GET("/3/discover/movie") Call<MovieResponse> getMovies(@Query("sort_by") String sortBy); }
@GET 表示这是一个GET方法,“/3/discover/movie” 指明请求的端点,Call<MovieResponse> 表示返回MovieResponse,@Query(“sort_by”) String sortBy 表示在“/3/discover/movie” 后面追加“sort_by” 请求参数,参数的值由String sortBy 指定。
完整文件可以参考这里。
4. 创建Retrofit
4.1. 添加Gson
默认情况下,Retrofit将HTTP正文反序列化为OkHttp的ResponseBody,可以通过添加额外的转换器来获得对其他类型的支持。
在我们的例子中,需要将JSON反序列化为之前定义的MovieResponse,这里使用Gson来解析JSON。在gradle中dependencies下添加(在2.1.中已添加过):
compile 'com.google.code.gson:gson:2.5'
然后创建Gson:
Gson gson = new GsonBuilder().create();
4.2. 添加OkHttpClient
在不同的平台上,Retrofit使用不同的HTTP client。在JVM上, Retrofit默认使用HttpUrlConnection,在Android上, 默认使用Apache HttpClient(Android 2.2及之前)或HttpUrlConnection HttpClient(Android 2.3及之后),也可以指定使用其他的client。
之前在定义接口getMovies()时,并没有加入与API key对应的参数,因为API key对于每个请求都是必须的,希望通过HTTP client在请求的末尾追加API key,以简化调用。这里使用OkHttp实现这一功能。首先在gradle中dependencies下添加:
compile 'com.squareup.okhttp:okhttp:2.7.0'
然后创建OkHttpClient:
OkHttpClient httpClient = new OkHttpClient();
4.2.1. 追加API key参数
为OkHttpClient添加Interceptor:
httpClient.interceptors().add(new Interceptor() { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request original = chain.request(); HttpUrl originalHttpUrl = original.httpUrl(); HttpUrl.Builder builder = originalHttpUrl.newBuilder() .addQueryParameter("api_key", BuildConfig.API_KEY); Request.Builder requestBuilder = original.newBuilder() .url(builder.build()) .method(original.method(), original.body()); Request request = requestBuilder.build(); return chain.proceed(request); } });
其中addQueryParameter(“api_key”, BuildConfig.API_KEY) 为查询请求追加“api_key” 参数,参数的值为BuildConfig.API_KEY 。这里使用了BuildConfig来获取API key,详见这里。
4.2.2. 打开Log
为了方便调试,可以打开Log:
httpClient.interceptors().add( new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
4.3. 创建Retrofit
最后来创建Retrofit:
final String BASE_URL = "http://api.themoviedb.org"; Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(httpClient) .addConverterFactory(GsonConverterFactory.create(gson)) .build();
这里还指定了BASE_URL ,前面定义getMovies()时使用的端点”/3/discover/movie”就是以此为基础。
得到Retrofit对象后,进一步创建接口:
movieService = retrofit.create(MovieService.class);
movieService 是MovieService类型的成员变量,其他类可以使用get方法获取MovieService,并调用其中的方法。
private MovieService movieService; ... public MovieService getMovieService() { return movieService; }
完整文件可以参考这里。
5. 调用接口
调用接口时,首先获取MovieService:
MovieService movieService = App.getRestClient().getMovieService();
然后调用getMovies(),得到Call<MovieResponse>:
Call<MovieResponse> call = movieService.getMovies(MovieService.SORT_BY_POPULARITY_DESC);
到这里为止,Retrofit还没有把查询请求发给服务器,Retrofit提供同步和异步的方法来进行请求。使用同步的方法只需调用call.execute();即可,向服务器的请求一般耗时较长,为防止卡顿,在UI线程应使用异步的方法:
call.enqueue(new Callback<MovieResponse>() { @Override public void onResponse(Response<MovieResponse> response, Retrofit retrofit) { if (response.isSuccess()) { MovieResponse movieResponse = response.body(); mMovies = movieResponse.getMovies(); Log.v(LOG_TAG, "onResponse(): mMovies size = " + mMovies.size()); updateListView(); } else { int statusCode = response.code(); Toast.makeText(MainActivity.this, "Error code: " + statusCode, Toast.LENGTH_SHORT).show(); } } @Override public void onFailure(Throwable t) { Toast.makeText(MainActivity.this, t.getMessage(), Toast.LENGTH_SHORT).show(); } });
其中call.enqueue() 使用回调函数的方法实现异步的请求,请求完成后,onResponse()会被调用,通过response.body() 获取由Gson反序列化后得到的movieResponse,并进一步通过movieResponse.getMovies() 得到Movie列表,之后更新UI。
需要注意的是,只要请求有回应,即便请求失败,onResponse()都会被调用,上面的代码使用isSuccess() 来确认请求是否成功(返回2XX)。
这里也是Retrofit 1.x版本和2.0版本之间的一个显著差异,1.x版本对应同步和异步的请求,需要定义不同的接口,在接口内定义回调函数;而2.0版本无论同步还是异步,接口定义都是一样的,只在调用方法上进行区别。如果发生连接错误,如没有网络连接,则会调用onFailure()。
完整文件可以参考这里。
6. 完整代码
完整代码可以在这里找到,使用了一个ListView来显示电影名称、发行时间和平均评分,运行效果如图2所示。
7. 更多
上面的例子只展示了Retrofit的一小部分功能,更多内容还请参考官方文档,Consuming APIs with Retrofit这篇文章也十分有用。值得一提的是,Retrofit还支持RxJava,可以使用Observable来代替Call,如:
@GET("/3/discover/movie") Observable<MovieResponse> getMovies(@Query("sort_by") String sortBy);