该文章讲述Retrofit中的一些基本使用总结,不涉及源码的介绍,是个人在使用中总结出来对Retrofit的用法。
我们知道Retrofit是当今安卓开发中热门的网络请求框架,内部封装了okhttp,并且能与RxJava结合一同使用。三者结合使网络请求变得更加简洁,轻松和强大。
首先导入Retrofit2
implementation'com.squareup.retrofit2:retrofit:2.3.0'
1. 基本用法
interface Demo {
@GET("feeds")
Call<ResponseBody> getData();
}
写一个接口和获取数据方法,其中@GET表示使用GET方法请求,括号中的feeds代表追加地址。Call<ResponseBody>为Retrofit默认的数据返回类型。
当然这里仅仅是写了一个接口,并没有真正做网络请求。上面说到feeds代表追加地址,那么它肯定还要有一个基本请求地址。
//构建Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/") //基础地址
.build();
可以看到我们在构建Retrofit的过程中添加了基础地址(baseUrl),这时它的完整链接就是基础地址加上追加地址
这里为:https://api.github.com/feeds
文章到传递表单字段前都用 https://api.github.com/ 来作为基本的请求地址
一次完整的网络请求(记得添加网络权限)
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//构建Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/") //基础地址
.build();
Demo demo = retrofit.create(Demo.class);
Call<ResponseBody> data = demo.getData(); //调用接口方法并返回
//网络请求
data.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> response) {
try {
Log.i("TAG", "请求成功:" + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
Log.i("TAG", "请求失败: " + t);
}
});
}
interface Demo {
@GET("feeds")
Call<ResponseBody> getData();
}
}
2. 占位符:
/**
* 使用占位符进行请求
*
* @param appendUrl 动态url
* @return 一次请求的结果
*/
@GET("{url}")
Call<ResponseBody> getData2(@Path("url") String appendUrl);
可以看到我们之前的具体链接feeds替换成{url},并且用注解@Path("url")声明该替换的url是动态路径。
Call<ResponseBody> data = demo.getData2("feeds");
我们只需要在方法调用时输入动态的追加地址即可。
3. 查询参数
网络请求中常常需要用到一些查询的参数来确定具体数据,Retrofit为我们提供了@Query和@QueryMap注解。
@Query
本次请求地址:https://api.github.com/search/repositories?q=tetris
其中q=tetris就是查询参数
@GET("search/repositories")
Call<ResponseBody> getData3(@Query("q") String queryUrl);
方法调用
Call<ResponseBody> data = demo.getData3("tetris");
@QueryMap
多个查询参数时用到
本次请求地址:https://api.github.com/search/repositories?q=tetris&sort=stars
//接口
interface Demo {
@GET("search/repositories")
Call<ResponseBody> getData4(@QueryMap Map<String, String> map);
}
//使用
Demo demo = retrofit.create(Demo.class);
Map<String, String> map = new HashMap<>();
map.put("q", "tetris");
map.put("sort", "stars");
Call<ResponseBody> data = demo.getData4(map);
4. FromUrlEncode传递表单字段
被该注解标记的方法表示请求体是Form表单,结合@Field或@FieldMap可以通过@Post方法传递表单字段到服务器。这里我搭建一个最简单的测试后台,结合注解做一个简单的登录操作。
在Eclipse创建Dynamic Web Project,结构如上图。
登录逻辑也很简单,判断密码是否正确,并返回给移动端。
/**
* 登录服务(Login.java)
*/
@WebServlet("/login")
public class Login extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public Login() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
response.getWriter().append("Served at: ").append(request.getContextPath());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置编码格式,防止数据传到Android端乱码
response.setCharacterEncoding("utf-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
PrintWriter pw = response.getWriter();
if ("123".equals(username) && "123".equals(password)) {
System.out.println("登录成功");
pw.print("登录成功!");
} else {
System.out.println("登录失败");
pw.print("登录失败!");
}
pw.flush();
pw.close();
}
}
然后在Android端使用Retrofit注解做登录请求
首先有FormUrlEncoded注解的方法不能使用@Get请求,否则会抛出异常
Caused by: java.lang.IllegalArgumentException: FormUrlEncoded can only be specified on HTTP methods with request body (e.g., @POST).
接口写法:
interface Demo {
/**
* 传递表单字段需要被FormUrlEncoded注解
* @param username 用户名,对应服务端参数
* @param password 密码,对应服务端参数
* @return 一次请求结果
*/
@FormUrlEncoded
@POST("TestDemo/login")
Call<ResponseBody> doLogin(@Field("username") String username, @Field("password") String password);
}
完整请求:
//构建Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.1.122:8080/") //基础地址,tomcat地址和端口
.build();
Demo demo = retrofit.create(Demo.class);
Call<ResponseBody> call = demo.doLogin("123", "123");
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Log.i("TAG", response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
}
Android端Log:
I/TAG: 登录成功!
[ 12-27 04:41:51.171 13335:13372 D/ ]
SurfaceInterface::setAsyncMode: set async mode 1
@FieldMap则通过Map来传递集合
//接口
@FormUrlEncoded
@POST("TestDemo/login")
Call<ResponseBody> doLogin2(@FieldMap Map<String,String> map);
//使用
Map<String,String> map=new HashMap<>();
map.put("username","123");
map.put("password","123");
Call<ResponseBody> call = demo.doLogin2(map);
5. 文件上传
单文件上传@Multipart
结合@Part MultipartBody.Part使用上传文件
interface Demo {
@Multipart
@POST("TestDemo/uploadFile")
Call<ResponseBody> uploadFile(@Part MultipartBody.Part file);
}
完整请求(记得添加读写权限)
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//构建Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.1.122:8080/") //基础地址,tomcat地址和端口
.build();
Demo demo = retrofit.create(Demo.class);
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/gxImg/test.jpg");
//将File转换成二进制数据传到服务器的request
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
//添加到MultipartBody.Part中等待上传,参数一:服务器接收的key,参数二:文件名
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
//上传文件
Call<ResponseBody> call = demo.uploadFile(body);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Log.i("TAG", response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.i("TAG", "onFailure: " + t);
}
});
}
服务端:
/**
* 上传服务
*/
@WebServlet("/uploadFile")
public class UpLoadFile extends HttpServlet {
private static final long serialVersionUID = 1L;
public UpLoadFile() {
super();
// TODO Auto-generated constructor stub
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
response.getWriter().append("Served at: ").append(request.getContextPath());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 创建文件项目工厂对象
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置文件上传路径
String upload = "d:";
// 设置缓冲区大小为 5M
factory.setSizeThreshold(1024 * 1024 * 5);
// 用工厂实例化上传组件, 用来解析文件上传请求
ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
// 解析结果放在List中
try {
List<FileItem> list = servletFileUpload.parseRequest(request);
for (FileItem item : list) {
String name = item.getFieldName();
InputStream is = item.getInputStream();
System.out.println("当前文件名字 " + name);
if (name.contains("file")) {
try {
inputStream2File(is, upload + "\\" + item.getName());
} catch (Exception e) {
e.printStackTrace();
}
} else {
String key = item.getName();
String value = item.getString();
System.out.println(key + "---" + value);
}
}
out.write("上传成功");
} catch (FileUploadException e) {
e.printStackTrace();
out.write("上传失败");
}
out.flush();
out.close();
}
// 流转化成字符串
public static String inputStream2String(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int i = -1;
while ((i = is.read()) != -1) {
baos.write(i);
}
return baos.toString();
}
// 流转化成文件
public static void inputStream2File(InputStream is, String savePath) throws Exception {
System.out.println("文件路径:" + savePath);
File file = new File(savePath);
InputStream inputSteam = is;
BufferedInputStream fis = new BufferedInputStream(inputSteam);
FileOutputStream fos = new FileOutputStream(file);
int f;
while ((f = fis.read()) != -1) {
fos.write(f);
}
fos.flush();
fos.close();
fis.close();
inputSteam.close();
}
}
多文件上传@PartMap
//依旧使用上面服务端
@Multipart
@POST("TestDemo/uploadFile")
Call<ResponseBody> uploadMultiFiles(@PartMap Map<String, RequestBody> map);
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/gxImg/test.jpg");
Map<String, RequestBody> map = new HashMap<>();
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
//file是传到服务器的键,filename是传到服务器后使用的文件名
map.put("file\";filename=\"icon.png" , requestFile);//文件1
map.put("file1\";filename=\"icon1.png" , requestFile);//文件2,这里为了方便使用同一个文件
Call<ResponseBody> call = demo.uploadMultiFiles(map);
6. 统一请求头@Headers
@Headers("Accept: application/json, text/plain, */*")
@FormUrlEncoded
@POST("TestDemo/login")
Call<ResponseBody> doLogin(@FieldMap Map<String, String> paramsMap);
7. 向服务端传Json数据@Body
@POST("TestDemo/uploadJson")
Call<ResponseBody> uploadJson(@Body UserInfo info);
说到向服务器传递Json数据那就要提到Retrofit的转换器
转换器有什么用?我们想一下,我们现在要提交Json数据给服务器,但是@Body中传递的是一个UserInfo的Bean类,直接发送肯定不行,这时就要通过转换器,将Bean类转换成Json数据给服务器接收。这里既然传递Json数据,我们就是用Gson转换器。
//添加依赖
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
在构建Retrofit的时候添加转换器
//构建Retrofit
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())//添加转换器
.baseUrl("http://192.168.1.122:8080/")
.build();
发送数据到服务器
Demo demo = retrofit.create(Demo.class);
Call<ResponseBody> call = demo.uploadJson(new UserInfo("Fan", 10));
服务器代码:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
InputStream is = request.getInputStream();
byte[] buf = new byte[1024];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len;
if ((len = is.read(buf)) != -1) {
bos.write(buf, 0, len);
}
System.out.println("收到数据:" + bos.toString());
bos.close();
}
既然发送时可以转换成Json数据,那么从服务器获取数据是否可以解析Json传到Bean类呢?
答案是肯定的。
interface Demo {
@POST("TestDemo/uploadJson")
Call<UserInfo> uploadJson();
}
可以看到我们将默认的ResponseBody换成想要转换的bean类即可。
当然如果传来的Json数据与bean类属性不符合则会出错。
Demo demo = retrofit.create(Demo.class);
Call<UserInfo> call = demo.uploadJson();
call.enqueue(new Callback<UserInfo>() {
@Override
public void onResponse(Call<UserInfo> call, Response<UserInfo> response) {
//解析成UserInfo的数据使用
Log.i("TAG", response.body().getName());
Log.i("TAG", String.valueOf(response.body().getAge()));
}
@Override
public void onFailure(Call<UserInfo> call, Throwable t) {
}
});
这样,在成功的回调onResponse中就可以使用UserInfo的属性了。
8. 结合RxJava
为什么Retrofit会成为热门网络请求框架,除了请求简单和强大外,可以与目前热门框架RxJava结合使用也是原因之一。
结合RxJava首先要添加以下依赖
//Retrofit中RxJava的适配器
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
//RxJava
implementation 'io.reactivex.rxjava2:rxjava:2.0.1'
//RxAndroid,有安卓特有的一些方法
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
这里改造一下上面用到的Login.java,模拟登录成功后返回Json数据
if ("123".equals(username) && "123".equals(password)) {
System.out.println("登录成功");
//仅仅返回一个success属性
pw.write("{\"success\":\"登录成功\"}");
} else {
System.out.println("登录失败");
pw.write("登录失败!");
}
//接口编写
interface Demo {
/**
* 结合RxJava和Gson适配器使用
* SuccessResponse里只有一个属性
* 就是服务器返回的success
* @param username 用户名
* @param password 密码
* @return 请求结果
*/
@FormUrlEncoded
@POST("TestDemo/login")
Observable<SuccessResponse> login(@Field("username") String username, @Field("password") String password);
}
添加RxJava适配器
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())//添加转换器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //添加RxJava适配器
.baseUrl("http://192.168.1.122:8080/")
.build();
使用
Demo demo = retrofit.create(Demo.class);
Observable<SuccessResponse> observable = demo.login("123","123");
observable.subscribeOn(Schedulers.io()) //网络请求时切换io线程
.observeOn(AndroidSchedulers.mainThread()) //请求成功后切换主线程显示
.subscribe(new Consumer<SuccessResponse>() {
@Override
public void accept(SuccessResponse successResponse) throws Exception {
//主线程,显示数据
Log.i("TAG", "返回数据: " + successResponse.getSuccess());
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Log.i("TAG", "登录失败:"+throwable);
}
});
9. 添加okhttpClient
为Retrofit添加okhttpClient,可以使用OkHttp的一些属性(例如,设置连接超时)。
因为一开始说过,Retrofit内部封装了OkHttp,所以能直接使用,这里我们再为Okhttp添加拦截器(可以拦截请求中的请求头和请求体等数据),需要添加依赖。
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
//创建拦截器
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
Log.i("TAG", "请求中发送的请求头请求体等数据拦截在这里: " + message);
}
});
//设置拦截等级
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
//创建OkHttpClient并设置属性
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)//设置超时
.readTimeout(10, TimeUnit.SECONDS)
.addInterceptor(interceptor)//添加拦截器
.build();
//构建Retrofit
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())//添加转换器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //添加RxJava适配器
.baseUrl("http://192.168.1.122:8080/")
.client(client) //添加OkHttp
.build();
以上就是个人总结的一些Retrofit的基本用法,当然还有没有提到的,例如@Url,@Streaming,@Header等一些注解要自行查找文档。