Android 网络编程 Retrofit
适用于 Android 和 Java 的类型安全 HTTP 客户端。Retrofit 是一款基于注解把 http api 转成接口,使用起来更加简单。
文档和代码
文档地址:
Retrofit 文档 :https://square.github.io/retrofit/
Github 地址:https://github.com/square/retrofit
Retrofit API 文档:https://square.github.io/retrofit/2.x/retrofit/
添加依赖
版本
版本在 release 里看就好了
1
| implementation 'com.squareup.retrofit2:retrofit:(insert latest version)'
|
目前最新的版本是:2.9.0
所以我这里的依赖就用
1
| implementation 'com.squareup.retrofit2:retrofit:2.9.0
|
另外,我们要把结果转成对象,这个时候需要加一些转换器
1
| implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
当然啦,有多种。在官方文档中有提到
可以转 xml 的数据,可以转 Json 的数据。我们的数据格式是 json,可以用 Gson,也可以用 Jackson
版本地址可以到中央仓库去搜索一下
converter-gson: https://mvnrepository.com/artifact/com.squareup.retrofit2/converter-gson
入门代码
- 先定义一个接口
1 2 3 4 5 6 7
| public interface SobMiniWebInterface {
@GET("/get/text") Call<JsonResult> getJson();
}
|
- 定义结果 bean 类(实体类)
- 创建 retrofit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public void getJson(View view) { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http//:127.0.0.1:9102") .addConverterFactory(GsonConverterFactory.create()) .build(); SobMiniWebInterface sobMiniWebInterface = retrofit.create(SobMiniWebInterface.class); Call task = sobMiniWebInterface.getJson(); task.enqueue(new Callback() { @Override public void onResponse(Call call,Response response) { Log.d(TAG,"response -- > " + response.body()); }
@Override public void onFailure(Call call,Throwable t) { Log.d(TAG,"exception -- > " + t.toString()); } }); }
|
- 执行结果:
初步封装
如果我们每次使用,都要去创建 Retrofit,那不是很麻烦吗?
所以我们可以把这些相关的配置,整合起来。把 Retrofit 的创建和配置定义成一个单例,这样子,我们要去发起请求的时候,直接使用单例获取就完事了。不用再重复去编写 baseURL 、转换器、连接时的相关属性,等等…
于是我们创建一个单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package com.sunofbeaches.retrofitdemo;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitCreator {
public static final String BASE_URL = "https://127.0.0.1:9102"; public static final int CONNECT_TIME_OUT = 10000; private Retrofit mRetrofit;
private RetrofitCreator() { createRetrofit(); }
private void createRetrofit() { OkHttpClient okHttpClient = new OkHttpClient.Builder() .connectTimeout(CONNECT_TIME_OUT,TimeUnit.MILLISECONDS) .build(); mRetrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build(); }
private static RetrofitCreator sRetrofitCreator = null;
public static RetrofitCreator getInstance() { if(sRetrofitCreator == null) { synchronized(RetrofitCreator.class) { if(sRetrofitCreator == null) { sRetrofitCreator = new RetrofitCreator(); } } } return sRetrofitCreator; }
public Retrofit getRetrofit() { return mRetrofit; }
}
|
那使用变成怎么样了呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public void getJson(View view) { SobMiniWebInterface sobMiniWebInterface = RetrofitCreator.getInstance().getRetrofit().create(SobMiniWebInterface.class); Call task = sobMiniWebInterface.getJson(); task.enqueue(new Callback() { @Override public void onResponse(Call call,Response response) { Log.d(TAG,"response -- > " + response.body()); }
@Override public void onFailure(Call call,Throwable t) { Log.d(TAG,"exception -- > " + t.toString()); } }); }
|
如果多个地方使用呢?重复的代码是不是变少啦!
接口定义
前面我们定义了一个接口
1 2 3 4 5 6 7
| public interface SobMiniWebInterface {
@GET("/get/text") Call<JsonResult> getJson();
}
|
这里我们可以看到有一个注解@Get
@Documented
@Target(value=METHOD)
@Retention(value=RUNTIME)
public @interface GET Make a GET request.
从官方的 API 文档里我们可以看到这个注解是作用于方法,是一个运行时起作用的注解。创建一个 get request 之前我们创建 Request 是 new 出来了。Retrofit 则直接使用注解
知道这个,那就好办了!
那我 post 请求呢?是不也可以来一个@Post 呢?
是的!Restrofit,rest 就是 Restful 的意思,按这个套路,肯定有 get、post、delete、put。当然还有其他的。
那咱们列一个思维导图吧
get 请求
1 2 3 4 5 6 7
| public interface SobMiniWebInterface {
@GET("/get/text") Call<JsonResult> getJson();
}
|
带参数的咋整呢?
比如说这个接口:
参数: - keyword 关键字 - page 页码 - order 0 顺序 1 逆序
接口定义:两种写法都可以
1 2 3 4 5
| @GET("/get/param") Call<GetParamsResult> getWithParams(@Query("keyword") String keyword,@Query("page") int page,@Query("order") String order);
@GET("/get/param") Call<GetParamsResult> getWithParams(@QueryMap Map<String,Object> params);
|
调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| Call<GetParamsResult> call = mSobMiniWebInterface.getWithParams("关键字测试",1,"1"); call.enqueue(new Callback<GetParamsResult>() { @Override public void onResponse(Call<GetParamsResult> call,Response<GetParamsResult> response) { GetParamsResult result = response.body(); Log.d(TAG,"result -- > " + result); if(result.getCode() == 10000) { Log.d(TAG,"请求成功"); Log.d(TAG,"结果信息 --> " + result.getMessage()); } }
@Override public void onFailure(Call<GetParamsResult> call,Throwable t) { Log.d(TAG,"请求失败...onFailure"); } });
Map<String,Object> params = new HashMap<>(); params.put("keyword","map 参数测试关键字"); params.put("page",1); params.put("order","0"); Call<GetParamsResult> mapTask = mSobMiniWebInterface.getWithParams(params); mapTask.enqueue(new Callback<GetParamsResult>() { @Override public void onResponse(Call<GetParamsResult> call,Response<GetParamsResult> response) { Log.d(TAG,"onResponse ==> " + response.body()); }
@Override public void onFailure(Call<GetParamsResult> call,Throwable t) { Log.d(TAG,"onFailure == > " + t.toString()); } });
|
结果:
1 2 3 4
| D/MainActivity: getWithParams.. D/MainActivity: result -- > GetParamsResult{success=true, code=10000, message='get带参数请求成功.', data=DataBean{page='1', keyword='关键字测试', order='逆序 D/MainActivity: 请求成功 D/MainActivity: 结果信息 --> get带参数请求成功.
|
到这里我们就学习了
@Qeury 注解,@QueryMap 注解
post 请求,body 携带字符串内容(json)
这个参数是 body 的内容
接口文档看前面呀。
定义接口
1 2
| @POST("/post/comment") Call<PostWithBodyResult> postWithBodyContent(@Body CommentItem commentItem);
|
调用代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public void postWithBody(View view) { CommentItem commentItem = new CommentItem(); commentItem.setArticleId("1251412341"); commentItem.setCommentContent("这是我提交的测试评论内容..."); Call<PostWithBodyResult> task = mSobMiniWebInterface.postWithBodyContent(commentItem); task.enqueue(new Callback<PostWithBodyResult>() { @Override public void onResponse(Call<PostWithBodyResult> call,Response<PostWithBodyResult> response) { Log.d(TAG,"response -- > " + response.body()); }
@Override public void onFailure(Call<PostWithBodyResult> call,Throwable t) { Log.d(TAG,"post with body...onFailure"); } }); }
|
结果:
1
| D/MainActivity: response -- > PostWithBodyResult{success=true, code=10000, message='评论成功:这是我提交的测试评论内容...', data=null}
|
到这里我们就学习了@Body 注解
post 请求 Url 携带参数
接口:
接口定义:
1 2 3
| @POST("/post/string") Call<PostWithUrlParamsResult> postWithUrlParams(@Query("string") String text);
|
调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public void postWithUrlParams(View view) { Call<PostWithUrlParamsResult> task = mSobMiniWebInterface.postWithUrlParams("这是我提交的参数内容"); task.enqueue(new Callback<PostWithUrlParamsResult>() { @Override public void onResponse(Call<PostWithUrlParamsResult> call,Response<PostWithUrlParamsResult> response) { Log.d(TAG,"onResponse result -- > " + response.body()); }
@Override public void onFailure(Call<PostWithUrlParamsResult> call,Throwable t) { Log.d(TAG,"onFailure -- > " + t.toString()); } }); }
|
单文件上传
权限自行处理
@Part 注解,要跟@Multipart 注解一起使用。
而参数类型有三种:
- MultipartBody.Part
- RequestBody
- Other object
具体可以去看看文档
除了 MultipartBody.Part 类型以外,其他的参数类型都要加上注解参数。
接口:
1 2 3 4
| @Multipart @POST("/file/upload") Call<FileUploadResult> postFile(@Part MultipartBody.Part file);
|
上传文件调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public void postFile(View view) { File file = new File("/storage/emulated/0/Download/1.jpg"); MediaType mediaType = MediaType.parse("image/jpg"); RequestBody fileBody = RequestBody.create(mediaType,file); MultipartBody.Part part = MultipartBody.Part.createFormData("file",file.getName(),fileBody); Call<FileUploadResult> task = mSobMiniWebInterface.postFile(part); task.enqueue(new Callback<FileUploadResult>() { @Override public void onResponse(Call<FileUploadResult> call,Response<FileUploadResult> response) { Log.d(TAG,"onResponse -- > " + response.body()); }
@Override public void onFailure(Call<FileUploadResult> call,Throwable t) { Log.d(TAG,"onFailure -- > " + t.toString()); } }); }
|
上传结果:
到这里我们就学习了@Part 和@Multipart 注解的使用了
上传文件及附带信息
提这个方式是为了跟大家了解@PartMap 的使用
接口定义:
1 2 3 4
| @Multipart @POST("/multiFiles/upload") Call<FileUploadResult> postFileWithParams(@PartMap Map<String,Object> params,@Part MultipartBody.Part file);
|
调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public void postMultiFile(View view) { Map<String,Object> params = new HashMap<>(); MultipartBody.Part filePart = getPart("file","/storage/emulated/0/Download/rBsADV3nxtKACoSfAAAPx8jyjF8169.png"); params.put("isFree","false"); params.put("description","我是文件的描述内容..."); Call<FileUploadResult> task = mSobMiniWebInterface.postFileWithParams(params,filePart); task.enqueue(new Callback<FileUploadResult>() { @Override public void onResponse(Call<FileUploadResult> call,Response<FileUploadResult> response) { Log.d(TAG,"onResponse -- > " + response.body()); }
@Override public void onFailure(Call<FileUploadResult> call,Throwable t) { Log.d(TAG,"onFailure -- >" + t.toString()); } }); }
|
结果:
1
| onResponse -- > FileUploadResult{success=true, code=10000, message='上传成功.文件路径为:E:\codes\Idear\SobNetworkCourseServer\target\classes\sobUpload\rBsADV3nxtKACoSfAAAPx8jyjF8169.png', data=your descriptions is --> "我是文件的描述内容..." isFree == > "false"}
|
多文件上传
后台接口:
定义接口:
1 2 3
| @Multipart @POST("/files/upload") Call<FileUploadResult> postFiles(@Part List<MultipartBody.Part> files);
|
调用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public void postFiles(View view) { List<MultipartBody.Part> files = new ArrayList<>(); files.add(getPart("files","/storage/emulated/0/Download/1.jpg")); files.add(getPart("files","/storage/emulated/0/Download/rBsADV3nxtKACoSfAAAPx8jyjF8169.png")); files.add(getPart("files","/storage/emulated/0/Download/rBsADV2rEz-AIzSoAABi-6nfiqs456.png"));
Call<FileUploadResult> task = mSobMiniWebInterface.postFiles(files); task.enqueue(new Callback<FileUploadResult>() { @Override public void onResponse(Call<FileUploadResult> call,Response<FileUploadResult> response) { Log.d(TAG,"多文件上传结果" + response.body()); }
@Override public void onFailure(Call<FileUploadResult> call,Throwable t) { Log.d(TAG,"onFailure -- > 多文件上传失败 ---> " + t.toString()); } }); }
|
辅助方法
1 2 3 4 5 6 7
| private MultipartBody.Part getPart(String key,String filePath) { File file = new File(filePath); MediaType mediaType = MediaType.parse("image/jpg"); RequestBody fileBody = RequestBody.create(mediaType,file); return MultipartBody.Part.createFormData(key,file.getName(),fileBody); }
|
多文件上传结果
FileUploadResult{success=true, code=10000, message=’上传成功 3 个文件,路径:C:/Users/TrillGates/Desktop/SOBAndroidMiniWeb/sobUpload’, data=null}
文件下载
接口
定义接口:
1 2 3
| @Streaming @GET Call<ResponseBody> downFile(@Url String url);
|
代码调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ublic void downFile(View view) { Call<ResponseBody> task = mSobMiniWebInterface.downFile("/download/10"); task.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call,Response<ResponseBody> response) { Headers headers = response.headers(); for(int i = 0; i < headers.size(); i++) { Log.d(TAG,headers.name(i) + " == " + headers.value(i)); } writeFile2Sd(response,headers); }
@Override public void onFailure(Call<ResponseBody> call,Throwable t) { Log.d(TAG,"onFailure -- > " + t.toString()); } }); }
|
同学们会发现,我们收取的内容,需要进行 iO 操作,但是主线程不可以做文件读取呀。
所以又整了一个子线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| private void writeFile2Sd(final Response<ResponseBody> response,final Headers headers) { new Thread(new Runnable() { @Override public void run() { String disposition = headers.get("Content-disposition"); if(disposition != null) { int fileNameIndex = disposition.indexOf("filename="); Log.d(TAG,"fileNameIndex -- > " + fileNameIndex); String fileName = disposition.substring(fileNameIndex + "filename=".length()); Log.d(TAG,"fileName -- > " + fileName); File picFilePath = MainActivity.this.getExternalFilesDir(Environment.DIRECTORY_PICTURES); Log.d(TAG,"picFilePath --> " + picFilePath); File file = new File(picFilePath + File.separator + fileName); Log.d(TAG,"file -- > " + file); FileOutputStream fos = null; try { if(!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } if(!file.exists()) { file.createNewFile(); } fos = new FileOutputStream(file); InputStream inputStream = response.body().byteStream(); byte[] buf = new byte[1024]; int len; while((len = inputStream.read(buf,0,buf.length)) != -1) { fos.write(buf,0,len); } } catch(Exception e) { e.printStackTrace(); } finally { if(fos != null) { try { fos.close(); } catch(IOException e) { e.printStackTrace(); } } } } } }).start(); }
|
这样子特别不方便,对吧!也不优雅,所以后面我们就要引入 RxJava 去解决这个问题了。
RxJava 可以很方便地切换线程。
下载结果
提交表单
方法:post 接口:/login
定义接口
1 2 3
| @FormUrlEncoded @POST("/login") Call<ResponseBody> doLogin(@Field("userName") String userName,@Field("password") String password);
|
调用代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public void login(View view) { Call<ResponseBody> call = mSobMiniWebInterface.doLogin("root","admin"); call.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call,Response<ResponseBody> response) { try { Log.d(TAG,"onResponse -- > " + response.body().string()); } catch(IOException e) { e.printStackTrace(); } }
@Override public void onFailure(Call<ResponseBody> call,Throwable t) { Log.d(TAG,"onFailure == > " + t.toString()); } }); }
|
运行结果:
1
| {"success":true,"code":10000,"message":"这是你提交上来的数据:root - admin","data":"8920d231-20e4-46c6-9b41-9812730c4cce"}
|
到这里的话,我们就把@Failed 搞定了,那@FailedMap 呢?
相信聪明的你早就想到了,套路跟前面一样呀!
再来一个,定义接口
1 2 3
| @FormUrlEncoded @POST("/login") Call<ResponseBody> login(@FieldMap Map<String,Object> params);
|
调用代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public void login(View view) { Map<String,Object> params = new HashMap<>(); params.put("password","234123lkjsfa"); params.put("userName","root"); Call<ResponseBody> call = mSobMiniWebInterface.login(params); call.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call,Response<ResponseBody> response) { try { Log.d(TAG,"onResponse -- > " + response.body().string()); } catch(IOException e) { e.printStackTrace(); } }
@Override public void onFailure(Call<ResponseBody> call,Throwable t) { Log.d(TAG,"onFailure == > " + t.toString()); } }); }
|
运行结果
1
| {"success":true,"code":10000,"message":"这是你提交上来的数据:root - 234123lkjsfa","data":"5af51e33-8c9c-4898-8c0e-bf44e0d2fc4f"}
|
Okay 啦,到这里@FiledMap 也搞定了
关于参数的接口
大家再 review 一下吧。
请求头注解我们还没使用上,那要怎么使用呢?
看码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Multipart @POST("/file/upload") Call<FileUploadResult> postFile(@Part MultipartBody.Part file,@Header("token") String token);
@Multipart @POST("/files/upload") Call<FileUploadResult> postFiles(@Part List<MultipartBody.Part> files,@HeaderMap Map<String,String> headers);
@Headers({"token:231231","version:1.0","client:android"}) @Multipart @POST("/file/params/upload") Call<FileUploadResult> postFileWithParams(@PartMap Map<String,Object> params,@Part MultipartBody.Part file);
|
其实我们很少这么用的,假如说,我们是为了用户验证,携带 token 的话,在每个方法都添加,那不是很麻烦吗?
而 Okhttp 是支持拦截器的,我们直接在每次请求的时候,在头部加上 token 就不可以了吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| private void createRetrofit() { OkHttpClient okHttpClient = new OkHttpClient.Builder() .connectTimeout(CONNECT_TIME_OUT,TimeUnit.MILLISECONDS) .addInterceptor(mHeaderInterceptor) .build(); mRetrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build(); }
private Interceptor mHeaderInterceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request().newBuilder() .addHeader("token","204391jawslejqowejqwi") .addHeader("version","1.2.0") .addHeader("client","android铂金版") .build(); return chain.proceed(request); } };
|
PS:注解里的可以覆盖掉拦截器里的内容.