Retrofit 是一款由 Square 公司开发的网络库,但是它和 OkHttp 的定位不同,OkHttp 侧重的是底层通信的实现,而 Retrofit 侧重的是上层接口的封装。事实上,Retrofit 就是 Square 公司在 OkHttp 的基础上进一步开发出来的应用层网络通信库,使得我们可以用更加面向对象的思维进行网络操作。
Retrofit 的设计基于以下几个事实:
同一款应用程序中所发起的网络请求绝大多数指向的是同一个服务器域名。(Retrofit 允许我们可以配置好一个根路径,然后在指定服务器接口地址时只需要使用相对路径即可,这样就不用每次都指定完整的 URL 地址)
服务器提供的接口通常是可以根据功能来归类的。(Retrofit 允许我们对服务器接口进行分类,将功能同属一类的服务器接口定义到同一个接口文件当中,从而让代码结构变得更加合理)
其实大多数人并不关心网络的具体通信细节,但是传统网络库的用法却需要编写太多网络相关的代码。(Retrofit 让我们完全不用关心网络通信的细节,只需要在接口文件中声明一系列方法和返回值,然后通过注解的方式指定该方法对应哪个服务器接口,以及需要提供哪些参数。当我们在程序中调用该方法时,Retrofit 会自动向对应的服务器接口发起请求,并将响应的数据解析成返回值声明的类型。)
准备工作
添加权限:
1 <uses-permission android:name ="android.permission.INTERNET" />
引入依赖:
1 2 3 implementation 'com.squareup.retrofit2:retrofit:2.7.1' implementation 'com.squareup.retrofit2:retrofit-converters:2.7.1' implementation 'com.squareup.retrofit2:retrofit-adapters:2.7.1'
添加扩展功能,如:
1 2 implementation 'com.squareup.retrofit2:converter-gson:2.7.1' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.1'
如果现有的扩展包不能满足需要,还可以自己扩展 converter,adapter 等。
基本使用
定义接口 Retrofit 不直接做网络请求,是按照接口去定制能做网络请求的 Call 网络工作对象。
1 2 3 4 5 6 7 8 9 10 11 12 public interface INetApiService { @GET("/demo/user") Call<UserBean> getData (@Query("name") String name) ; }
获取相关对象 依次获得 Retrofit 对象、接口实例对象、网络工作对象 :
首先,需要新建一个 retrofit 对象。 然后,根据上一步的接口,实现一个 retrofit 加工过的接口对象。 最后,调用接口函数,得到一个可以执行网络访问的网络工作对象。
1 2 3 4 5 6 7 8 9 10 11 Retrofit retrofit = new Retrofit .Builder() .baseUrl(URL) .addConverterFactory(GsonConverterFactory.create()) .build(); INetApiService iNetApiService = retrofit.create(INetApiService.class);Call<UserBean> callWorker = iNetApiService.getData("xx" );
访问网络请求 1 2 3 4 5 6 7 8 9 10 11 12 13 callWorker.enqueue(new Callback <UserBean>() { @Override public void onResponse (Call<UserBean> call, Response<UserBean> response) { Log.e(TAG,response.toString()); } @Override public void onFailure (Call<UserBean> call, Throwable t) { } });
参数注解
Retrofit 的注解分为三大类 :
@Path 动态配置 URL 地址:
1 2 3 4 5 6 @GET("{path}/getIpInfo.php?ip=59,108,54,37") Call<UserBean> getData (@Path("path") String path) ; Call<UserBean> callWorker = iNetApiService.getData("service" );
1 2 3 @GET("getIpInfo.php") Call<UserBean> getData (@Query("ip") String ip) ;
@QueryMap 动态指定查询条件组:
1 2 3 4 5 6 7 8 9 @GET("getIpInfo.php") Call<UserBean> getData (@QueryMap Map<String,String> options) ; HashMap<String,String> map = new HashMap <>(); map.put("ip" ,"59.108.54.37" ); map.put("" ,"" ); Call<UserBean> callWorker = iNetApiService.getData(map);
@Field 传输数据类型为键值对:
1 2 3 4 @FormUrlEncoded @POST("getIpInfo.php") Call<UserBean> getData (@Field("ip") String first) ;
@Body 传输数据类型 JSON 字符串:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @POST("getIpInfo.php") Call<UserBean> getData (@Body Ip json) ; public class Ip { private String ip; private String name; public Ip (String ip,String name) { this .ip = ip; this .name = name; } } Call<UserBean> callWorker = iNetApiService.getData(new Ip (msg,"xx" ));
@Part 单个文件上传:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Multipart @POST("user/photo") Call<UserBean> getData (@Part MultipartBody.Part photo, @Part("description") RequestBody description) ; File file = new File (Environment.getExternalStorageDirectory(),"wangshu.png" );RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png" ),file);MultipartBody.Part photo = MultipartBody.Part.createFormData("photos" ,"wangshu.png" ,photoRequestBody); INetApiService iNetApiService = retrofit.create(INetApiService.class);String msg="59.108.54.37" ; Call<UserBean> callWorker =iNetApiService.getData(photo,RequestBody.create(null ,"wangshu" ));
@PartMap 多个文件上传:
1 2 3 4 @Multipart @POST("user/photo") Call<UserBean> getData (@PartMap Map<String,RequestBody> photos,@Part("description") RequestBody description) ;
消息报头
在 HTTP 请求中,为了防止攻击或过滤掉不安全的访问,或者添加特殊加密的访问等,以便减轻服务器的压力和保证请求的安全,通常都会在消息报头中携带一些特殊的消息头处理。Retrofit 提供了两种添加消息报头的方式:
静态添加 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @GET("getIpInfo.php?ip=59.108.54.37") @Headers({ "Accept-Encoding: application/json", "User-Agent: MoonRetrofit" }) Call<UserBean> getData () ; OkHttpClient okHttpClient = new OkHttpClient .Builder() .addInterceptor(new Interceptor () { @Override public okhttp3.Response intercept (Chain chain) throws IOException { Request request = chain.request(); Log.e(TAG, String.valueOf(request.headers())); return chain.proceed(request); } }).build(); Retrofit retrofit = new Retrofit .Builder() .baseUrl("http://ip.taobao.com/service/" ) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(okHttpClient) .build(); INetApiService iNetApiService = retrofit.create(INetApiService.class); String msg="59.108.54.37" ; Call<UserBean> callWorker = iNetApiService.getData(); callWorker.enqueue(new Callback <UserBean>() { @Override public void onResponse (Call<UserBean> call, Response<UserBean> response) { Log.e(TAG,response.toString()); Log.e(TAG,response.body().toString()); } @Override public void onFailure (Call<UserBean> call, Throwable t) { } });
动态添加 1 2 @GET("getIpInfo.php?ip=59.108.54.37") Call<ResponseBody> getData (@Header("Location") String location) ;
功能扩展
OkHttpClient Retrofit 可使用自己扩展的 OkHttpClient,一般最常扩展的就是 Interceptor 拦截器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 OkHttpClient okHttpClient = new OkHttpClient .Builder() .addInterceptor(new Interceptor () { @Override public okhttp3.Response intercept (Chain chain) throws IOException { Request.Builder builder = chain.request().newBuilder(); builder.addHeader("Accept-Charset" , "UTF-8" ); builder.addHeader("Accept" , " application/json" ); builder.addHeader("Content-type" , "application/json" ); Request request = builder.build(); return chain.proceed(request); } }).build(); Retrofit retrofit = new Retrofit .Builder() .baseUrl(URL) .addConverterFactory(GsonConverterFactory.create()) .client(okHttpClient) .build();
addConverterFactory 扩展的是对返回的数据类型的自动转换,把一种数据对象转换为另一种数据对象。
1 2 3 4 5 Retrofit retrofit = new Retrofit .Builder() .baseUrl(URL) .addConverterFactory(GsonConverterFactory.create()) .build();
如果现有的扩展包不能满足需要,可以继承 Retrofit 的接口。retrofit2.Converter<F,T>,自己实现 Converter 和ConverterFactory。
addCallAdapterFactory 扩展的是对网络工作对象 callWorker 的自动转换,把 Retrofit 中执行网络请求的 Call 对象,转换为接口中定义的Call 对象。
Retrofit底 层虽然使用了 OkHttpClient 去处理网络请求,但并没有使用 okhttp3.call 这个 Call 接口,而是自己又建了一个 retrofit2.Call 接口,OkHttpCall 继承的是 retrofit2.Call,与 okhttp3.call 只是引用关系。(依赖倒置原则)
1 2 3 4 5 6 7 public interface INetApiService { @GET("/demo/yy") Flowable<UserBean> getInfo (@Query("id") String id) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Retrofit retrofit = new Retrofit .Builder() .baseUrl(URL) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(okHttpClient) .build(); INetApiService iNetApiService = retrofit.create(INetApiService.class);Flowable<UserBean> callWorker = iNetApiService.getInfo("111" );
如果现有的扩展包不能满足需要,可以继承 Retrofit 的接口 retrofit2.CallAdapter<R,T>,自己实现 CallAdapter 和 CallAdapterFactory。
Kotlin 用法
首先添加依赖 :
1 2 3 4 5 implementation 'com.squareup.retrofit2:retrofit:2.7.1' implementation 'com.squareup.retrofit2:converter-gson:2.7.1'
data class 可以通过 JSON To Kotlin Class(JsonToKotlinClass)插件生成,快捷键 Option + K on Mac。
使用 Kotlin 简化 Retrofit 网络请求的回调 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 val appService = ServiceCreator.create<AppService>()appService.getAppData().enqueue(object : Callback<List<AppBean>>{ override fun onResponse (call: Call <List <AppBean >>, response: Response <List <AppBean >>) { } override fun onFailure (call: Call <List <AppBean >>, t: Throwable ) { } }) suspend fun getAppData () { try { val appList = ServiceCreator.create<AppService>().getAppData().await() }catch (e:Exception){ } } suspend fun <T> Call<T> .await () : T{ return suspendCoroutine { continuation -> enqueue(object : Callback<T>{ override fun onResponse (call: Call <T >, response: Response <T >) { val body = response.body() if (body!=null )continuation.resume(body) else continuation.resumeWithException(RuntimeException("response body is null" )) } override fun onFailure (call: Call <T >, t: Throwable ) { continuation.resumeWithException(t) } }) } }
注:挂起函数只能在协程作用域或其他挂起函数中调用,使用起来会有局限性。但通过合理的项目架构设计,也可以轻松地将各种协程的代码应用到一个普通的项目当中。
示例1
1 2 3 4 5 6 7 8 9 10 11 interface Api { @GET("users/{user}/repos" ) fun listRepos (@Path("user" ) user:String ) : Call<List<Repo>> @GET("users/{user}/repos" ) suspend fun listReposKt (@Path("user" ) user:String ) : List<Repo> }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 val retrofit = Retrofit.Builder() .baseUrl("https://api.github.com/" ) .addConverterFactory(GsonConverterFactory.create()) .build() val api = retrofit.create(Api::class .java) GlobalScope.launch { try { val repos = api.listReposKt("feiyeyuanye" ) binding.textView.text = repos[0 ].name }catch (e:Exception){ binding.textView.text = e.message } }
示例2 :
1 class AppBean (val id: String, val name: String, val version: String)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface AppService { @GET("get_data.json" ) fun getAppData () : Call<List<AppBean>> }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class RetrofitActivity : BaseActivity () { override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_retrofit) btnGet.setOnClickListener{ val retrofit = Retrofit.Builder() .baseUrl("http://10.0.2.2/" ) .addConverterFactory(GsonConverterFactory.create()) .build() val appService = retrofit.create(AppService::class .java) appService.getAppData().enqueue(object : Callback<List<AppBean>> { override fun onResponse (call: Call <List <AppBean >>, response: Response <List <AppBean >>) { val list = response.body() if (list != null ){ for (app in list){ Log.d("TAG" ,"id is ${app.id} " ) Log.d("TAG" ,"name is ${app.name} " ) Log.d("TAG" ,"version is ${app.version} " ) } } } override fun onFailure (call: Call <List <AppBean >>, t: Throwable ) { t.printStackTrace() } }) } } }
示例3 :
1 2 3 4 5 6 7 8 interface ApiService { @GET("users" ) suspend fun queryData () :List<UserBean> }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class TestActivity : AppCompatActivity () { private val TAG = "TAG_TestActivity" private lateinit var binding: ActivityTestBinding override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) binding = ActivityTestBinding.inflate(layoutInflater) setContentView(binding.root) initData() } private fun initData () { val retrofit = Retrofit.Builder() .baseUrl("https://api.github.com/" ) .addConverterFactory(GsonConverterFactory.create()) .build() val apiService = retrofit.create(ApiService::class .java) val job = Job() CoroutineScope(job).launch(Dispatchers.Main) { val result = Gson().toJson(apiService.queryData()) binding.tvText.text = result } } }
如果报错: java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; in class Ljava/lang/invoke/LambdaMetafactory; or its super classes (declaration of ‘java.lang.invoke.LambdaMetafactory’ appears in /apex/com.android.runtime/javalib/core-oj.jar)
在 build.gradle 中添加:
1 2 3 4 5 6 7 8 android { .... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }
处理复杂的接口地址类型
除了上述简单的固定接口地址外,还有可能接口地址中的部分内容是动态变化的,如:
GET http://example.com/<page>/get_data.json
,page 部分代表页数。可按照如下写法:
1 2 3 4 5 6 @GET("{page}/get_data.json" ) fun getData (@Path("page" ) page: Int ) :Call<Data>
服务器接口可能还会要求我们传入一系列的参数,格式如下:
GET http://example.com/get_data.json?u=<user>&t=<token>
。可按照如下写法:
1 2 3 4 5 @GET("get_data.json" ) fun getData (@Query("u" ) user: String , @Query("t" ) token:String ) :Call<Data>
除了 GET ,还有一些其它的常用请求方法。GET 请求用于从服务器获取数据,POST 请求用于向服务器提交数据,PUT 和 PATCH 请求用于修改服务器上的数据,DELETE 请求用于删除服务器上的数据。
比如服务器提供了如下接口:DELETE http://example.com/data/<id>
,可按如下写法:
1 2 3 4 5 6 7 @DELETE("data/{id}" ) fun deleteData (@Path("id" ) id:String ) :Call<ResponseBody>
如果需要向服务器提交数据:
POST http://example.com/data/create
{“id”:1, “content”:”The data”}
1 2 3 4 5 6 7 8 @POST("data/create" ) fun createData (@Body data :AppBean ) :Call<ResponseBody>
如果服务器接口要求在 HTTP 请求的 header 中指定参数:
1 2 3 GET http://example.com/get_data.json User-Agent: okhttp Cache-Control: max-age=0
1 2 3 4 5 6 7 8 9 10 11 12 13 @Headers("User-Agent: okhttp" ,"Cache-Control: max-age=0" ) @GET("get_data.json" ) fun getData () :Call<AppBean>@GET("get_data.json" ) fun getData1 (@Header("User-Agent" ) userAgent: String , @Header("Cache-Control" ) cacheControl:String ) :Call<AppBean>
Retrofit 构造器的最佳写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 object ServiceCreator { private const val BASE_URL = "http://10.0.2.2/" private val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build() fun <T> create (serviceClass: Class <T >) :T = retrofit.create(serviceClass) }
1 2 val appService = ServiceCreator.create(AppService::class .java)
通过泛型实化继续优化代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 object ServiceCreator { private const val BASE_URL = "http://10.0.2.2/" private val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build() fun <T> create (serviceClass: Class <T >) :T = retrofit.create(serviceClass) inline fun <reified T> create () :T = create(T::class .java) }
1 2 val appService = ServiceCreator.create<AppService>()
备注
参考资料:
GitHub 官方主页
第一行代码(第3版)
Android 进阶之光
从架构角度看 Retrofit 的作用、原理和启示