技术标签: 职场 Retrofit2 界面 okhttp 移动开发 源码
之前写了个okhttputils的工具类,然后有很多同学询问这个工具类和retrofit
什么区别,于是上了下官网,发现其底层对网络的访问默认也是基于okhttp
,不过retrofit
非常适合于restful url
格式的请求,更多使用注解的方式提供功能。
既然这样,我们本篇博文首先研究其所提供的常用的用法:
一般的get请求(如何通过注解携带参数,拼接url)
一般的post请求(包含各种注解的使用)
上传文件
下载文件等
此外,由于其内部提供了ConverterFactory
用于对返回的requestBody进行转化,所以本文也包含:
如何自定义Converter.Factory
最后呢,因为其源码并不复杂,本文将对源码进行整体的介绍,即
retrofit 源码分析
ok,说这么多,既然需要restful url
,我只能捡起我那个半桶水的spring mvc 搭建一个服务端的小例子~~
最后本文使用版本:
compile 'com.squareup.retrofit2:retrofit:2.0.2'
retrofit
在使用的过程中,需要定义一个接口对象,我们首先演示一个最简单的get请求,接口如下所示:
public interface IUserBiz
{
@GET("users")
Call<List<User>> 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);
Call<List<User>> call = userBiz.getUsers();
call.enqueue(new Callback<List<User>>()
{
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response)
{
Log.e(TAG, "normalGet:" + response.body() + "");
}
@Override
public void onFailure(Call<List<User>> call, Throwable t)
{
}
});
依然是构造者模式,指定了baseUrl
和Converter.Factory
,该对象通过名称可以看出是用于对象转化的,本例因为服务器返回的是json格式的数组,所以这里设置了GsonConverterFactory
完成对象的转化。
ok,这里可以看到很神奇,我们通过Retrofit.create
就可以拿到我们定义的IUserBiz
的实例,调用其方法即可拿到一个Call
对象,通过call.enqueue
即可完成异步的请求。
具体retrofit怎么得到我们接口的实例的,以及对象的返回结果是如何转化的,我们后面具体分析。
这里需要指出的是:
接口中的方法必须有返回值,且比如是Call<T>
类型
.addConverterFactory(GsonConverterFactory.create())
这里如果使用gson,需要额外导入:
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
当然除了gson以外,还提供了以下的选择:
Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
当然也支持自定义,你可以选择自己写转化器完成数据的转化,这个后面将具体介绍。
既然call.enqueue
是异步的访问数据,那么同步的访问方式为call.execute
,这一点非常类似okhttp的API,实际上默认情况下内部也是通过okhttp3.Call
实现。
那么,通过这么一个简单的例子,应该对retrofit
已经有了一个直观的认识,下面看更多其支持的特性。
@PATH
文章开头提过,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<User> 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)
{
}
});
@Query
看下面的url
http://baseurl/users?sortby=username
http://baseurl/users?sortby=id
即一般的传参,我们可以通过@Query
注解方便的完成,我们再次在接口中添加一个方法:
public interface IUserBiz
{
@GET("users")
Call<List<User>> getUsersBySort(@Query("sortby") String sort);
}
访问的代码,其实没什么写的:
//省略retrofit的构建代码
Call<List<User>> call = userBiz.getUsersBySort("username");
//Call<List<User>> call = userBiz.getUsersBySort("id");
//省略call执行相关代码
ok,这样我们就完成了参数的指定,当然相同的方式也适用于POST,只需要把注解修改为@POST
即可。
对了,我能刚才学了@PATH
,那么会不会有这样尝试的冲动,对于刚才的需求,我们这么写:
@GET("users?sortby={sortby}")
Call<List<User>> getUsersBySort(@Path("sortby") String sort);
乍一看别说好像有点感觉,哈,实际上运行是不支持的~估计是@ Path
的定位就是用于url的路径而不是参数,对于参数还是选择通过@Query
来设置。
@Body
大家都清楚,我们app很多时候跟服务器通信,会选择直接使用POST方式将json字符串作为请求体发送到服务器,那么我们看看这个需求使用retrofit
该如何实现。
再次添加一个方法:
public interface IUserBiz
{
@POST("add")
Call<List<User>> addUser(@Body User user);
}
提交的代码其实基本都是一致的:
//省略retrofit的构建代码
Call<List<User>> call = userBiz.addUser(new User(1001, "jj", "123,", "jj123", "[email protected]"));
//省略call执行相关代码
ok,可以看到其实就是使用@Body
这个注解标识我们的参数对象即可,那么这里需要考虑一个问题,retrofit是如何将user对象转化为字符串呢?下文将详细解释~
下面对应okhttp,还有两种requestBody,一个是FormBody
,一个是MultipartBody
,前者以表单的方式传递简单的键值对,后者以POST表单的方式上传文件可以携带参数,retrofit
也二者也有对应的注解,下面继续~
@FormUrlEncoded
这里我们模拟一个登录的方法,添加一个方法:
public interface IUserBiz
{
@POST("login")
@FormUrlEncoded
Call<User> login(@Field("username") String username, @Field("password") String password);
}
访问的代码:
//省略retrofit的构建代码
Call<User> call = userBiz.login("zhy", "123");
//省略call执行相关代码
ok,看起来也很简单,通过@POST
指明url,添加FormUrlEncoded
,然后通过@Field
添加参数即可。
@Multipart
下面看一下单文件上传,依然是再次添加个方法:
public interface IUserBiz
{
@Multipart
@POST("register")
Call<User> 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<User> call = userBiz.registerUser(photo, RequestBody.create(null, "abc"), RequestBody.create(null, "123"));
ok,这里感觉略为麻烦~~不过还是蛮好理解~~多个@Part
,每个Part指定一个RequestBody。
这里插个实验过程,其实我最初对于文件,也是尝试的@Part RequestBody
,因为@Part("key")
,然后传入一个代表文件的RequestBody
,我觉得更加容易理解,后来发现试验无法成功,而且查了下issue,给出了一个很奇怪的解决方案,这里可以参考:https://github.com/square/retrofit/issues/1063。
给出了一个类似如下的方案:
public interface ApiInterface {
@Multipart
@POST ("/api/Accounts/editaccount")
Call<User> editUser (@Header("Authorization") String authorization, @Part("file\"; filename=\"pp.png") RequestBody file , @Part("FirstName") RequestBody fname, @Part("Id") RequestBody id);
}
可以看到对于文件的那个@Part
value竟然写了这么多奇怪的东西,而且filename竟然硬编码了~~这个不好吧,我上传的文件名竟然不能动态指定。
为了文件名不会被写死,所以给出了最上面的上传单文件的方法,ps:上面这个方案经测试也是可以上传成功的。
最后看下多文件上传~
@PartMap
再添加一个方法~~~
public interface IUserBiz
{
@Multipart
@POST("register")
Call<User> registerUser(@PartMap Map<String, RequestBody> 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<String,RequestBody> photos = new HashMap<>();
photos.put("photos\"; filename=\"icon.png", photo);
photos.put("username", RequestBody.create(null, "abc"));
Call<User> 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<ResponseBody> downloadTest();
调用:
Call<ResponseBody> call = userBiz.downloadTest();
call.enqueue(new Callback<ResponseBody>()
{
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response)
{
InputStream is = response.body().byteStream();
//save file
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t)
{
}
});
可以看到可以返回ResponseBody
,那么很多事都能干了~~
but,也看出这种方式下载感觉非常鸡肋,并且onReponse回调虽然在UI线程,但是你还是要处理io操作,也就是说你在这里还要另外开线程操作,或者你可以考虑同步的方式下载。
最后还是建议使用okhttp去下载,例如使用okhttputils.
有人可能会问,使用okhttp,和使用retrofit会不会造成新建多个OkHttpClient
对象呢,其实是可设置的,参考下文。
ok,上面就是一些常用的方法,当然还涉及到一些没有介绍的注解,但是通过上面这么多方法的介绍,再多一二个注解的使用方式,相信大家能够解决。
这个需要简单提一下,很多时候,比如你使用retrofit需要统一的log管理,给每个请求添加统一的header等,这些都应该通过okhttpclient去操作,比如addInterceptor
例如:
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor()//log,统一的header等
{
@Override
public okhttp3.Response intercept(Chain chain) throws IOException
{
return null;
}
}).build();
或许你需要更多的配置,你可以单独写一个OkhttpClient的单例生成类,在这个里面完成你所需的所有的配置,然后将OkhttpClient
实例通过方法公布出来,设置给retrofit。
设置方式:
Retrofit retrofit = new Retrofit.Builder()
.callFactory(OkHttpUtils.getClient())
.build();
callFactory
方法接受一个okhttp3.Call.Factory
对象,OkHttpClient
即为一个实现类。
明天继续关注:Retrofit2 完全解析 探索与okhttp之间的关系(二)
文章浏览阅读1.5k次。1,MyUtuils.kt将被调用的文件class MyUtils { fun show(info:String){ println(info) }}fun show(info:String){ println(info)}2,Java文件调用该类,ClientJava.javapublic class ClientJava { public static void main(String[] args) { /** _java 调用kt 对象
文章浏览阅读6.6k次,点赞4次,收藏4次。在进行UDP编程的时候,我们最容易想到的问题就是,一次发送多少bytes好? 当然,这个没有唯一答案,相对于不同的系统,不同的要求,其得到的答案是不一样的,我这里仅对 像ICQ一类的发送聊天消息的情况作分析,对于其他情况,你或许也能得到一点帮助: 首先,我们知道,TCP/IP通常被认为是一个四层协议系统,包括链路层,网络层,运输层,应用层. UDP属于运输层_最大请求报文大小
文章浏览阅读10w+次,点赞14次,收藏18次。代码如下:for /l %a in (0,0,1) do echo hello,world粘贴在cmd命令行窗口中,回车即可无限死循环输出hello,world。如果需要停止,可以按ctrl+c中断。解析通用形式:for /l %variable IN (start,step,end) DO command [command-parameters] 该集表示以增量形式从start到end的一个数字序列。具体到第一段代码,如果是 (0,0,1) 就是从0开始,每次增_cmd装比代码无限循环
文章浏览阅读917次,点赞18次,收藏11次。为了方便有学习需要的朋友,我把资料都整理成了视频教程(实际上比预期多花了不少精力)当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
文章浏览阅读2k次,点赞5次,收藏14次。1、写出微分方程函数2、求解function dy=rigid(t,y)dy=zeros(3,1);dy(1)=y(2)*y(3);dy(2)=-y(1)*y(3);dy(3)=-0.51*y(1)*y(2);end%将微分方程写成函数形式,待调用options=odeset('RelTol',1e-4,'AbsTol',[1e-4 1e-4 1e-5]);[T Y]=ode45(@rigid,[0 12],[0 1 1],options);plot(T,Y(:,1),'-',T,Y_ode函数离散
文章浏览阅读3.8w次,点赞41次,收藏180次。==操作符与equals方法的区别_java中==和equals的区别
文章浏览阅读218次。1.官方的建议1.1 电池续航时间优化(Optimizing Battery Life)参考文章:优化电池使用时间已有中文的详细说明,此处做简要说明:(1)监控电池电量和充电状态(Monitoring the Battery Level and Charging State)通过系统广播,获取充电状态和电池电量的变化来调整数据更新等操作;如在充电时,更新数据及应用,在低电量时,减少更新频..._com.tencent.mm:exdevice
文章浏览阅读818次,点赞14次,收藏9次。计算e1=2.718,e5=148.413,e3=20.086,e1+e5+e3=171.217。“人/B 们/E 常/S 说/S 生/B 活/E 是/S 一/S 部/S 教/B 科/M 书/E ”给一段文字做分词标注,标注每个字对应的标号。图中是双向的三层 RNNs,堆叠多层的RNN网络,可以增加模型的参数,提高模型的拟合。双向的 RNN 是同时考虑“过去”和“未来”的信息,输入(黑色点)沿着黑色的实线箭。比如标签0将表示为([1,0,0,0,0,0,0,0,0,0]),标签3将表示为。
文章浏览阅读513次,点赞9次,收藏10次。无法加载DLL"halconxl": 找不到指定的模块。(异常来自HRESULT:0X8007007E)。在exe安装目录中中添加halconxl.dll文件继续运行就了。_c#如何免安装halcon12
文章浏览阅读1k次,点赞11次,收藏9次。你的键盘上应该有两个Ctrl键,按右边的Ctrl解决了。_键盘一直自动按ctrl
文章浏览阅读141次。Linux 命令【6】:cut文章目录一、简介二、命令详解三、实例演示一、简介cut 命令是一个将文本按列进行切分的小工具,它可以指定分隔每列的定界符。二、命令详解命令格式:cut {选项} {文件名}选项:-b :以字节为单位进行分割。这些字节位置将忽略多字节字符边界,除非也指定了 -n 标志。-c :以字符为单位进行分割。-d :自定义分隔符,默认为制表符。-f :与-d一起使用,指定显示哪个区域。-n :取消分割多字节字符。仅和 -b 标志一起使用。如果字符的最后一._cut使用特殊字符为分隔符
文章浏览阅读2.4k次。/** * 播放audio标签视频控制 * */ //等待音频加载完毕 点击每一段录音进行播放 $('.lis').click(function(){ $('.j_voiceCont').show(); var src = $(this).attr("src"); $(this).addClass('c_audiotrack可以设置进度吗