Retrofit知道这些就差不多了
引言
最近项目没什么进度,一直在学习IOS跟复习PHP。Android这边也没怎么搞了。不过这东西可不能拉下啊。二话不说赶紧上Github学习下新的框架。就看到了两个比较火的
RXJava
和Retrofit
。于是就开始啃一下Retrofit
文档。文档可谓是短小精悍。。。一开始硬是没看懂。下面就把自己看懂的部分说一下吧.
Retrofit到底是个啥
Retrofit是一套RESTful架构的Android(Java)客户端实现,基于注解,提供JSON to POJO(Plain Ordinary Java Object,简单Java对象),POJO to JSON,网络请求(POST,GET,PUT,DELETE等)封装。
从别人那里偷来的定义,看清来应该已经足够清晰了吧?至于什么是RESTful
?不要问我,因为我对这个概念也比较模糊。。。
没时间解释了,赶紧上车
我们这里跟官方文档不一样,我们先获取最简单的网络数据吧。就是获取
String
类型的数据。
然后我们再去结合Gson
去直接把返回的数据转成模型
。
环境
- Android Studio 2.1.1
- OSX 10.11.6
第一步:导入库
compile 'com.squareup.retrofit2:retrofit:2.1.0'
没啥好说的
第二部:编写服务接口
刚开始看这种做法有点奇葩,可能是本人的见识不足,勿喷。。。。不过后来发现这样写是真的屌。。自己慢慢去体会。
/**
* Created by August on 16/7/29.
* 获取Github首页的HTML源码
*/
public interface GithubService {
@GET("/")
Call<String> getGitHubIndexSource();
}
上面只是获取HTML源码,别想太多。。。。
-
@GET("/")
:使用GET
方法,到时候请求某网站的/
路径下的网页。获取啥就得到啥。一般根目录都是首页的。至于一般获取数据使用GET
,传输数据使用POST
这个就没什么异议了吧? -
Call<String>
:最终数据类型使用String
第三部:直接调用
Retrofit retrofit = new Retrofit
.Builder()
.baseUrl("https://github.com/")
.addConverterFactory(new Converter.Factory() {
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return new Converter<ResponseBody, String>() {
@Override
public String convert(ResponseBody value) throws IOException {
return value.string();
}
};
}
})
.build();
GithubService githubService = retrofit.create(GithubService.class);
Call<String> indexSource = githubService.getGitHubIndexSource();
indexSource.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
Log.i("TAG", response.body());
}
@Override
public void onFailure(Call<String> call, Throwable t) {
Log.i("TAG", t.getMessage());
}
});
我们成功就可以打印出结果了。看到这里你可能会想暴揍一顿笔者,然后头也不回地离开。。。我第一次也有这样的想法,不过你得耐心看下去,真的。。。因为此处我们使用了最基础的
Converter
所以代码写起来有点多。但是作为程序员要懂得封装
。待会我们就可以使用GsonConverter
编写出简介的代码。
-
baseUrl
:这里才真正给出了请求的地址,这里的地址会和GithubService
中的GET
中的参数/
进行拼接。 -
addConverterFactory
:添加转换器,这里我们使用了原生的Converter.Factory
抽象类结合Converter
。我们把返回的二进制流
(姑且认为你们都知道数据是以二进制数据流传输的。。。)转换成ResponseBody
然后转换成String
。其实我们一般只需要做ResponseBody
到String
或者其他模型
的转换就可以了。因为Android开发一般访问都是要的是数据,所以二进制流的就甭管了。。。 - 然后我们就通过
retrofit.create
去实现GithubService
接口并返回。内部具体怎样就不要问我,因为我也没(kan)去(bu)看(dong)它的源码。 -
githubService.getGitHubIndexSource
:构造出来了一个Call
对象咯,这跟很多网络库没啥区别。 -
indexSource.enqueue
:把Call
加入异步请求队列
。然后通过回调获取结果。
嗯?整个过程就大概像我上面那样说的。。。
除了Get还有什么呢?
我们都知道除了Get
的请求方法,常见的还有Post(携带字段数据)
和Multipart(携带文件数据与字段数据等)
Post
@POST(URLConstant.SEND_LOGIN_CODE)
@FormUrlEncoded
Call<BaseResponseEntity> sendCode(@Field("phone") String phone);
Post中我们使用Field来指定参数字段
Multipart
@Multipart
@POST(URLConstant.COMPLETE_DATA)
Call<BaseObjResponseEntity<UserEntity>> completeData(@Part("user_token") RequestBody userToken,
@Part MultipartBody.Part icon,
@Part("nick_name") RequestBody nickName);
这里的尽管userToken跟nickName都是字符串类型,但是在Multipart中需要包装一下.重要的方法就是RequestBody.create()
,其中包含多个重载的函数.大家可以使用下面的工具类来生成对应参数
/**
* Created by August on 2017/2/10.
* 文件上传帮助类
*/
public class MultiPartHelper {
/**
* 构造字段信息参数
*
* @param content
* @return
*/
public static RequestBody create(String content) {
return RequestBody.create(MediaType.parse("multipart/form-data"), content);
}
/**
* 构造单个文件上传
*
* @param key
* @param file
* @return
*/
public static MultipartBody.Part create(String key, File file) {
return MultipartBody.Part.createFormData(key, file.getName(), create(file));
}
public static RequestBody create(File file) {
return RequestBody.create(MediaType.parse("multipart/form-data"), file);
}
public static MultipartBody.Part create(String key, String filePath) {
return create(key, new File(filePath));
}
/**
* 构造多个文件上传参数
*
* @param fileList
* @return
*/
public static List<MultipartBody.Part> create(List<String> fileList) {
List<MultipartBody.Part> partList = new ArrayList<>();
for (String filename : fileList) {
File file = new File(filename);
RequestBody imageBody = create(file);
partList.add(MultipartBody.Part.createFormData(file.getName(), file.getName(), imageBody));
}
return partList;
}
}
一起装逼一起飞
上面使用原生的
CallBack
我们已经受够了。。。一坨坨的代码如翔一样挥之不去。。。我们结合别人封装的GsonConverter
来写一个从请求
到模型的转换吧
。
我们使用一个天气的API来获取数据吧。自己注册开发者身份
第一步:还是导入库(这是新的库。。。)
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
因为我们前面选择的是
Retrofit2.1.0
的库。所以我们选择的converter-gson
的版本也要对应上,我在学习的过程中就遇到过版本不对上的话就出错了。可能版本之间差异还是比较大的。还有让你添加库,你丫的别把Retrofit2.1.0
给删掉。。。
第二步:编写模型
我们可以找到我们需要获取的数据的返回格式
获取城市列表返回数据
{
errNum: 0,
errMsg: "success",
retData: [
{
province_cn: "北京", //省
district_cn: "北京", //市
name_cn: "朝阳", //区、县
name_en: "chaoyang", //城市拼音
area_id: "101010300" //城市代码
},
{
province_cn: "辽宁",
district_cn: "朝阳",
name_cn: "朝阳",
name_en: "chaoyang",
area_id: "101071201"
}
]
}
获取指定天气信息返回数据
{
errNum: 0,
errMsg: "success",
retData: {
city: "北京", //城市
pinyin: "beijing", //城市拼音
citycode: "101010100", //城市编码
date: "15-02-11", //日期
time: "11:00", //发布时间
postCode: "100000", //邮编
longitude: 116.391, //经度
latitude: 39.904, //维度
altitude: "33", //海拔
weather: "晴", //天气情况
temp: "10", //气温
l_tmp: "-4", //最低气温
h_tmp: "10", //最高气温
WD: "无持续风向", //风向
WS: "微风(<10m/h)", //风力
sunrise: "07:12", //日出时间
sunset: "17:44" //日落时间
}
}
从上面我们可以看出,我们需要建立4个模型。
-
CityListResult
:包装获取城市列表返回数据
整个JSON -
CityInfo
:包装CityListResult
中的retData
中的每一个城市信息 -
WeatherResult
:包装获取指定天气信息返回数据
整个JSON -
Weather
:包装WeatherResult
中的每一个retData
所以我们的模型代码就像下面那样
CityInfo模型
public class CityInfo {
@SerializedName("province_cn")
private String provinceCN;
@SerializedName("district_cn")
private String districtCN;
@SerializedName("name_cn")
private String nameCN;
@SerializedName("name_en")
private String nameEN;
@SerializedName("area_id")
private String areaID;
public String getProvinceCN() {
return provinceCN;
}
public void setProvinceCN(String provinceCN) {
this.provinceCN = provinceCN;
}
public String getDistrictCN() {
return districtCN;
}
public void setDistrictCN(String districtCN) {
this.districtCN = districtCN;
}
public String getNameCN() {
return nameCN;
}
public void setNameCN(String nameCN) {
this.nameCN = nameCN;
}
public String getNameEN() {
return nameEN;
}
public void setNameEN(String nameEN) {
this.nameEN = nameEN;
}
public String getAreaID() {
return areaID;
}
public void setAreaID(String areaID) {
this.areaID = areaID;
}
}
CityListResult模型
/**
* Created by August on 16/7/29.
* 城市查询结果模型
*/
public class CityListResult {
@SerializedName("errNum")
private int errNum;
@SerializedName("errMsg")
private String errMsg;
@SerializedName("retData")
private List<CityInfo> retData;
public int getErrNum() {
return errNum;
}
public void setErrNum(int errNum) {
this.errNum = errNum;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
public List<CityInfo> getRetData() {
return retData;
}
public void setRetData(List<CityInfo> retData) {
this.retData = retData;
}
}
Weather模型
/**
* Created by August on 16/7/29.
* 指定天气结果模型
*/
package demo.august1996.top.retrofitnewssample.model;
import com.google.gson.annotations.SerializedName;
/**
* Created by August on 16/7/29.
* 指定天气结果模型
*/
public class Weather {
@SerializedName("city")
private String city;
@SerializedName("pinyin")
private String pinyin;
@SerializedName("citycode")
private long cityCode;
@SerializedName("date")
private String date;
@SerializedName("time")
private String time;
@SerializedName("postCode")
private long postCode;
@SerializedName("longitude")
private double longitude;
@SerializedName("latitude")
private double latitude;
@SerializedName("altitude")
private double altitude;
@SerializedName("weather")
private String weather;
@SerializedName("temp")
private int temp;
@SerializedName("l_tmp")
private int l_tmp;
@SerializedName("h_tmp")
private int h_tmp;
@SerializedName("WD")
private String WD;
@SerializedName("WS")
private String WS;
@SerializedName("sunrise")
private String sunrise;
@SerializedName("sunset")
private String sunset;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getPinyin() {
return pinyin;
}
public void setPinyin(String pinyin) {
this.pinyin = pinyin;
}
public long getCityCode() {
return cityCode;
}
public void setCityCode(long cityCode) {
this.cityCode = cityCode;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public long getPostCode() {
return postCode;
}
public void setPostCode(long postCode) {
this.postCode = postCode;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public double getAltitude() {
return altitude;
}
public void setAltitude(double altitude) {
this.altitude = altitude;
}
public String getWeather() {
return weather;
}
public void setWeather(String weather) {
this.weather = weather;
}
public int getTemp() {
return temp;
}
public void setTemp(int temp) {
this.temp = temp;
}
public int getL_tmp() {
return l_tmp;
}
public void setL_tmp(int l_tmp) {
this.l_tmp = l_tmp;
}
public int getH_tmp() {
return h_tmp;
}
public void setH_tmp(int h_tmp) {
this.h_tmp = h_tmp;
}
public String getWD() {
return WD;
}
public void setWD(String WD) {
this.WD = WD;
}
public String getWS() {
return WS;
}
public void setWS(String WS) {
this.WS = WS;
}
public String getSunrise() {
return sunrise;
}
public void setSunrise(String sunrise) {
this.sunrise = sunrise;
}
public String getSunset() {
return sunset;
}
public void setSunset(String sunset) {
this.sunset = sunset;
}
}
WeatherResult模型
/**
* Created by August on 16/7/29.
* 获取特定城市返回结果模型
*/
public class WeatherResult {
@SerializedName("errNum")
private int errNum;
@SerializedName("errMsg")
private String errMsg;
@SerializedName("retData")
private Weather retData;
public int getErrNum() {
return errNum;
}
public void setErrNum(int errNum) {
this.errNum = errNum;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
public Weather getRetData() {
return retData;
}
public void setRetData(Weather retData) {
this.retData = retData;
}
}
前面的例子中使用的是
String
类型的结果,这次使用的是特定模型的结果
。
-
@SerializedName
注解:里面的值就是对应字段
的JSON字段
编写服务接口
/**
* Created by August on 16/7/28.
* 获取天气相关的服务
*/
public interface WeatherService {
@Headers("apikey:ffe27ca85324881f8414e8c1ab28xxxx")
@GET("/apistore/weatherservice/citylist")
Call<CityListResult> getCityList(@Query("cityname") String cityName);
@Headers("apikey:ffe27ca85324881f8414e8c1ab28xxxx")
@GET("/apistore/weatherservice/cityid")
Call<WeatherResult> getWeather(@Query("cityid") String cityID);
}
上面第一个就是获取城市列表与指定城市天气结果的定义
-
@Headers
:根据API接口要求,在header中添加apikey
。大家务必换成自己的apikey
-
@Query("cityname") String cityName
:到时候调用时传入一个参数cityName
,Retrofit
会把cityName
自动映射到GET请求的cityname
字段。 - 补充:
- 如果请求是
POST
请求就把上面的@GET
换成@POST
,同时把@Query
换成@Field
即可。 - 因为有些请求参数不是写在请求参数里面的,而是修改链接的。例如Github的
https://api.github.com/users/august1996/repos
就是通过请求路径的这时候我们可以写成:
- 如果请求是
@GET("https://api.github.com/users/{user}/repos")
Call<String> getGitHub(@Path("user") String user);
调用走起!
我们通过一个简单的ListView去展示城市列表,然后点击城市显示相应的天气信息。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private ListView mListView;
private CityListResult mResult;
private ArrayList<String> mDatas = new ArrayList<>();
private ArrayAdapter<String> mAdapter;
private WeatherService mWeatherService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.getBtn).setOnClickListener(this);
mWeatherService = new Retrofit.Builder()
.baseUrl("http://apis.baidu.com")
.addConverterFactory(GsonConverterFactory.create())
.build().create(WeatherService.class);
mListView = (ListView) findViewById(R.id.mListView);
mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mDatas);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, final int i, long l) {
mWeatherService.getWeather(String.valueOf(mResult.getRetData().get(i).getAreaID()))
.enqueue(new Callback<WeatherResult>() {
@Override
public void onResponse(Call<WeatherResult> call, final Response<WeatherResult> response) {
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
if (response.body().getErrNum() == 0) {
Toast.makeText(MainActivity.this, response.body().getRetData().getWeather(), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, response.body().getErrMsg(), Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
public void onFailure(Call<WeatherResult> call, Throwable t) {
t.printStackTrace();
}
});
}
});
}
@Override
public void onClick(View view) {
mWeatherService.getCityList("广东").enqueue(new Callback<CityListResult>() {
@Override
public void onResponse(Call<CityListResult> call, final Response<CityListResult> response) {
mResult = response.body();
if (mResult.getErrNum() == 0) {
mDatas.clear();
for (int i = 0; i < mResult.getRetData().size(); i++) {
mDatas.add(mResult.getRetData().get(i).getNameCN());
}
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.notifyDataSetChanged();
}
});
} else {
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, response.body().getErrMsg(), Toast.LENGTH_SHORT).show();
}
});
}
}
@Override
public void onFailure(Call<CityListResult> call, Throwable t) {
t.printStackTrace();
}
});
}
}
看到这么恶心的代码。。。把我自己也吓到了,其实并不多【尴尬死了】。主要的就是
获取服务
mWeatherService = new Retrofit.Builder()
.baseUrl("http://apis.baidu.com")
.addConverterFactory(GsonConverterFactory.create())
.build().create(WeatherService.class);
mListView = (ListView) findViewById(R.id.mListVie
创建请求
mWeatherService.getWeather(String.valueOf(mResult.getRetData().get(i).getAreaID())).enqueue(this);
结果解析
response.body();