前言
上一篇文章博主介绍了okhttp这个同步的网络请求框架的使用,也说了okhttp的优点,但是同时也指出了okhttp在使用方面的不便,所以本文介绍基于okhttp的网络框架retrofit的使用,还是同一个公司的产品.
1.支持异步请求
2.用户自己定义请求接口,由框架自动通过代理实现,这就相当于把网络请求的部分代码抽取出来,代码更加简洁
3.利用注解描述请求的参数,极大的方便了用户的使用
本文重在讲解如何使用retrofit,原理将会在后续的博客中发表,请感兴趣的人关注一下
一个牵扯到网络请求的app,在很多的Activity或者Fragment中都可能会用到网络请求,所以这里就使用在项目中比较常见的搭建方式
声明请求接口
/**
* Created by cxj on 2016/6/10.
* 所有的网络请求
*/
public interface NetWorkService {
/**
* 一个get请求的请求接口,返回是字符串
*
* @return
*
*/
@GET("https://publicobject.com/helloworld.txt")
public Call<String> get();
}
暂时先书写一个返回字符串的请求接口,由于这里最后的结果需要转化成字符串,在框架中需要添加转化的组建,在官网中提供了以下几种
快速搭建环境
编写MyApp继承Application,别忘了在项目清单文件中的application节点中添加name属性,并且添加网络请求的权限
/**
* Created by cxj on 2016/6/10.
*/
public class MyApp extends Application {
/**
* 声明请求的接口
*/
public static NetWorkService netWorkService;
/**
* 网络请求框架
*/
public static Retrofit retrofit;
@Override
public void onCreate() {
super.onCreate();
retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.1.102:8080/Retrofit/")
.addConverterFactory(ScalarsConverterFactory.create())
.build();
//让框架自动实现我们的请求接口,让我们的请求接口可以被调用
netWorkService = retrofit.create(NetWorkService.class);
}
}
上述代码中其实就是创建了初始化了请求的接口,并且添加了一个转化器,这样子在任何地方就可以直接使用了
并且可以看到我们有一个baseUrl,这是相当于前缀的意思,如果你请求的地址不是绝对的,那么他会添加上这个前缀,如果你的请求是绝对的地址,那么这个baseUrl就会被自动忽略
Get请求
xml布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<Button
android:layout_width="match_parent"
android:text="get请求测试<span style="font-family: Arial, Helvetica, sans-serif;">"</span>
android:onClick="getTest"
android:layout_height="wrap_content" />
</RelativeLayout>
就是一个按钮,点击请求网络
Activity中代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void getTest(View view) {
Call<String> call = MyApp.netWorkService.get();
call.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
String result = response.body();
System.out.println("result = " + result);
}
@Override
public void onFailure(Call<String> call, Throwable t) {
System.out.println("请求失败");
}
});
}
}
相比我们之前使用okhttp来请求,是不是变得简单了许多呢?
来看看请求的结果,可以看到请求成功
Post请求提交普通键值对
这里就需要用到自己的服务器啦,这里还是使用okhttp博文中使用的服务器,博文结束会分享
先使用浏览器测试一下返回的结果,先有一个直观的了解
第一次使用name:cxj pass:123 来注册
第二次使用name:cxj pass:123 注册
可以看到返回的数据中有失败的提示和响应的错误信息,表示用户名已经存在,那么接口这边没有问题啦,那我们就开工写客户端的代码吧!
在网络请求接口中添加测试方法
public interface NetWorkService {
/**
* 一个get请求的请求接口,返回是字符串
*
* @return
*/
@GET("https://publicobject.com/helloworld.txt")
public Call<String> get();
/**
* post提交数据
*
* @param name
* @param pass
* @return
*/
@FormUrlEncoded
@POST("test/register")
public Call<String> register(@Field("name") String name, @Field("pass") String pass);
}
可以看淡添加了一个注册的方法,使用POST请求,路径是相对路径,所以会拼接上前缀,提交的数据有name和pass,需要用@Field注解标明
xml文件中添加要给按钮,这里不再贴出,按钮调用注册的方法
Activity中注册方法代码
public void register() {
Call<String> call = MyApp.netWorkService.register("cxj", "123");
call.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
//拿到返回的json数据
String result = response.body();
//打印结果
System.out.println("result = " + result);
//利用Gson转化json为实体对象
Gson gson = new GsonBuilder().create();
//传化后的实体对象
Msg msg = gson.fromJson(result, Msg.class);
//提示实体对象中的信息
Toast.makeText(MainActivity.this, msg.getMsgText(), Toast.LENGTH_LONG).show();
}
@Override
public void onFailure(Call<String> call, Throwable t) {
System.out.println("注册失败");
}
});
}
如果请求成功,就会提示msg对象中的信息,这里牵扯到两个实体对象,这里贴一下代码
Msg:
public class Msg {
public static final String OK = "ok";
public static final String ERROR = "error";
/**
* 只有两个值<br>
* 1."ok"<br>
* 2."error"
*/
private String msg = ERROR;
/**
* 信息的文本
*/
private String msgText = ERROR;
private Object data;
public Msg() {
super();
}
public Msg(String msg, Object data) {
super();
this.msg = msg;
this.data = data;
}
public Msg(String msg, String msgText, Object data) {
super();
this.msg = msg;
this.msgText = msgText;
this.data = data;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getMsgText() {
return msgText;
}
public void setMsgText(String msgText) {
this.msgText = msgText;
}
@Override
public String toString() {
return "Msg [msg=" + msg + ", msgText=" + msgText + ", data=" + data + "]";
}
}
User:
/**
* 用户
*
* @author cxj
*
*/
public class User {
private String name;
private String pass;
public User(String name, String pass) {
this.name = name;
this.pass = pass;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
/**
* 只有当密码和名字一样的时候才相等
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof User) {
User u = (User) obj;
if (name != null && pass != null) {
return name.equals(u.name);
}
}
return false;
}
}
博主使用同一个用户名和密码连续注册两次的效果如下
效果图
可以看到第一次是提示成功,第二次是提示用户名存在,并且在控制台打印返回的结果可以看到是json数据,而我们提示成功说明了json数据被成功的转化
Post提交文件
首先是单文件上传,并且提交两个普通的键值对
先添加请求的接口中的上传单个文件的方法
/**
* 测试提交一个文件和两个普通的键值对
*
* @param fileBody
* @param nameBody
* @param passBody
* @return
*/
@Multipart
@POST("test/uploadFile")
public Call<String> postFile(@Part("file\"; filename=\"avatar.db") RequestBody fileBody, @Part("name") RequestBody nameBody, @Part("pass") RequestBody passBody);
比上述两个稍微复杂一点,因为文件上传必须使用POST请求,而且是一个请求体中分多个部分,所以这里使用@Part表示一部分的请求体,第一个是我们的文件部分的数据,@Part里面的字符串当作这部分的名称,但是我们可以看到我们这里写的比较奇怪,那就先看一下这个请求提交的时候拦截下来的POST报文
这部分是POST请求中最上面的,我们看到倒数第四行中发现了我们所写的内容,原来是为了既能输出name的value值,也能输出这分布的文件的名称,这也就解释的通上述第一个参数中的注解里面的内容了
还有另外两个键值对,也看一下报文吧
乱码的那几个字其实就是我提交的name的值 "小金子"
下面还有一个 '123' 是我提交的pass的值,而我们可以看到我们在请求接口中的@Part注解内的内容作为了这里的name属性的value值
上述查看报文完全是为了帮助读者了解为什么文件部分的那个RequestBody参数的注解@Part为什么是
file\"; filename=\"avatar.db
完全是为了在报文中输出文件名,这里我觉得官方网站还是可以再优化一下的,毕竟这样子让人不太好理解
Activity中上传文件的代码
public void postFile(View v) {
//需要上传的文件
File f = new File(Environment.getExternalStorageDirectory(), "address.db");
//创建文件部分的请求体对象
RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), f);
//普通键值对的请求体
RequestBody nameBody = RequestBody.create(null,"小金子");
RequestBody passBody = RequestBody.create(null,"123");
Call<String> call = MyApp.netWorkService.postFile(fileBody, nameBody, passBody);
call.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
System.out.println("上传成功" + response.body() + response.code() + response.errorBody());
}
@Override
public void onFailure(Call<String> call, Throwable t) {
System.out.println("上传文件失败");
}
});
}
查看提交的效果
可以看到在服务器上已经收到啦
Post提交多文件
这回我们换一种方法来声明请求接口中的方法
@POST("test/uploadFiles")
public Call<String> postFiles(@Body MultipartBody multipartBody );
这回直接让请求体作为参数,其实这个和自己利用okhttp发送已经很像了
Activity中上传多文件的代码
/**
* 多文件上传
*
* @param view
*/
public void postFiles(View view) {
//需要上传的文件
File f1 = new File(Environment.getExternalStorageDirectory(), "address.db");
File f2 = new File(Environment.getExternalStorageDirectory(), "1.apk");
//创建文件部分的请求体对象
RequestBody fileBody1 = RequestBody.create(MediaType.parse("application/octet-stream"), f1);
RequestBody fileBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), f2);
MultipartBody multipartBody = new MultipartBody.Builder()
.addFormDataPart("files", f1.getName(), fileBody1)
.addFormDataPart("files", f2.getName(), fileBody2)
.addFormDataPart("name", "小金子")
.addFormDataPart("pass", "123")
.build();
Call<String> call = MyApp.netWorkService.postFiles(multipartBody);
call.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
System.out.println("上传成功");
}
@Override
public void onFailure(Call<String> call, Throwable t) {
System.out.println("上传多文件失败");
}
});
}
由于博主使用的是模拟器,没有那么多的文件测试,这里就用了两个文件,和n个文件原理是一样的,你们可以适度的封装一下,利用for循环来添加文件部分的请求体