目前Github上使用比较多的Android Http库依次是Retrofit,okhttp,android-async-http,okhttp-utils,async-http-client,等等;
中间经过一系列的分析,根据start,更新速度,作者,库的实用性,性能,以及拓展性等等,最终选出了3个PK.(Retrofit,okhttp,okhttp-utils)
其中Retrofit,okhttp都是square公司出的okhttp是底层库,使用起来比较麻烦,肯定需要2次封装,这里okhttp-utils是洪洋大神在okhttp上进行的封装开源库,刚好弥补了这个缺陷,是目前封装的比较好的。
而Retrofit同样也是在okhttp上进行封装的,使用的是注解,使用起来相当方便,并且有很好的扩展性,PK下来。
Retrofit获胜。
1、okhttp 和 async http是一个基础的通信库,都很强大,但需要自己封装使用才更方便。另外okhttp已经被谷歌官方用在android源码中了。
2、retrofit和 volley是属于比较高级点的封装库了 其中 retrofit是默认使用okhttp volley也支持okhttp作为其底层通信的部件。retrofit的特点是使用清晰简单的接口,非常方便,而 volley在使用的时候也还简单,不过要使用高级一点的功能需要自己自定义很多东西
3、volley是一个简单的异步http库,仅此而已。缺点是不支持同步,这点会限制开发模式;不能post大数据,所以不适合用来上传文件。
4、android-async-http。与volley一样是异步网络库,但volley是封装的httpUrlConnection,它是封装的httpClient,而android平台不推荐用HttpClient了,所以这个库已经不适合android平台了。
5、okhttp是高性能的http库,支持同步、异步,而且实现了spdy、http2、websocket协议,api很简洁易用,和volley一样实现了http协议的缓存。
6、retrofit与picasso一样都是在okhttp基础之上做的封装,项目中可以直接用了。
7、retrofit主要针对的是url请求 ,okhttp在实际项目中直接用很麻烦,一般需要自己封装一下(有一个大坑是他onResponse方法默认是在异步线程,不能直接操作UI
既然选择了Retrofit,那我们就来一起了解一下吧。本次分享要求:理解Retrofit与okhttp的区别,简单的学会使用Retrofit;
首先贴出官网地址 http://square.github.io/retrofit
这里也主要是根据官网给出的案列进行推进讲解:
Retrofit和Java领域的ORM概念类似, ORM把结构化数据转换为Java对象,而Retrofit 把REST API返回的数据转化为Java对象方便操作,并且retrofit非常适合于restful url格式的请求,更多使用注解的方式提供功能。
compile ‘com.squareup.retrofit2:retrofit:2.0.2’
retrofit在使用的过程中,需要定义一个接口对象,我们首先演示一个最简单的get请求,接口如下所示:
请求如下的URL:http://192.168.31.242:8080/springmvc_users/user/users
public interface IUserBiz
{
@GET("users")
Call> getUsers();
}
可以看到有一个getUsers()方法,通过@GET注解标识为get请求,@GET中所填写的value和baseUrl组成完整的路径,baseUrl在构造retrofit对象时给出。
下面看如何通过retrofit完成上述的请求:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.31.242:8080/springmvc_users/user/")
.addConverterFactory(GsonConverterFactory.create())
.build();
IUserBiz userBiz = retrofit.create(IUserBiz.class);
CallUser
>> call = userBiz.getUsers();
call.enqueue(new CallbackUser
>>()
{
@Override
public void onResponse(CallUser>> call, ResponseUser>> response)
{
Log.e(TAG, "normalGet:" + response.body() + "");
}
@Override
public void onFailure(CallUser
>> call, Throwable t)
{
}
});
依然是构造者模式,指定了baseUrl和Converter.Factory,该对象通过名称可以看出是用于对象转化的,本例因为服务器返回的是json格式的数组,所以这里设置了GsonConverterFactory完成对象的转化。
ok,这里可以看到很神奇,我们通过Retrofit.create就可以拿到我们定义的IUserBiz的实例,调用其方法即可拿到一个Call对象,通过call.enqueue即可完成异步的请求。
具体retrofit怎么得到我们接口的实例的,以及对象的返回结果是如何转化的,我们后面具体分析。
新的URL定义方式
Retrofit 2.0使用了新的URL定义方式。Base URL与@Url 不是简单的组合在一起而是和的处理方式一致。用下面的几个例子阐明。
ps:貌似第二个才符合习惯。
对于 Retrofit 2.0中新的URL定义方式,这里是我的建议:
Base URL: 总是以 /结尾
@Url: 不要以 / 开头
比如 上例的那种
而且在Retrofit 2.0中我们还可以在@Url里面定义完整的URL:
public interface IUserBiz
{
@POST("http://192.168.31.242:8080/springmvc_users/user/users")
Call> getUsers();
}
这种情况下Base URL会被忽略,适合一个项目会出现多个BaseUrl的情况。
可以看到Retrofit 1.9在URL的处理方式上发生了很大变化。它和前面的版本完全不同。如果你想把代码迁移到Retrofit 2.0,别忘了修正URL部分的代码。
新的Service定义方式,不再有同步和异步之分
关于在Retrofit 1.9中service 接口的定义,如果你想定义一个同步的函数,你应该这样定义:
/* Synchronous in Retrofit 1.9 */
public interface APIService {
@POST("/list")
Repo loadRepo();
}
而定义一个异步的则是这样:
/* Asynchronous in Retrofit 1.9 */
public interface APIService {
@POST("/list")
void loadRepo(Callback cb);
}
但是在Retrofit 2.0上,只能定义一个模式,因此要简单得多。
/* Retrofit 2.0 */
public interface APIService {
@POST("/list")
Call loadRepo();
}
而创建service 的方法也变得和OkHttp的模式一模一样。如果要调用同步请求,只需调用execute;而发起一个异步请求则是调用enqueue。
同步请求
// Synchronous Call in Retrofit 2.0
Call call = service.loadRepo();
Repo repo = call.execute();
以上的代码会阻塞线程,因此你不能在安卓的主线程中调用,不然会面临NetworkOnMainThreadException。如果你想调用execute方法,请在后台线程执行。
异步请求
// Synchronous Call in Retrofit 2.0
Call call = service.loadRepo();
call.enqueue(new Callback() {
@Override
public void onResponse(Response response) {
// Get result Repo from response.body()
}
@Override
public void onFailure(Throwable t) {
}
});
以上代码发起了一个在后台线程的请求并从response 的response.body()方法中获取一个结果对象。注意这里的onResponse和onFailure方法是在主线程中调用的。
我建议你使用enqueue,它最符合 Android OS的习惯。
取消正在进行中的业务
service 的模式变成Call的形式的原因是为了让正在进行的事务可以被取消。要做到这点,你只需调用call.cancel()。
call.cancel();
事务将会在之后立即被取消。好简单嘿嘿!
在Retrofit 1.9中,GsonConverter 包含在了package 中而且自动在RestAdapter创建的时候被初始化。这样来自服务器的son结果会自动解析成定义好了的Data Access Object(DAO)
但是在Retrofit 2.0中,Converter 不再包含在package 中了。你需要自己插入一个Converter 不然的话Retrofit 只能接收字符串结果。同样的,Retrofit 2.0也不再依赖于Gson 。
如果你想接收json 结果并解析成DAO,你必须把Gson Converter 作为一个独立的依赖添加进来。
compile 'com.squareup.retrofit:converter-gson:2.0.2'
然后使用addConverterFactory把它添加进来。注意RestAdapter的别名仍然为Retrofit。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://api.nuuneoi.com/base/")
.addConverterFactory(GsonConverterFactory.create())
.build();
service = retrofit.create(APIService.class);
这里是Square提供的官方Converter modules列表。选择一个最满足你需求的。
Gson: com.squareup.retrofit:converter-gson
Jackson: com.squareup.retrofit:converter-jackson
Moshi: com.squareup.retrofit:converter-moshi
Protobuf: com.squareup.retrofit:converter-protobuf
Wire: com.squareup.retrofit:converter-wire
Simple XML: com.squareup.retrofit:converter-simplexml
你也可以通过实现Converter.Factory接口来创建一个自定义的converter 。
我比较赞同这种新的模式。它让Retrofit对自己要做的事情看起来更清晰。
当然也支持自定义,你可以选择自己写转化器完成数据的转化,这个后面将具体介绍。
那么,通过这么一个简单的例子,应该对retrofit已经有了一个直观的认识,下面看更多其支持的特性。
以下是引用洪洋的博客内容:
文章开头提过,retrofit非常适用于restful url的格式,那么例如下面这样的url:
//用于访问zhy的信息
http://192.168.1.102:8080/springmvc_users/user/zhy
//用于访问lmj的信息
http://192.168.1.102:8080/springmvc_users/user/lmj
即通过不同的username访问不同用户的信息,返回数据为json字符串。
那么可以通过retrofit提供的@PATH注解非常方便的完成上述需求。
我们再定义一个方法:
public interface IUserBiz
{
@GET("{username}")
Call getUser(@Path("username") String username);
}
可以看到我们定义了一个getUser方法,方法接收一个username参数,并且我们的@GET注解中使用{username}声明了访问路径,这里你可以把{username}当做占位符,而实际运行中会通过@PATH(“username”)所标注的参数进行替换。
那么访问的代码很类似:
//省略了retrofit的构建代码
Call<User> call = userBiz.getUser("zhy");
//Call<User> call = userBiz.getUser("lmj");
call.enqueue(new Callback<User>()
{
@Override
public void onResponse(Call<User> call, Response<User> response)
{
Log.e(TAG, "getUsePath:" + response.body());
}
@Override
public void onFailure(Call<User> call, Throwable t)
{
}
});
看下面的url
http://baseurl/users?sortby=username
http://baseurl/users?sortby=id
即一般的传参,我们可以通过@Query注解方便的完成,我们再次在接口中添加一个方法:
public interface IUserBiz
{
@GET("users")
Call> getUsersBySort(@Query("sortby") String sort);
}
访问的代码,其实没什么写的:
//省略retrofit的构建代码
CallUser
>> call = userBiz.getUsersBySort("username");
//CallUser
>> call = userBiz.getUsersBySort("id");
//省略call执行相关代码
ok,这样我们就完成了参数的指定,当然相同的方式也适用于POST,只需要把注解修改为@POST即可。
对了,我能刚才学了@PATH,那么会不会有这样尝试的冲动,对于刚才的需求,我们这么写:
@GET("users?sortby={sortby}")
Call> getUsersBySort(@Path("sortby") String sort)
;
乍一看别说好像有点感觉,哈,实际上运行是不支持的~估计是@ Path的定位就是用于url的路径而不是参数,对于参数还是选择通过@Query来设置。
大家都清楚,我们app很多时候跟服务器通信,会选择直接使用POST方式将json字符串作为请求体发送到服务器,那么我们看看这个需求使用retrofit该如何实现。
再次添加一个方法:
public interface IUserBiz
{
@POST("add")
Call> addUser(@Body User user);
}
提交的代码其实基本都是一致的:
//省略retrofit的构建代码
CallUser
>> call = userBiz.addUser(new User(1001, "jj", "123,", "jj123", "[email protected]"));
//省略call执行相关代码
ok,可以看到其实就是使用@Body这个注解标识我们的参数对象即可,那么这里需要考虑一个问题,retrofit是如何将user对象转化为字符串呢?下文将详细解释~
下面对应okhttp,还有两种requestBody,一个是FormBody,一个是MultipartBody,前者以表单的方式传递简单的键值对,后者以POST表单的方式上传文件可以携带参数,retrofit也二者也有对应的注解,下面继续~
这里我们模拟一个登录的方法,添加一个方法:
public interface IUserBiz
{
@POST("login")
@FormUrlEncoded
Call login(@Field("username") String username, @Field("password") String password);
}
访问的代码:
//省略retrofit的构建代码
Call<User> call = userBiz.login("zhy", "123");
//省略call执行相关代码
ok,看起来也很简单,通过@POST指明url,添加FormUrlEncoded,然后通过@Field添加参数即可。
下面看一下单文件上传,依然是再次添加个方法:
public interface IUserBiz
{
@Multipart
@POST("register")
Call registerUser(@Part MultipartBody.Part photo, @Part("username") RequestBody username, @Part("password") RequestBody password);
}
这里@MultiPart的意思就是允许多个@Part了,我们这里使用了3个@Part,第一个我们准备上传个文件,使用了MultipartBody.Part类型,其余两个均为简单的键值对。
使用:
File file = new File(Environment.getExternalStorageDirectory(), "icon.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
MultipartBody.Part photo = MultipartBody.Part.createFormData("photos", "icon.png", photoRequestBody);
Call call = userBiz.registerUser(photo, RequestBody.create(null, "abc"), RequestBody.create(null, "123"));
ok,这里感觉略为麻烦。不过还是蛮好理解~~多个@Part,每个Part对应一个RequestBody。
这里插个实验过程,其实我最初对于文件,也是尝试的@Part RequestBody,因为@Part(“key”),然后传入一个代表文件的RequestBody,我觉得更加容易理解,后来发现试验无法成功,而且查了下issue,给出了一个很奇怪的解决方案,这里可以参考:retrofit#1063。
给出了一个类似如下的方案:
public interface ApiInterface {
@Multipart
@POST ("/api/Accounts/editaccount")
Call editUser (@Header("Authorization") String authorization, @Part("file\"; filename=\"pp.png") RequestBody file , @Part("FirstName") RequestBody fname, @Part("Id") RequestBody id);
}
可以看到对于文件的那个@Partvalue竟然写了这么多奇怪的东西,而且filename竟然硬编码了~~这个不好吧,我上传的文件名竟然不能动态指定。
为了文件名不会被写死,所以给出了最上面的上传单文件的方法,ps:上面这个方案经测试也是可以上传成功的。
恩,这个奇怪方案,为什么这么做可行,下文会给出非常详细的解释。
最后看下多文件上传~
再添加一个方法~~~
public interface IUserBiz
{
@Multipart
@POST("register")
Call registerUser(@PartMap Map params, @Part("password") RequestBody password);
}
这里使用了一个新的注解@PartMap,这个注解用于标识一个Map,Map的key为String类型,代表上传的键值对的key(与服务器接受的key对应),value即为RequestBody,有点类似@Part的封装版本。
执行的代码:
File file = new File(Environment.getExternalStorageDirectory(), "messenger_01.png");
RequestBody photo = RequestBody.create(MediaType.parse("image/png", file);
Map photos = new HashMap<>();
photos.put("photos\"; filename=\"icon.png", photo);
photos.put("username", RequestBody.create(null, "abc"));
Call call = userBiz.registerUser(photos, RequestBody.create(null, "123"));
可以看到,可以在Map中put进一个或多个文件,键值对等,当然你也可以分开,单独的键值对也可以使用@Part,这里又看到设置文件的时候,相对应的key很奇怪,例如上例”photos\”; filename=\”icon.png”,前面的photos就是与服务器对应的key,后面filename是服务器得到的文件名,ok,参数虽然奇怪,但是也可以动态的设置文件名,不太影响使用~~
这个其实我觉得直接使用okhttp就好了,使用retrofit去做这个事情真的有点瞎用的感觉~~
增加一个方法:
@GET("download")
Call downloadTest() ;
调用:
Call call = userBiz.downloadTest();
call.enqueue(new Callback()
{
@Override
public void onResponse(Call call, Response response)
{
InputStream is = response.body().byteStream();
//save file
}
@Override
public void onFailure(Call call, Throwable t)
{
}
});
可以看到可以返回ResponseBody,那么很多事都能干了~~
but,也看出这种方式下载感觉非常鸡肋,并且onReponse回调虽然在UI线程,但是你还是要处理io操作,也就是说你在这里还要另外开线程操作,或者你可以考虑同步的方式下载。
最后还是建议使用okhttp去下载,例如使用okhttputils.
有人可能会问,使用okhttp,和使用retrofit会不会造成新建多个OkHttpClient对象呢,其实是可设置的,参考下文。
ok,上面就是一些常用的方法,当然还涉及到一些没有介绍的注解,但是通过上面这么多方法的介绍,再多一二个注解的使用方式,相信大家能够解决。
首先构造retrofit,几个核心的参数呢,主要就是baseurl,callFactory(默认okhttpclient),converterFactories,adapterFactories,excallbackExecutor。
然后通过create方法拿到接口的实现类,这里利用Java的Proxy类完成动态代理的相关代理
在invoke方法内部,拿到我们所声明的注解以及实参等,构造ServiceMethod,ServiceMethod中解析了大量的信息,最痛可以通过toRequest构造出okhttp3.Request对象。有了okhttp3.Request对象就可以很自然的构建出okhttp3.call,最后calladapter对Call进行装饰返回。
拿到Call就可以执行enqueue或者execute方法了