今天支付宝提现正式收费了!将对个人用户超出免费额度的提现收取0.1%的服务费,个人用户每人累计享有2万元基础免费提现额度,单笔服务费不到0.1元的则按照0.1元收取。值得注意的是,相比微信的零钱只能提现到本人的银行卡,支付宝提现可提现到本人银行卡和转账到他人银行卡。
除了提现之外,使用支付宝进行消费、理财、购买保险、手机充值、水电煤缴费、挂号、缴纳交通罚款、使用手机支付宝转账到支付宝账户、还款等服务不受任何影响。支付宝余额里的钱可以用来还信用卡,是不收取服务费的。
之前我也推送过一些Retrofit的文章,但是主要偏向进阶教程,好多朋友直呼高能、难理解。那么本篇来自
LaterEqualsNever
的投稿,继续保持了“高能状态”,大家别被吓到。。。这里的高能是指文章的篇幅,文章内容还是很基础的!
LaterEqualsNever
直接通过
自己对网络请求的理解以及示例,
带大家把官网撸个遍。我相信通过这篇文章,尚未入门的朋友肯定有很大收获!
LaterEqualsNever
的博客地址:
http://blog.csdn.net/ghost_programmer
在正式开始了解
Retrofit
的使用之前,我们有必要先了解一个概念,即
RESTful
。这是因为 Retrofit 的初衷就是根据 RESTful风格的API 来进行封装的。
关于
RESTful
的学习,可以参考一下:
《RESTful API 设计指南》
http://www.androidchina.net/3749.html
相信看完之后,就会对
Restful
有一个基本的认识和理解了。
但我们这里可以对 RESTful 的核心思想做一个最简练的总结,那就是:
所谓”资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。那么我们访问API的本质就是与网络上的某个资源进行互动而已
。那么,因为我们实质是访问资源,所以 RESTful 设计思想的提出者Fielding认为:
URI
当中不应当出现
动词
,因为”
资源
“表示一种
实体
,所以应该用
名词
表示,而动词则应该放在HTTP协议当中。那么举个最简单的例子:
xxx.com/api/createUser
xxx.com/api/getUser
xxx.com/api/updateUser
xxx.com/api/deleteUser
这样的API风格我们应该很熟悉,但如果要遵循 RESTful 的设计思想,那么它们就应该变为类似下面这样:
[POST]xxx.com/api/User
[GET
] xxx.com/api/User
[PUT]xxx.com/api/User
[DELETE]xxx.com/api/User
也就是说:因为这四个API都是访问服务器的 USER表,所以在 RESTfu l里URL是相同的,而是
通过HTTP不同的RequestMethod来区分增删改查的行为
。
而有的时候,如果某个API的行为不好用请求方法描述呢?比如说,A向B转账500元。那么,可能会出现如下设计:
POST /accounts/1/transfer/500/to/2
在RESTful的理念里,如果某些动作是HTTP动词表示不了的,你就应该把动作做成一种资源。可以像下面这样使用它:
POST /transaction HTTP/1.1
Host: 127.0.0.1
from=1&to=2&amount=500.00
好了,当然实际来说RESTful肯定不是就这点内容。这里我们只是了解一下RESTful最基本和核心的设计理念。
当我们要去学习一样新的技术,还有什么是比官方的资料更好的了呢?所以,第一步我们打开网址:
http://square.github.io/retrofit
然后开始阅读,我们发现官方的介绍非常简单粗暴,一上来就通过一个
Introduction
来展示了如何使用 Retrofit 来进行一个最基本的请求。下面我们就逐步的分析一下:
从上图中,我们首先注意到了一个关键的说明信息:
Retrofit会将你的HTTP API转换为Java中的interface的形式
。OK,接着看:
这里我们读到的描述是:Retrofit类 可以针对之前 定义的interface 生成一个具体实现。我们发现官方对此解释的很言简意赅,但更通俗的来讲的话:
也就是说虽然我们之前将此次请求的API信息封装为了一个接口,但我们也知道Java中接口是不能产生对象的,这时Retrofit类就站出来扮演了这个角色。
我们可以将Retrofit类看作是一个“工厂类”的角色,我们在接口中提供了此次的“产品”的生产规格信息,而Retrofit则通过信息负责为我们生产。
这里我们看到了一个重要的东西“
Call
”:
通过之前封装的请求接口对象创建的任一的Call都可以发起一个同步(或异步)的HTTP请求到远程服务器
。
之后说了一些通过注解来描述request的好处,然后这个简单的Introduction就结束了。那么,现在我们来简单总结一下,目前为止我们的感受如何。
我觉得就仅从以上简单的介绍当中我们起码有两点直观感受:那就是
解耦明确;使用简单
。通过注解的方式描述request让人眼前一亮。但与此同时:
我们发现读完Introduction后,仅仅是这个基本的用例中,都仍然有很多小细节需要我们通过实际使用之后才能搞明白。这可能在一定程度上说明了:
为什么说Retrofit的使用门槛相对于其它库来说要更高一些。不过没关系,我们自己先来写一个比官方Introduction更简单的用例,再逐步深入。
已经阅读完了官方的用例介绍,乍看之下没什么,但实际用起来说不定就得碰坑。为方便测试,仍然通过 servlet 在本地简单的实现服务器。
我们现在的设想可能是这样的,我只是想要写一个demo来测试一下用 Retrofit 来成功发起一次最基本的 get请求,所以最初的 doGet 无比简单:
完成了以上代码的编写,然后我们把该servlet的URL配置一下,言简意赅的,就配置为“
/api/retrofitTesting
”好了。这时服务器就准备完毕了。
很显然,下面我们就可以把焦点放在Android客户端的实现上来了。为了使用 Retrofit,所以我们的第一步工作自然就是在自己的项目中
设置依赖
:
配置好了依赖,接着我们就可以开始编码工作了。还记得官方用例的第一步吗?所以我们要做的显然是模仿它也把我们自己的HTTP API封装成interface。
public interface DemoService {
@GET("api/retrofitTesting")
Call testHttpGet();
}
// GSON - BEAN
class ResponseInfo {
String describe;
}
现在我们再来看这个所谓的API接口,可能就更加明确一点了。首先是通过
注解@GET
来声明本次请求方式为GET以及注明API-URL。
而就之后声明的方法来说:从其为 Call 的返回类型不难猜想多半与请求的发起有关,因为如果我们对 OkHttp 有所了解的话,一定就记得下面这样的代码:
newCall方法
实际就是返回一个Call类型的实例。而
Retrofit
中的Call接口相对于
OkHttp
添加了一个泛型,该泛型用于说明本次请求响应的数据解析类型。
那么这里的泛型为什么是我们自己建立的一个实体类呢?其实回忆一下之前服务器在 response 中返回的内容(JSON),就不难猜想到与GSON有关。
好的,我们继续按照官方用例中接下来的步骤去完善我们的demo。封装好了interface,接下来自然就是调用了,最终的代码如下:
有了之前的说明并对照官方示例,现在会发现以上代码很容易理解。而我们发现官方没有给出的step4其实也很熟悉,因为它和OkHttp的使用是相同的。
这个时候看上去我们的准备工作就已经完成了,于是兴致勃勃的编译并运行demo来查看一下效果,却发现收到了如下的一个异常:
为什么会出现这种情况呢?蛋疼啊!别急,通过异常信息的描述,我们得知这似乎与类型的转换相关,然后带着这个疑问再去查看官方文档,于是发现:
从上述信息我们得知 Retrofit 默认只能将响应体转换为 OkHttp 中的 ResponseBody,而我们之前为 Call 设置的泛型类型是自定义的类型 ResponseInfo 。
将JSON格式的数据转换为 Java-BEAN,很自然就会想到GSON。而 Retrofit 如果要执行这种转换是要依赖于另一个库的,所以我们还得在项目中配置另一个依赖:
之后,在构造Retrofit对象的时候加上一句代码就可以了:
这个时候,当我们再次运行程序就没问题了,成功的得到如下的日志打印:
现在,经过我们的一番摸索和折腾,关于Retrofit很基本的第一个demo就捣鼓出来了。
其实这是有意义的,因为回想一下会发现:现在我们对于Retrofit大致的使用套路,在心里已经有个一二三了。
前面我们说到对于Retrofit的使用已经有了一个基本的认识和了解,接下来要做的自然就是深入和继续学习更多的使用细节。那么很显然,回到官方吧。
事实上前面我们已经使用到了
注解@GET
,这里就是告诉我们这种注解其实对于HTTP其它常用的请求方式(GET,POST,PUT,DELETE等等)都封装了。
而对于
@GET
来说,我们知道HTTP-GET是可以将一些简单的参数信息直接通过URL进行上传的,所以URL又可以像下面那样使用:
@GET("api/retrofitTesting?param=value")
replacement blocks与@path
不知道大家注意到官方示例和我们刚才自己测试写的demo中有一个细小的差别没,就是下面这样的东西:
//官方的
@GET("users/{user}/repos")
//我们的
@GET("api/retrofitTesting")
我们注意到官方的API的URL中有一个”
{user}
“这样的东西?它的作用是什么呢?我们也能在官方文档上找到答案:
从上述介绍中,我们注意到一个叫做
replacement blocks
的东西。可以最简单的将其理解为路径替换块,用”{}”表示,与
注解@path
配合使用。
当然我们自己实际去使用一下能够对其有一个更加深刻的理解,所以我们将我们之前的demo修改一下,变成下面这样:
使用的方式也会有所不同,我们在调用的使用需要将对应的值传给
responseInfo
方法。
Call call = service.testHttpGet("retrofitTesting");
这样修改过后的实际效果实际上与我们之前的demo是一样的,那么这样做的好处是什么?显然是为了解耦。以官方的例子来说:
“
https://api.github.com/users/{user}/repos
”中的{user}就是为了针对不同的github用户解耦。因为这里假设代入我的github,URL就将变成:
“
https://api.github.com/users/RawnHwang/repos
”。而github的用户千千万万,如果使用我们之前的方式代码就会如下:
@GET("users/RawnHwang/repos")
这二者的优劣一目了然,我们肯定不会想要为了获取不同的user的repos去写N多个套路完全相同的API - interface吧。
@Query
前面我们讲到:对于@GET来说,参数信息是可以直接放在url中上传的。那么你马上就反应过来了,这一样也存在严重的耦合!于是,就有了
@query
。
同样,为了便于理解,我们仍然自己来实际的使用一下。就用我们之前的举到的例子好了,这里我们用 @query 来替换我们之前说到的如下代码:
@GET("api/retrofitTesting?param=value")
替换为:
调用的时候改为:
Call call = service.testHttpGet("value");
然后就可以在服务器查看是否成功接收到参数了:
@QueryMap
聪明的你现在肯定还不满足,因为可能有这样的疑问:假设我要在参数中上传10个参数呢?这意味着我要在方法中声明10个@Query参数?当然不是!
我们看到了,Retrofit也考虑到了这点,所以针对于复杂的参数上传,为我们准备了@QueryMap。现在来修改我们自己的demo:
调用的时候自然也发生了变化:
Map params = new HashMap();
params.put("param1","value1");
params.put("param2","value2");
Call call = service.testHttpGet(params);
@POST
有了之前的基础,现在我们免不了要捣鼓一下 POST。相信有了 @GET 的使用经验,如果只是想发发简单的 POST请求 是没有多大的难度,我们关注下 POST的BODY,即请求体的使用技巧。
通过官方文档,我们发现出现了一个新的东西叫做“
@Body
”,顾名思义它就是用来封装请求体的。而同时通过后面的“
User
”参数类型,我们不难推断出:使用
@Body
时,是通过实体对象的形式来进行封装的。那么闲话少说,我们当然仍旧是自己动手来试一下:
首先,我们编写一下 servlet 的 doPost 方法,假设我们这里提供一个新建 User 的API。完成后为该POST-request配置一个新的API URL,我们这里配置为:“
/api/users
”。然后,在Android端编写一个新的interface,大致如下:
这样其实就搞定了,是不是很简单。然后我们通过如下代码去调用测试就行了:
Call call = service.uploadNewUser(new User("tsr","male",24));
这里唯一需要说明的就是,通过 @BODY 这种方式封装请求体,Retrofit 是通过JSON的形式来封装数据的。我们可以在服务器读取流中的数据打印:
{"name":"tsr","gender":"male","age":24}
我单独额外说明一下这个的初衷是因为:这种情况以servlet来说,通过如下的形式是读取不到的对应的参数值的(返回null),需要自己解析。
//输出为null
System.out.println(request.getParameter("username"));
@FormUrlEncoded
这时有的朋友就说了,那我要是就想要通过request.getParameter的方式直接读取参数信息呢?没关系,也是可以的,使用
@
FormUrlEncoded
搞定。
其实通过这个注解的命名,我们就很容易联系到 HTTP Content-Type 中的 application/x-www-form-urlencoded:而其实在服务器打印Content-Type的话,会发现的确如此。也就是说,其实使用该注解过后,正是通过表单形式来上传参数的。
然后是依旧是调用,这时服务器就可以通过request.getParameter直接读取参数了:
Call call = service.uploadNewUser("tsr","male",24);
当然,这个时候难免又会想起那个老梗:要写这么多的 @Field 参数?当然不是,也有@FieldMap 供我们使用,使用方法参照 @QueryMap
@Headers与@Header
使用了 @FormUrlEncoded 之后,不知道你有没有好奇一下,假设我们的参数中含有中文信息,会不会出现乱码?让我们来验证一下:
Call call = service.uploadNewUser("张德帅","male",24);
这里上传的 username 信息是中文,而在服务器读取后进行打印,其输出的是“?????·???”。没错,确实出现乱码了。
这个时候我们应该如何去解决呢?当然可以通过 URLEncode 对数据进行指定编码,然后服务器再进行对应的解码读取:
String name = URLEncoder.encode("张德帅","UTF-8");
Call call = service.uploadNewUser(name,"male",24);
但如果了解一点 HTTP协议 的使用,我们知道还有另一种解决方式:在 Request-Header 中设置 charset 信息。于是,这个时候就涉及到添加请求头了:
关于
@
Headers
的使用看上去非常简单。那么,接下来我们就来修改一下我们之前的代码:
通过 @Headers 我们在 Content-type 中同时指明了编码信息,再次运行程序测试,就会发现服务器正确读取到了中文的信息。
除了
@Header
s
之外,还有另一个注解叫做
@Header
。它的不同在于是动态的来添加请求头信息,也就是说更加灵活一点。我们也可以使用一下:
读取response header
通过上面的总结我们知道通过 @Header 可以在请求中 添加header,那么我们如何去读取响应中的 header 呢?我们会发现官方文档并没有相关介绍。
那么显然我们就只能自己看看了,一找发现对于 Retrofit2 来说 Response类 有一个方法叫做 headers(),通过它就获取了本次请求所有的响应头。
那么,我们记得在 OkHttp 里面,除了 headers(),还有用于获取单个指定头的 header() 方法。我们能不能在 Retrofit 里使用这种方式呢?答案是可以。
我们发现 Retrofit 的 Response 还有一个方法叫做 raw(),调用该方法就可以把 Retrofit 的Response 转换为原生的 OkHttp 当中的 Response。而现在我们就很容器实现 header 的读取了吧。
okhttp3.Response okResponse = response.raw();
okResponse.header("Cache-Control");
@Multipart 与文件上传
在官方文档中,实际上还有一个重要的注解,那就是
@Multipart
。这个使用起来相对要复杂一点,所以放在这里。下面我们就来看一下这个东西的使用。
可以看到官方文档上对该注解使用的说明非常简单,但这个注解使用起来却不是那么简单,这就很烦人了。不过也没关系,我们由浅入深的来看一下。
这里对官方示例做了简化,因为我们在
@Part
参数中只使用了String类型,而没有声明 RequestBody 类型。接着就是调用它来进行测试:
Call call = service.testMultipart("this is the part1","this is the part2");
为了一探究竟,我们现在在服务器打印一下 Content-Type 与 请求体(request-body) 分别是怎样的:
现在我们来分析一下从打印的请求体中,我们能够得到哪些信息: