rest API客户端实现之Retrofit
rest API客户端实现之Retrofit
Linus Jhon什么是Retrofit
官方是这样的描述的
A type-safe REST client for Android and Java.
使用注解来描述HTTP请求,默认会集成URL参数替换。还提供了自定义头信息,多请求体,文件上传下载,模拟响应等功能。
怎样描述API终端 在你发起第一个请求之前,你需要描述你需要与之交互的API终端。首先你需要创建一个接口并且定义一个方法。
GitHubClient
下面的代码定义了一个接口GitHubClient和一个方法reposForUser来请求给定用户的仓库列表。 @GET注解声明此请求使用HTTP GET方法。在定义的方法中,当调用reposForUser方法时,{user}路径将替换为给定的变量值。
1 | public interface GitHubClient { |
定义了一个类,GitHubRepo,这个类包括返回数据的所有属性。
1 | public class GitHubRepo { |
关于前面提到的JSON映射:GitHubClient接口定义了一个名为reposForUser的方法,返回类型为List 。 Retrofit确保服务器响应得到正确映射。
Retrofit REST Client
在描述完APi接口和对象模型后,下面准备创建请求。Retrofit的所有请求的基础是Retrofit(2.0+)这个类。你可以使用构造器为所有请求设置一些常规选项,包括BaseURl和converter。
创建adapter后,可以创建一个客户端来执行请求。
1 | String API_BASE_URL = "https://api.github.com/"; |
在上边的代码中我们定义了BaseURl是”https://api.github.com/“,并且使用了最少的配置。Retrofit可以有更多的配置,但是在本例中不使用。
JSON Mapping
在大多数情况下,对服务器的请求和来自服务器的响应不是Java对象。 它们映射到一些其他格式中,如JSON。 GitHub的API使用JSON,在Retrofit2中,你需要显式的将一个转换器(convert)田家达Retrofit对象。
Retrofit in Use
在Retrofit 2中,您使用客户端获取call对象。一旦你对创建的call对象调用了.enqueue(异步请求),请求将由Retrofit进行。
1 | // Create a very simple REST adapter which points the GitHub API endpoint. |
Retrofit将返回一个方便的列表,你可以进一步使用它来显示您的应用程序中的数据。
API终端
怎样描述一个API终端
我们在一个接口文件中描述我们的API终端。
1 | public interface GitHubClient { |
现在让我们来看看这些选项中的细节。
HTTP Method
在Java接口中使用注解来描述每一个API终端,最终处理请求。第一件事就是定义HTTP请求方法,如GET,POST,PUT,DELETE等。Retrofit为每个请求方法都提供了注解,你只需要为每个HTTP方法添加下面的注解即可: @GET,@PSOT,@PUT,@DELETE,@PATCH,@HEAD
。
下面是几个简单的例子:
1 | public interface FutureStudioClient { |
HTTP Resource Location
此外,你需要为你的注解添加相对与BaseURL的String参数来完成路径,例如 @GET(“/ user / info”)。 在大多数情况下,您只会传递相对网址,而不传递完整网址(例如http://futurestud.io/api/user/info)。 这具有的优点是,Retrofit只需要一次请求基本URL(http://futurestud.io)。 如果你要更改API基本网址,则只需在一个位置更改它。 此外,它使一些更高端的事情,如动态基本URL,更容易。 不过,你可以指定完整的URL。
1 | public interface FutureStudioClient { |
Function Name & Return Type
Java方法声明:Call getUserInfo(); 这包含三个部分:
方法名—getUserInfo 你可以自由定义方法名称。 Retrofit不在乎,它不会对功能产生任何影响。不过,你应该选择一个名称,这将有助于你和其他开发人员了解什么是API请求。
方法返回的类型—UserInfo 你必须定义你期望从服务器的什么样的数据。例如,当您请求某些用户信息时,您可以将其指定为Call 。 UserInfo类包含将保存用户数据的属性。 Retrofit会自动映射它,您不必进行任何手动解析。如果你想要原始响应,你可以使用ResponseBody而不是像UserInfo这样的特定类。如果你根本不在乎服务器响应什么,你可以使用Void。在所有这些情况下,你必须将它包装到一个类型的Retrofit Call <>类中。
方法传递的参数—此处为空 您可以将参数传递给方法。有各种各样的可能选项,此处列举一些:
@Body: 发送Java对象作为请求体 @Url: 使用动态地址 @Field: 以表单形式发送数据
1 | public interface FutureStudioClient { |
Path Parameters
REST API是基于动态URL构建的。 您可以通过替换部分URL来访问资源,例如获取我们网页上的第三个教程可能是http://futurestud.io/api/tutorials/3。 最后的3指定您要访问的页面。 Retrofit提供了一种简单的方法来替换路径参数。 例如:
1 | public interface GitHubClient { |
这里,{user}值是动态的,并且将在请求发生时设置。 如果在URL中包含路径参数,则需要添加@Path()函数参数,其中@Path值与URL中的占位符匹配(在本例中为@Path(“user”))。 如有必要,您可以使用多个占位符。 只需确保您始终具有匹配参数的确切数量。 您甚至可以使用可选的路径参数。
Query Parameters
动态URL的另一个大部分是查询参数 与路径参数不同,您不需要将它们添加到注释URL。 你可以简单地添加一个方法参数@Query()和查询参数名称,描述类型,你很好去。 Retrofit会自动将其附加到请求。 如果传递一个空值作为查询参数,Retrofit将忽略它。 您还可以添加多个查询参数。
1 | public interface FutureStudioClient { |
在上面的例子中,你可以使用第二个方法来替换第一个方法,只需要把其他的值设置为null即可。
创建一个可复用的客户端
The ServiceGenerator Retrofit对象及其构建器是所有请求的核心。 在这里,你可以配置和准备请求,响应,认证,日志记录和错误处理。 让我们从简单的代码开始。 在其当前状态下,它仅定义一种方法为给定类/接口创建基本REST客户端,该接口从接口返回服务类。
1 | public class ServiceGenerator { |
ServiceGenerator类使用Retrofit的Retrofit构造器创建具有BaseURL(BASE_URL)的新REST客户端。 例如,GitHub的API的BaseURL位于https://api.github.com/。
createService方法将serviceClass(它是API请求的注释接口)作为参数,并从中创建一个可用的客户端。 在生成的客户端上,您将能够执行网络请求。
Why Is Everything Declared Static Within the ServiceGenerator?
我们想在整个应用程序中使用相同的对象(OkHttpClient,Retrofit,…),只打开一个套接字连接,处理所有的请求和响应,包括缓存和更多。 通常的做法是只使用一个OkHttpClient实例来重用开放套接字连接。 这意味着,我们需要通过依赖注入或使用静态字段将OkHttpClient注入到此类中。 正如你所看到的,我们选择使用静态字段。 并且因为我们在这个类中使用OkHttpClient,我们需要使所有字段和方法静态。
除了加快速度,我们可以在移动设备上节省一些有价值的内存,当我们不必一遍又一遍地重新创建相同的对象。
Using the ServiceGenerator
1 | GitHubClient client = ServiceGenerator.createService(GitHubClient.class); |
Preparing Logging
使用Retrofit 2进行日志记录是由称为HttpLoggingInterceptor的拦截器完成的。 您需要向OkHttpClient添加此拦截器的实例。 例如,您可以通过以下方式解决它:
1 | public class ServiceGenerator { |
有一些事情你必须知道。 首先,确保你没有不小心多次添加拦截器!通过httpClient.interceptors().contains(logging)来检查日志拦截器已经存在。 其次,确保不在每次createService调用上创建新的对象。 否则,将会使ServiceGenerator失去意义。
Prepare Authentication
需要在创建客户端时传递附加参数到createService。
让我们看一个Hawk身份验证的示例:
1 | public class ServiceGenerator { |
createService现在具有HawkCredentials的第二个参数。 如果传递非空值,它将创建必要的Hawk身份验证拦截器并将其添加到Retrofit客户端。 我们还需要重建Retrofit以将更改应用到下一个请求。
URL的处理与解析
Url Handling Introduction
在Retrofit 2中,所有的URL都是使用由HttpUrl类来解决的,它是由OkHttp3提供给你的。 尽管如此,它引入了您需要处理您的应用程序中的所有网址的方式的更改:BaseURL和endpointURl以及为特定请求定义的任何动态网址。
请记住:Retrofit 2中的网址会像网页上的链接一样处理: … 。
baseUrl Resolution
使用Retrofit,您需要一个总是具有相同BaseURL的特定API。这个BaseURl共享相同的方案和主机,您可以在一个地方(使用Retrofit.Builder())定义它,并在必要时更改它,而不必触及应用程序中的每个终端。
1 | Retrofit.Builder builder = new Retrofit.Builder().baseUrl("https://your.base.url/api/"); |
BaseURL用于每个请求,任何终端(如@GET等)都将针对此地址解析。BaseURL必修以斜杠结尾:/。具有相对路径地址的每个终端定义将正确解析,因为它将自身附加到已经定义或包括路径参数的BaseURL。
让我们来看一个例子:
Good Practice
base url: https://futurestud.io/api/
endpoint: my/endpoint
Result: https://futurestud.io/api/my/endpoint
Bad Practice
base url: https://futurestud.io/api
endpoint: /my/endpoint
Result: https://futurestud.io/my/endpoint
上面的示例说明了如果你不以斜杠结尾你的BaseURl的api路径参数将被忽略,并从生成的请求网址中删除。
实际上,Retrofit帮助你,如果你传递一个基本url没有尾部斜线。 它会抛出异常,告诉你,你的基本url需要以斜杠结尾。
Absolute Urls
你可以将绝对url传递给你的端点url。 尽管如此,这种技术可能需要在您的应用程序中调用适当的端点。 随着时间的推移,您的后端将发布一个新的API版本。 根据版本控制的类型,让我们假设您的后端开发人员选择在网址中的API版本。 您需要将基本网址从v2压缩到v3。 此时,您必须处理API v3引入的所有突变。 要依赖于所选的v2端点,可以使用绝对URL来直接指定API版本。
Example 1
base url: https://futurestud.io/api/v3/
endpoint: my/endpoint
Result: https://futurestud.io/api/v3/my/endpoint
Example 2
base url: https://futurestud.io/api/v3/
endpoint: /api/v2/another/endpoint
Result: https://futurestud.io/api/v2/another/endpoint
在更改BaseURL的情况下,你将自动更需吧所有端点以使用新的URL和请求。 可以看到,示例1的工作原理与预期一样,只是将端点url附加到针对v3的API调用的基本URL。
示例2说明了将基本URL升级到v3并仍然使用所选端点的API v2的情况。 这可能是由于您的客户端的巨大升级造成的,并且您仍然希望从其他端点的API v3的所有其他好处中获益。
Dynamic Urls or Passing a Full Url
使用Retrofit 2,您可以将给定的URL传递到端点,然后用于请求。也就是说,如果您已使用https定义了BaseURL,并且想要确保应用程序中的所有其他请求也都使用https,那么你只需使用双斜线//开始请求网址即可。 这是Web中的常见做法,以避免浏览器在同一页面上使用安全和不安全的资源时发出警告。
Example 3 — completely different url
base url: http://futurestud.io/api/
endpoint: https://api.futurestud.io/
Result: https://api.futurestud.io/
Example 4 — Keep the base url’s scheme
base url: https://futurestud.io/api/
endpoint: //api.futurestud.io/
Result: https://api.futurestud.io/
Example 5 — Keep the base url’s scheme
base url: http://futurestud.io/api/
endpoint: //api.github.com
Result: http://api.github.com
示例3显示了在使用完全不同的URL时替换BaseURL。 此示例在请求具有不同位置的文件或图像时很有用,例如某些文件在您自己的服务器上,而其他文件或图像存储在Amazon的S3上。 您只将该位置作为网址接收并使用Retrofit,您可以传递请求端点的完整网址。
如前所述,您可以保留BaseURL的方案。 在示例4中,我们不使用API的路径段,而是使用子域。 我们仍然想保留以前定义的方案,因此只传递带有前导//的完整网址。
示例5使用与定义的BaseURL相同的方案,但用给定的端点url替换主机和路径段。
在运行时更换BaseURL
The Core: ServiceGenerator ServiceGenerator使用多个静态字段和一个String常量API_BASE_URL,它保存API基址url:
1 | public class ServiceGenerator { |
Adjusting the ServiceGenerator
通过此设置,您无需在运行时更改API_BASE_URL常量。假如你在源代码中改变它,编译一个新的.apk并再次测试它,这是非常不方便,如果你正在使用多个API部署,我们将对ServiceGenerator类进行小的更改:
复制
1 | public class ServiceGenerator { |
将常量API_BASE_URL重命名为非最终字段apiBaseUrl。添加新的静态方法changeApiBaseUrl(String newApiBaseUrl),它将更改apiBaseUrl变量。 它还会创建一个新版本的Retrofit.Builder实例构建器。 因为我们正在为请求重新使用构建器,如果我们不创建一个新的实例,所有的请求仍然会违反原来的apiBaseUrl值。