Retrofit 解析 JSON 数据
来源:互联网 发布:centos官网 编辑:程序博客网 时间:2024/05/16 05:54
转载自:点击打开链接
Retro是一个类型安全的REST客户端,它可以直接解析JSON数据变成JAVA对象,甚至支持回调操作,处理不同的结果,本文将以IP地址API数据解析为例,讲解如何使用Retrofit
本文适用于2.0以下的版本,目前1.9还是主流,此文章将渐渐成为历史
将要使用的网站
- Retrofit
- IP地址查询站
- JSON数据在线转换
文章目录
- JSON数据如何转成JAVA
- Retrofit同步获取方法
- Retrofit异步回调方法
JSON数据如何转成JAVA
打开了刚刚引用的API查询网页,那个网页给了一串JSON数据的示例
{"code":0,"data":{"ip":"210.75.225.254","country":"\u4e2d\u56fd","area":"\u534e\u5317",
"region":"\u5317\u4eac\u5e02","city":"\u5317\u4eac\u5e02","county":"","isp":"\u7535\u4fe1",
"country_id":"86","area_id":"100000","region_id":"110000","city_id":"110000",
"county_id":"-1","isp_id":"100017"}}
根据数据的大意,我们可以考虑构建一个JavaIP
类
public class IP { private int code; private Data data;}
这个类中的Date类是十分麻烦的,所以我们考虑用工具直接生成
- 进入JSON数据在线转换
- 粘贴JSON代码进去,在右边的
Source Type
选择JSON,Anotation Style
选择Gson
,其他的选择自行摸索,我的配置如图
注意服务器发送的
区分大小写
与带下划线
的数据可能无法识别,导致返回为NULL,所以一定要勾选上Gson
这个Anotation,这个勾选后,你的POJO数据,以及Gson的jar包都可以完全混淆,是一种超级偷懒的写法。
- 点击下方的
Jar
,就会生成源码包,你可以直接扔到工程中或者改名为zip手动折腾,注意代码的有些注解(Annotation
)可能在AS中无法通过编译,删除即可
HTTP GET简介
请求指定的页面信息,并返回实体主体,我们可以在http链接中加入path,key-value等参数,从而得到具体的对象。
我们回到API网站,它告诉了我们它的API是通过HTTP GET方法获取的,何为GET?简单的说,就是在请求的url中加入key-value参数,发送给服务器,这里的key是ip
,value是你要查询的地址,比如202.202.33.33
http://ip.taobao.com/service/getIpInfo.php?ip=202.202.33.33
服务器根据你的GET请求,返回如下的JSON数据
{"code": 0, "data": { "country": "中国", "country_id": "CN", "area": "西南", "area_id": "500000", "region": "重庆市", "region_id": "500000", "city": "重庆市", "city_id": "500000", "county": "", "county_id": "-1", "isp": "教育网", "isp_id": "100027", "ip": "202.202.33.33" }}
接下来我们如何使用Retrofit获取并解析数据呢,现在开始正式的使用
Retrofit同步获取方法
这里的同步
获取方法是指以只获得JAVA对象为目标,而不更新UI线程中的数据,异步
是指获取到数据后立刻回调,更新UI线程中的界面,我们先讲简单的同步
,建议跟着官方Wiki一起看
打开Android Studio,新建一个工程,添加网络权限,Bulid.gradle添加如下依赖
//自行更新后面的版本号 compile 'com.squareup.retrofit:retrofit:1.7.1' compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.0' compile 'com.squareup.okhttp:okhttp:2.0.0'
修改UI界面,里面放上一个EditText,一个Button,一些Textview,用于输入和显示UI数据,这步略
写IP工具类,用于封装获取获取IP,这里的命名
IPUtils
比较吐槽,各位先忽视public class IPUtils { //eg : http://ip.taobao.com/service/getIpInfo.php?ip=202.202.32.202 static final String ENDPOINT = "http://ip.taobao.com/service"; public interface TaobaoIPService { @GET("/getIpInfo.php") IP getIp(@Query("ip")String ip); } static RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(ENDPOINT) //是否Debug .setLogLevel(RestAdapter.LogLevel.FULL) .build(); static public TaobaoIPService taobaoIPService = restAdapter.create(TaobaoIPService.class); }
这个
IPUtils
首先建立了一个接口TaobaoIPService
,接着又创建了一个restAdapter
,最后用restAdapter
实例化接口,我们要获取IP的时候,直接调用taobaoIPService
中的getIp
方法了,至于为什么我要写这个接口?官网上有详细的讲解现在只需要在Activity中调用
getIp
即可IP ip = IPUtils.taobaoIPService.getIp("202.202.33.33");
就可以获得所有的IP数据了,注意这个任务是网络任务,所以不要忘记给程序加入网络权限,并且让这个
getIp
在非UI线程中使用(比如最简单的AsyncTask
),之后如何使用IP数据就简单了,操作第二步的UI组件即可
Retrofit异步回调方法
Retrofit的异步回调是指在获取到数据后,立刻进行UI的更新,你不需要自己另外写AsyncTask,代码看上去简洁,如果配合Dagger(一个Android注解框架,本文不讨论)
的使用就更加美了
建立一个工具类
public class IPUtils { //eg : http://ip.taobao.com/service/getIpInfo.php?ip=202.202.32.202 static final String ENDPOINT = "http://ip.taobao.com/service"; public interface TaobaoIPService { @GET("/getIpInfo.php") void getIp(@Query("ip")String ip, Callback<IP> callback); } static RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(ENDPOINT) .setLogLevel(RestAdapter.LogLevel.FULL) .build(); public static TaobaoIPService taobaoIPService = restAdapter.create(TaobaoIPService.class); }
从代码中可以看出
TaobaoIPService
中的方法getIp
没有返回值了,反而多了一个Callback,官方 Wiki是这么说的On Android, callbacks will be executed on the main thread.
在我们结束了数据获取后,无论是否成功,都将启动回调,回调将在主线程(UI线程)执行。
在Activity的使用
submitButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String query = editText.getText().toString(); if (!query.isEmpty()) { //该组件能够自动启动Http线程,然后在回调中用main线程修改UI //详情可以看“SYNCHRONOUS VS. ASYNCHRONOUS VS. OBSERVABLE” IPUtils.taobaoIPService.getIp(query, new Callback<IP>() { @Override public void success(IP ip, Response response) { textView_code.setText(String.valueOf(ip.getCode())); textView_ip.setText(ip.getData().getIp()); textView_country.setText(ip.getData().getCountry()); textView_area.setText(ip.getData().getArea()); textView_region.setText(ip.getData().getRegion()); textView_city.setText(ip.getData().getCity()); textView_isp.setText(ip.getData().getIsp()); } @Override public void failure(RetrofitError error) { showToast("failure:" + error.getKind()); } }); } } });
我们可以看出
Callback
重写了2个方法,一个是成功,一个是失败。在成功(success)
中,我们利用回调的数据,直接进行UI的更新这里注意可能出现的内存泄露,如果你是执行耗时任务,当你退出activity后,回调后可能会出现空指针异常。
对错误以及异常的处理
可以看到,在刚刚的代码中,我们仅仅输出了错误的种类,没有个性化的输出,作为客户端我们应该如何处理不同的错误异常呢?我们先列举用户出错的情况,常见的错误种类如下
public enum Kind { /** An {@link IOException} occurred while communicating to the server. */ NETWORK, /** An exception was thrown while (de)serializing a body. */ CONVERSION, /** A non-200 HTTP status code was received from the server. */ HTTP, /** * An internal error occurred while attempting to execute a request. It is best practice to * re-throw this exception so your application crashes. */ UNEXPECTED }
NETWORK:用户没有联网,这个简单,你可以发一个Toast,或者在提交数据前检查网络连接
CONVERSION:用户输入错误的数据,比如
1234
,导致服务器返回错误的数据,使客户度无法解析(CONVERSION)
{"code":1,"data":"invaild ip."}
对于这个错误(
CONVERSION
),我们可以在本地用正则表达式在提交数据前对数据进行简单的验证,当然如果服务器真的传来了,也没什么,你同样只用发一个Toast,提示“重新填写请求”即可HTTP:处理这个错误,需要服务端与客户端写好技术文档,或者使用Mock模拟所有的错误,一般一个好的服务器是不会出现这个错误的,真的出现了话,特例处理,比如常见的500,404错误
UNEXPECTED:暂时没见过
最后,我们写出的
failure
应该是这样的,健壮高效@Overridepublic void failure(RetrofitError error) {switch (error.getKind()) { case NETWORK: showToast("网络错误"); break; case CONVERSION: showToast("重新输入"); break; case HTTP: //这里可以用Mockito模拟 showToast("错误代码:" + String.valueOf(error.getResponse().getStatus()) + "错误原因:" + error.getResponse().getReason()); break; case UNEXPECTED: showToast("未知错误"); //TODO:写入日志 break; } showToast("failure:" + error.getKind());}
在用界面看来,用户得到了有效的错误消息,可以与开发作者沟通反馈。
PS1:POST操作
我目前有个开源的项目,图片上传用的就是Retrofit2.0的POST上传,有兴趣去看看吧。
Fork me on gitHub
PS2: Retrofit2.0
- 我在stackoverflow回答的关于Retrofit2.0的相关问题
- 使用RxJava与Retrofit2.0使用的实例:Retrofit 2.0 RxJava Sample
- JW大神的文章
- http://wuxiaolong.me/2016/01/15/retrofit/
后记
本文全完,谢谢观看!本博客持续更新与搬运国外大神的文章,有兴趣的话不妨点一个收藏,另外我还维护着一个材料设计的专题,欢迎收藏。
- Retrofit 解析 JSON 数据
- Retrofit解析网页Json数据简单实例
- Retrofit解析网页Json数据简单实例
- retrofit Json解析
- Gson+retrofit解析同一位置不同类型的json数据
- 在retrofit访问网络返回json数据添加解析器
- Retrofit如何提交json数据
- 使用Retrofit获取JSON数据
- retrofit gson 解析json数据失败问题“com.google.gson.stream.MalformedJsonException:”
- Retrofit 使用心得-json解析
- 使用Retrofit获取json并解析
- 【Android】Retrofit的使用(2)-使用Retrofit提交JSON数据
- 如何通过Retrofit提交Json格式数据
- 使用Retrofit通过post提交Json数据
- 如何通过Retrofit提交Json格式数据
- Android Retrofit框架请求复杂json数据
- Retrofit不进行Json解析,直接返回Json String
- Retrofit初体验,复杂数据gson解析
- HDU1358-Period(KMP)
- STK资源
- C++函数中那些不可以被声明为虚函数的函数
- 关于 collection 头视图 尾视图 设置
- 1031. 查验身份证(15)
- Retrofit 解析 JSON 数据
- VS2008用于生成命令和属性的宏
- String的内存分配机制
- ios 根据文字数量计算UILabel高度(已修改)
- xxx cannot be resolved or is not a field
- 宏碁4755G电脑升级
- JavaScript prototype 属性
- C语言 交换两个整形变量的值
- Java锁机制 自旋锁(spinlock)剖析
火枪辉耀了
很好,准备用OKHTTP和Retrofit作网络框架。同步和异步的区别是:同步需要自己写thread,用handle或 onPostExecute更新UI,异步直接在callback里面更新UI?
BlackSwift
@火枪辉耀了 对的,用CallBack的话,貌似更好,它有自己优化好的线程池,而不用自己控制
石木12: @BlackSwift 也在就纠结这个问题,很感谢你的回答
BlackSwift: @石木12 retrofit在构造时,如果没有传入executor,它将默认使用主线程的Handler作为回调的处理者,所以放心地在回调中调用ui事件
火枪辉耀了
@miao1007 好的,明白了,谢谢。关于离线支持有什么好的建议么,除了数据库,还有其他简便的方案么?http cache好像可以做,保存json也是一条选择,但Retrofit不能获取json,没找到好的资料。
89145aa6a54e: @火枪辉耀了 retrofit 里面是依赖okhttp的 可以设置他的缓存 你可以看下OkhttpClient
89145aa6a54e: @火枪辉耀了 我也是最近在学习这个 缓存今天看的 你可以看下这篇文章 http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0115/3873.html
BlackSwift: @89145aa6a54e 缓存策略由服务器的header决定,不论是cache control还是etag,非常不建议用客户端强制改写。如果非要用缓存的话,还是建议用数据库或者文件缓存。
BlackSwift
@火枪辉耀了 小于50K的东西全部使用GreenDAO等orm框架,放到Sqlite里面,文本保存在50K以上才有速度优势。同时,开发Retrofit的大神表示以后将有缓存功能的,再等等就有了
89145aa6a54e: @BlackSwift 恩 GreenDao是目前速度最近的orm啦 我一般也是用它
火枪辉耀了
@miao1007 好吧,那就用green Dao吧,THX
火枪辉耀了
郁闷了,mutilpart有问题@Multipart
@POST(API)
void signup(@PartMap Map<String, String> params,
@Part(KEY_AVATAR) TypedFile file,
Callback<UserResponse> callback);//传TypeFile正常
@POST(API)
void signup(@PartMap Map<String, String> params,
@Part(KEY_AVATAR) TypedByteArray file.toByte(),
Callback<UserResponse> callback);//服务器端把part当成文本字段处理了
火枪辉耀了
@火枪辉耀了 只能用OKHttp去做了,mutiPart用Okhttp,其他用Retrofit
王立腾: @火枪辉耀了 Expected a string but was BEGIN_OBJECT at line 1 column 2 path 上传图片文件出现这样的错误,不知道怎么解决了。
BlackSwift
@火枪辉耀了 “服务器端把part当成文本字段处理了” ,这个是 http header的问题吧,明显minetype错误了,但是用okhttp也没什么,反正就是一个异步任务,自己多处理异常而已
火枪辉耀了
Content-Disposition: form-data; name="version"//version字段是text/plain,内容是1.0.1
Content-Type: text/plain; charset=UTF-8
Content-Length: 5
Content-Transfer-Encoding: binary
1.0.1
--58166202-0408-4b7c-9709-73d6b7d10746
Content-Disposition: form-data; name="avt"
Content-Type: application/octet-stream//avt是byte类型,指定为application/octet-
Content-Length: 43098
Content-Transfer-Encoding: binary
�PNG
������
服务断不能识别这个avt,不管是nodejs,还是PHP。但把TypedByteArray换成TypedFile就可以了