在Android开发中,我们肯定是会遇到网络请求的,由于网络请求操作是一个耗时操作,不能在UI线程里执行,而且实际开发网络请求还有很多问题要考虑,你需要自己管理线程切换,需要自己解析读取数据,解析数据成对象,切换回主线程,回调给上层。所以我们初学者一般是应用一些技术成熟的第三方框架来进行网络请求,像Volley、Okhttp、Retrofit等,下面我就用我的学习经历来简单介绍一下Retrofit2.0的用法。
介绍
Retrofit是Square开发的开源网络请求库, 简化了网络请求的使用,为Android平台的应用提供一个类型安全的一套RESTful架构的Android(Java)客户端实现。
Retrofit已经更新到2.0了,所以我就直接学习2.0版本的使用,这里有Retrofit2.0更新变化。
环境搭建:
刚接触如Gson,Jackson等第三方类库框架时,要接触一个新的框架,首先就是要从网上找它的jar包,导入到IDE中,然后就接触到Maven,但是Maven是Eclipse的,AndroidStudio有Gradle,两个都是项目自动构建工具,用Gradle就可以通过几行的代码就把第三方类库自动下载导入到项目中,但是有可能有一些第三方类库的下载地址是被墙了的,我上次就是不知道干了什么Gradle build每一个项目都要一个多小时,一怒之下把我的AndroidStudio1.5换成2.0之后再让它build两个小时才搞定,而且现在打开以前的project都变成Gradle版本不对编译不了,所以换版本要慎重。
- 以前我使用第三方框架的时候都是下载它的jar包导入到Project里面,现在学会用了Gradle之后就不用这样做了,直接在Gradle里面加依赖就行了。
Retrofit包括了网络请求部分(Retrofit2.0之后强制使用Okhttp3.x),还有一个将从网络请求到的数据解析的功能,所以就要在Gradle里面导入这些类库了:
- 首先在App目录下找到
- 然后在里面的dependencies里加入compile项:
- 首先在App目录下找到
compile 'com.squareup.retrofit2:retrofit:2.0.2' // Retrofit
compile 'com.google.code.gson:gson:2.5' // Gson
compile 'com.squareup.retrofit2:converter-gson:2.0.2' // 用Gson解析json的转换器
compile 'com.squareup.okhttp3:okhttp:3.1.2'// okhttp,这里加入Okhttp的原因是下面用到的Post请求数据的需要一个Okhttp的RequestBody包装。
数据解析这里不一定用Gson,Retrofit官网给出可以用以下的类库解析对应的数据:
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
- 最后就是进行Make project了
这样就完成了Retrofit2.0在AndroidStudio的环境配置了。
使用Retrofit进行网络请求
使用步骤
使用Retrofit的步骤总结起来有4个:
1. 创建Retrofit对象
2. 创建访问API的请求,即创建Service接口
3. 发送请求
4. 处理结果
Retrofit把网络请求的URL分成了两部分,一部分baseurl放在Retrofit对象里,另一部分放在Service接口里,如果接口里的url是一个完整的网址,那么baseurl可以忽略。
5. (取消请求)
Retrofit2.0新增了取消请求的功能,无论你的请求执行到哪一步,都可以随时调用 cancel 方法来取消请求。
一个使用GET方法的例子,将向有道API请求json数据并用gson解析:
这是它的接口,用来声明用什么方法请求和请求的url
package scut.textretrofit;
import retrofit2.Call;
import retrofit2.http.GET;
/**
* Created by yany on 2016/4/25.
*/
public interface Youdao {
@GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
Call<fanyi> getCall();
}
Gson对应的JavaBean:
package scut.textretrofit;
import java.util.List;
public class fanyi {
public String[] translation;
public basic basic;
public static class basic{
public String phonetic;
public String[] explains;
}
public String query;
public int errorCode;
public List<wb> web;
public static class wb{
public String[] value;
public String key;
}
public void show(){
for (int i = 0;i<translation.length;i++)
{
System.out.println(translation[i]);}
System.out.println(basic.phonetic);
for (int i = 0;i<basic.explains.length;i++){
System.out.println(basic.explains[i]);
}
System.out.println(query);
System.out.println(errorCode);
for (int i = 0;i<web.size();i++){
for(int j = 0; j<web.get(i).value.length;j++)
{
System.out.println(web.get(i).value[j]);
}
System.out.println(web.get(i).key);
}
}
}
MainActivity:
package scut.textretrofit;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class MainActivity extends AppCompatActivity {
public static final String API_URL = "http://218.192.170.132:8082";
private TextView tv;
HashMap<String, String> map;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
request();
}
});
}
public void request() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://fanyi.youdao.com/")//设置baseUrl
.addConverterFactory(GsonConverterFactory.create())//设置使用json解析
.build();
Youdao YD = retrofit.create(Youdao.class);
Call<fanyi> call = YD.getCall();
call.enqueue(new Callback<fanyi>() {//进行异步请求
//请求成功时候的回调
@Override
public void onResponse(Call<fanyi> call, Response<fanyi> response) {
response.body().show();
}
//请求失败时候的回调
@Override
public void onFailure(Call<fanyi> call, Throwable throwable) {
System.out.println("连接失败");
}
});
}
}
输出:
System.out: 车
System.out: kɑː
System.out: n. 汽车;车厢
System.out: n. (Car)人名;(土)贾尔;(法、西)卡尔;(塞)察尔
System.out: car
System.out: 0
System.out: 汽车
System.out: 小汽车
System.out: 轿车
System.out: Car
System.out: 概念车
System.out: 概念车
System.out: 概念汽车
System.out: concept car
System.out: 碰碰车
System.out: 碰撞用汽车
System.out: 碰碰汽车
System.out: bumper car
一个用POST方法请求的例子:
接口声明用POST方法,声明方法传入POST请求的BODY
package scut.textretrofit;
import java.util.HashMap;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
/**
* Created by yany on 2016/4/25.
*/
public interface SearchService {
@POST("http://218.192.170.132:8082/commodity/search")
Call<SearchBean> getCall(@Body RequestBody body);//Body只能用Okhttp的RequestBody
}
Gson对应的JavaBean:
package scut.textretrofit;
import java.util.List;
/**
* Created by yany on 2016/4/23.
*/
public class SearchBean {
private List<Data> data ;
private boolean fail;
private int total;
public void setData(List<Data> data){
this.data = data;
}
public List<Data> getData(){
return this.data;
}
public void setFail(boolean fail){
this.fail = fail;
}
public boolean getFail(){
return this.fail;
}
public void setTotal(int total){
this.total = total;
}
public int getTotal(){
return this.total;
}
static class Data {
private String picture;
private int price;
private String address;
private int express_cost;
private String name;
private int lowerest_wholesale;
public void setPicture(String picture) {
this.picture = picture;
}
public String getPicture() {
return this.picture;
}
public void setPrice(int price) {
this.price = price;
}
public int getPrice() {
return this.price;
}
public void setAddress(String address) {
this.address = address;
}
public String getAddress() {
return this.address;
}
public void setExpress_cost(int express_cost) {
this.express_cost = express_cost;
}
public int getExpress_cost() {
return express_cost;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setLowerest_wholesale(int lowerest_wholesale) {
this.lowerest_wholesale = lowerest_wholesale;
}
public int getLowerest_wholesale() {
return this.lowerest_wholesale;
}
}
}
MainActivity:
package scut.textretrofit;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.HashMap;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class MainActivity extends AppCompatActivity {
public static final String API_URL = "http://218.192.170.132:8082";
private TextView tv;
HashMap<String, String> map;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
POSTrequest();
}
});
}
public void POSTrequest(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
SearchService yd1 = retrofit.create(SearchService.class);
//输入POST的BODY
JSONObject jo = new JSONObject();
try {
jo.put("content","台灯");
jo.put("from",1);
jo.put("size",2);
} catch (JSONException e) {
e.printStackTrace();
}
//把Body加上头,将请求的json包装成httpPOST请求
MediaType JSON = MediaType.parse("application/json;charset=utf-8");
RequestBody Body=RequestBody.create(JSON,jo.toString());
System.out.println(jo.toString());
Call<SearchBean> call = yd1.getCall(Body);
call.enqueue(new Callback<SearchBean>() {
@Override
public void onResponse(Call<SearchBean> call, Response<SearchBean> response) {
System.out.println("有回应");
if (response.isSuccessful()) {
for(int i = 0; i<response.body().getData().size();i++) {
System.out.println(response.body().getData().get(i).getName());
System.out.println(response.body().getData().get(i).getAddress());
System.out.println(response.body().getData().get(i).getPicture());
System.out.println(response.body().getData().get(i).getExpress_cost());
}
}else{
System.out.println("返回的信息解析失败");//检查回应能否被解析
try {
System.out.println("返回的信息:"+response.errorBody().string());//服务器返回的错误信息
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Call<SearchBean> call, Throwable throwable) {
System.out.println("失败");
}
});
}
}
输出
05-02 11:56:58.720 26995-26995/com.example.yany.testretrofit I/System.out: 有回应
05-02 11:56:58.720 26995-26995/com.example.yany.testretrofit I/System.out: 迷你台灯
05-02 11:56:58.720 26995-26995/com.example.yany.testretrofit I/System.out: 广东省清远市
05-02 11:56:58.720 26995-26995/com.example.yany.testretrofit I/System.out: http://www.test.com/4.jpg
05-02 11:56:58.720 26995-26995/com.example.yany.testretrofit I/System.out: 10
05-02 11:56:58.720 26995-26995/com.example.yany.testretrofit I/System.out: 低级台灯
05-02 11:56:58.720 26995-26995/com.example.yany.testretrofit I/System.out: 广东省清远市
05-02 11:56:58.720 26995-26995/com.example.yany.testretrofit I/System.out: http://www.test.com/4.jpg
05-02 11:56:58.720 26995-26995/com.example.yany.testretrofit I/System.out: 10
直接获取API服务器返回的内容:
如果我想直接获得API服务器返回的数据内容,不进行解析的话,那我可以直接用把Service接口的返回值类型改为Call<ResponseBody>
:
public interface Youdao {
@GET("openapi.do")
Call<ResponseBody> getCall(@QueryMap Map<String,String> map);
}
我把第一个GET方法修改一下:
public class MainActivity extends AppCompatActivity {
public static final String API_URL = "http://218.192.170.132:8082";
private TextView tv;
HashMap<String, String> map;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
map = new HashMap<String, String>();
map.put("content","台灯");
map.put("from","1");
map.put("size","2");
request();
}
public void request() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://fanyi.youdao.com/")//设置baseUrl
.addConverterFactory(GsonConverterFactory.create())//设置使用gson解析
.build();
Youdao YD = retrofit.create(Youdao.class);
Map<String,String> map = new HashMap<String, String>();
map.put("keyfrom","Yanzhikai");
map.put("key","2032414398");
map.put("type","data");
map.put("doctype","json");
map.put("version","1.1");
map.put("q","car");
Call<ResponseBody> call = YD.getCall(map);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
System.out.println(response.isSuccessful());
if (response.isSuccessful()) {
try {
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
else {
try {
System.out.println(response.errorBody().string());
} catch (IOException e) {
e.printStackTrace();
}
;
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable throwable) {
System.out.println("连接失败");
}
});
}
}
输出:
System.out: {"translation":["车"],"basic":{"us-phonetic":"kɑr","uk-speech":"car&type=1","speech":"car&type=1","phonetic":"kɑː","uk-phonetic":"kɑː","us-speech":"car&type=2","explains":["n. 汽车;车厢","n. (Car)人名;(土)贾尔;(法、西)卡尔;(塞)察尔"]},"query":"car","errorCode":0,"web":[{"value":["汽车","小汽车","轿车"],"key":"Car"},{"value":["装甲车","防弹车","装甲车"],"key":"Armored car"},{"value":["紧凑型轿车","小型汽车","中级轿车"],"key":"compact car"}]}
Retrofit的请求参数注解字段
请求参数注解字段,就是在接口里面的注解,Retrofit把注解也应用了,就像重写方法就加一个@Overwrite一样,Retrofit里面的@GET、@POST、@PUT、@DELETE、@HEAD修饰了五种HTTP请求方式。
另外还有:
- @Header和@Headers
有一些API(例如百度API)要求把秘钥信息放在Healer里面进行请求,用这个可以用于添加HttpHeader:
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
相当于:
@Headers("Authorization: authorization")
@GET("user")
Call<User> getUser()
- @Path
可以在URL里面充当变量:
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId);
groupId变量的值就是{id}的值。
- @Query和@QueryMap:
这是专门用于GET方法里面的查询参数注解:
@GET("group/users")
Call<List<User>> groupList(@Query("id") int groupId);
相当于
@GET("group/users?id=groupId")
所以根据上面的GET方法URL,可以把上面改为:
@GET("openapi.do")
Call<fanyi> getCall(@QueryMap Map<String,String> map);
然后在调用时候传入对应的Map
Map<String,String> map = new HashMap<String, String>();
map.put("keyfrom","Yanzhikai");
map.put("key","2032414398");
map.put("type","data");
map.put("doctype","json");
map.put("version","1.1");
map.put("q","car");
Call<fanyi> call = YD.getCall(map);
得到的效果一样。
@Url
@Url是Retrofit2.0新的标注,允许你直接传入一个请求的 URL变量,然后访问这个URL
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
@GET
Call<List<Contributor>> repoContributorsPaginate(
@Url String url);
}
这里就是通过repoContributors方法进行第一个请求,获取返回到的URL之后传入到repoContributorsPaginate方法,继续进行请求。
- @Field
这是专门用于Post方式传递参数,在请求接口方法上添加@FormUrlEncoded,就可以以表单的方式传递参数到服务器就像这样:
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
- @Body
这也是专门用于Post方式传递参数,在Body里面可以传自己想传的数据类型给服务器,不过要符合服务器HTTP请求的格式,就像前面的例子提交一个json数据请求一样,如果提交的是一个Map,那么作用就是相当于@Field了,不过Map要经过FormBody.Builder类处理成为符合Okhttp格式的表单,如:
FormBody.Builder builder = new FormBody.Builder();
builder.add("key","value");
...
同步和异步请求
每一个Call对象可以进行同步请求(call.excute())或者异步请求(call.enquene(CallBack callBack)),每一个对象仅仅能够被使用一次,但是可以通过clone()函数创建一个新的可用的对象。
2.0版本之前,要根据请求方式是否同步来在接口声明不同的方法,但是在2.0版本之后,声明方式都统一了,所以以前很多资料都和2.0版本不同,不要被误导了。
总结
以上一些我对于Retrofit2.0的学习经验,由于2.0刚推出才半年,而且和上一个版本相差很大,所以网上的很多资料都不是符合2.0,Retrofit2.0还有很多的功能我还没有用到,以后还有机会学习的,希望我的经验能帮助到大家。
参考:
http://square.github.io/retrofit/
https://segmentfault.com/a/1190000004536439
http://www.jianshu.com/p/7687365aa946
http://www.loongwind.com/archives/242.html