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"}}
IP
类
public class IP {
private int code;
private Data data;
}
这个类中的Date类是十分麻烦的,所以我们考虑用工具直接生成
- 进入JSON数据在线转换
- 粘贴JSON代码进去,在右边的
Source Type
- 选择JSON,
Anotation Style
- 选择
Gson
- ,其他的选择自行摸索,我的配置如图
Retrofit
区分大小写 与 带下划线的数据可能无法识别,导致返回为NULL,所以一定要勾选上Gson这个Anotation,这个勾选后,你的POJO数据,以及Gson的jar包都可以完全混淆,是一种超级偷懒的写法。
- 点击下方的
Jar
- ,就会生成源码包,你可以直接扔到工程中或者改名为zip手动折腾,注意代码的有些
注解(Annotation
- )可能在AS中无法通过编译,删除即可
HTTP GET简介
请求指定的页面信息,并返回实体主体,我们可以在http链接中加入path,key-value等参数,从而得到具体的对象。
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数据,这步略
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);
}
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
方法了,至于为什么我要写这个接口?官网上有详细的讲解getIp
- 即可
IP ip = IPUtils.taobaoIPService.getIp("202.202.33.33");
getIp
在非UI线程中使用(比如最简单的AsyncTask
),之后如何使用IP数据就简单了,操作第二步的UI组件即可
Retrofit异步回调方法
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);
}
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());
}
});
}
}
});
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,或者在提交数据前检查网络连接
1234
- ,导致服务器返回错误的数据,使客户度无法解析(
CONVERSION)
{"code":1,"data":"invaild ip."}
CONVERSION
),我们可以在本地用正则表达式在提交数据前对数据进行简单的验证,当然如果服务器真的传来了,也没什么,你同样只用发一个Toast,提示“重新填写请求”即可
- HTTP:处理这个错误,需要服务端与客户端写好技术文档,或者使用Mock模拟所有的错误,一般一个好的服务器是不会出现这个错误的,真的出现了话,特例处理,比如常见的500,404错误
- UNEXPECTED:暂时没见过
failure
- 应该是这样的,健壮高效
@Override
public 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());
}
@Override
public 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/
后记
本文全完,谢谢观看!本博客持续更新与搬运国外大神的文章,有兴趣的话不妨点一个收藏,另外我还维护着一个材料设计的专题,欢迎收藏。