Retrofit简介
1 2 Retrofit 是一个Square开发的安卓客户端请求库。其中内部封装了okhttp库。 官方的介绍是使用非常简短 Retrofit使用注解,能够极大的简化网络请求数据的代码;
与其它网络库的对比
1 2 3 4 AndroidAsynHttp - 基于HttpClient作者已停止维护,Android5.0不再使用HttpClient,因此不推荐使用。 Volley - 基于HttpUrlConnection,Google官方推出,只适合轻量级网络交互如数据传输小,不适合大文件下传下载场景;
Retrofit优点
1 API设计简洁易用、注解化配置高度解耦、支持多种解析器、支持Rxjava
Retrofit使用
1.Retrofit开源库、OkHttp网络库、数据解析器集成注册网络权限
1 2 3 4 5 6 //依赖包导入 implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.okhttp3:okhttp:3.14.9' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' //网络权限 <uses-permission android:name="android.permission.INTERNET"/>
2.创建接口设置请求类型与参数
1 2 3 4 5 新建UserInfoModel类和UserMgrService接口 public interface RequestInterface { @GET("login") Call<LoginBean> getCloseTime(@Query("ip") String devideIp); }
请求示例
//假设BASE_URL = “http://192.168.0.1/ “
@Url
1 2 3 //整个地址都要变化,甚至是baseUrl @GET Call<List<Repo>> getData(@Url String user);
@Path
1 2 3 4 //用于替换Url路径中的变量字符。 @GET("weather/{city_name}") Observable<Object> getWeather(@Path("city_name") String city_name); //完整结果:http://192.168.0.1/weather/北京
2.1Get请求
@Query
1 2 3 4 //主要用于Get请求数据,用于拼接在拼接在Url路径后面的查询参数,一个@Query相当于拼接一个参数,多个参数中间用,隔开。 @GET("weather") Observable<WeatherEntity> getWeather(@Query("city") String city); //完整结果:http://192.168.0.1/weather?city=北京
@QueryMap
1 2 3 4 //主要的效果等同于多个@Query参数拼接,主要也用于Get请求网络数据。 @GET("weather/{city_name}") Observable<Object> getWeather(@Path("city_name") String city_name, @QueryMap Map<String, String> queryParams); //完整结果:http://192.168.0.1/weather/北京?user_id=1&user_name=jojo
2.2Post请求
@Filed
1 2 3 4 5 6 //@Field的用法类似于@Query,于拼接在拼接在Url路径后面的查询参数。 @FormUrlEncoded //使用@Field时记得添加@FormUrlEncoded @POST("comment") void doComments(@Field("content") String content, @Field("user_id") String user_id); //完整结果:http://192.168.0.1/comment //body参数:{content":"我是评论","user_id":"1001"}
@FieldMap
1 2 3 4 5 6 7 //用法类似于@QueryMap,主要用于Map拼接多个参数 @FormUrlEncoded @POST("comment") void doComments(@FieldMap Map<String, String> paramsMap ); // HashMap<String, String> hashMap = new HashMap<>(); // hashMap.put("content","我是评论"); // hashMap.put("user_id","1001");
@Body
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 //例1 @POST("comment") void doComments(@Body Object reqBean); //例2 @POST("comment") void doComments(@Body List<Object> requestList); //例3 文件上传 @POST("upload/") Observable<Object> uploadFile(@Body RequestBody requestBody); //只不过文件上传传入的是RequestBody类型,下面是构建RequestBody的方式: File file = new File(mFilePath); //mImagePath为上传的文件绝对路径 //构建body RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file)) .build();
2.3Put请求
1 2 3 @PUT("comment/{comment_id}") void comment(@Path("comment_id") String comment_id); //完整结果:http://192.168.0.1/comment/88
1 2 3 @PUT("comment/{comment_id}") void comment(@Path("comment_id") String comment_id @Query("user_id") String user_id); //完整结果:http://192.168.0.1/comment/88?user_id=1001
1 2 3 4 5 6 7 8 9 10 11 12 13 14 //加body参数:{"content":"我是评论","type":"1"} //此类请求的应用场景:适合于body中需要传入多个请求参数,这样可以将多个请求的参数字段封装到一个实体中,这样就不用写多个@Filed了 public class RequestBean { public String content; public String type; //实际中可能还有多个请求字段 } ... RequestBean requestBean = new RequestBean(); requestBean .content = "我是评论"; requestBean .type = "1"; ... @PUT("comment/{comment_id}") void comment(@Path("comment_id") String comment_id @Query("user_id") String user_id @Body RequestBean reqBean);
2.4Delete
1 2 3 @DELETE("comment/{comment_id}") void comment(@Path("comment_id") String comment_id); //假如删除评论:http://192.168.0.1/comment/88
综上所述,可以归纳出上面几个注解的用法:
1 2 3 4 5 6 @Path : 请求的参数值直接跟在URL后面时,用@Path配置 @Query: 表示查询参数,以?key1=value1&key2=value2的形式跟在请求域名后面时使用@Query @QueryMap :以map的方式直接传入多个键值对的查询参数 @Field: 多用于post请求中表单字段,每个@Field后面,对应一对键值对。 @FieldMap :以map的方式传入多个键值对,作为body参数 @Body: 相当于多个@Field,以对象的形式提交
注意:Filed和FieldMap需要FormUrlEncoded结合使用,否则会抛异常!
3.创建Retrofit对象、设置数据解析器
1 2 Retrofit retrofit = new Retrofit.Builder().baseUrl("http://149.0.171.247:8088/") .addConverterFactory(GsonConverterFactory.create().build();
baseUrl:
1 2 3 这里的baseUrl是自己访问的Url的基类地址,加上刚才@GET(“login”)中的login才是我们真正要访问的地址, 因为使用了@Query("ip"),所以最终的访问地址为http://149.0.171.247:8088/login?ip=devideIp,此处的ip为自己传入的参数。 注意: baseUrl必须要以/结尾!!!
4.生成接口对象
1 RequestInterface service= retrofit.create(RequestInterface.class);
5.调用接口方法返回Call对象
1 Call<LoginBean> call=service.login("zhangsan""123456")
6.发送请求(同步、异步)
1 2 3 4 5 6 7 8 9 10 11 12 13 同步:调用Call对象的execute(),返回结果的响应体 异步:调用Call对象的enqueue0,参数是一个回调 call.enqueue(new Callback<LoginBean>() { @Override public void onResponse(Call<LoginBean> call, Response<LoginBean> response) { } @Override public void onFailure(Call<LoginBean> call, Throwable t) { } });
问题:
1 2 3 4 5 6 7 使用时别忘了申请网络权限哦,使用时可能会遇到以下问题: CLEARTEXT communication to mock-api.com not permitted by network security policy 这是因为Android P不允许明文访问,而前面的mock地址是http开头的, 解决办法是在AndroidManifest中的application内加入下面这段代码即可: android:usesCleartextTraffic="true"
一、Retrofit工具类的封装(核心类)
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 53 54 /** * Retrofit工具类 */ public class RetrofitUtils { public static final String BASE_URL = "http://XXX"; /** * 超时时间 */ public static final int TIMEOUT = 60; private static volatile RetrofitUtils mInstance; private Retrofit mRetrofit; public static RetrofitUtils getInstance() { if (mInstance == null) { synchronized (RetrofitUtils.class) { if (mInstance == null) { mInstance = new RetrofitUtils(); } } } return mInstance; } private RetrofitUtils() { initRetrofit(); } /** * 初始化Retrofit */ private void initRetrofit() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); // 设置超时 builder.connectTimeout(TIMEOUT, TimeUnit.SECONDS); builder.readTimeout(TIMEOUT, TimeUnit.SECONDS); builder.writeTimeout(TIMEOUT, TimeUnit.SECONDS); OkHttpClient client = builder.build(); mRetrofit = new Retrofit.Builder() // 设置请求的域名 .baseUrl(BASE_URL) // 设置解析转换工厂,用自己定义的 .addConverterFactory(ResponseConvert.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(client) .build(); } /** * 创建API */ public <T> T create(Class<T> clazz) { return mRetrofit.create(clazz); } }
代码很简单,创建后台请求接口,调用create即可。
二、Converter.Factory的封装
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 53 54 55 56 57 /** * 自定义Gson解析转换 */ public class ResponseConvert extends Converter.Factory { public static ResponseConvert create() { return new ResponseConvert(); } /** * 转换的方法 */ @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return new BodyConverter<>(type); } private class BodyConverter<T> implements Converter<ResponseBody, T> { private Gson gson; private Type type; public BodyConverter(Type type) { this.type = type; gson = new GsonBuilder() .registerTypeHierarchyAdapter(List.class, new ListTypeAdapter()) .create(); } @Override public T convert(ResponseBody value) throws IOException { String json = value.string(); return gson.fromJson(json, type); } } /** * 空列表的转换 */ private static class ListTypeAdapter implements JsonDeserializer<List<?>> { @Override public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { if (json != null && json.isJsonArray()) { JsonArray array = json.getAsJsonArray(); Type itemType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0]; java.util.List list = new ArrayList<>(); for (int i = 0; i < array.size(); i++) { JsonElement element = array.get(i); Object item = context.deserialize(element, itemType); list.add(item); } return list; } else { //和接口类型不符,返回空List return Collections.EMPTY_LIST; } } } }
三、Retrofit网络请求基类的封装
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 /** * 后台统一接口API */ public interface ServerApi { // 联系人编辑 @POST(URLS.LOGIN) Observable<ResponseBean<LoginBean>> login(@Body RequestBody requestBody); } /** * 请求网络业务的基类,AppPresenterr 的封装 */ public class AppPresenter { protected ServerApi mApi = RetrofitUtils.getInstance().create(ServerApi.class); private static final Gson gson = new Gson(); /** * 1. 转换 * 统一处理一些动作 */ public static <T> void convert(Observable<ResponseBean<T>> observable, Observer<T> observer) { observable .map(new Function<ResponseBean<T>, T>() { @Override public T apply(ResponseBean<T> httpResult) throws Exception { // 打印响应的对象 LogUtils.object(httpResult); // TODO 实际开发的时候统一处理一些东西 if (httpResult == null || httpResult.head == null) { throw new RuntimeException("请求数据异常"); } else if (!"1".equals(httpResult.head.bcode)) { throw new RuntimeException(httpResult.head.bmessage); } return httpResult.data; } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(observer); } /** * 2. 执行的方法 */ public static <T> void execute(Observable<ResponseBean<T>> observable, Observer<ResponseBean<T>> observer) { observable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(observer); } /** * 3.请求数据是Json,Json转成RequestBody */ public static RequestBody createRequestBody(Object obj) { RequestBean bean = new RequestBean<>(obj); String json = gson.toJson(bean); // 打印请求的Json LogUtils.json(json); RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json); return body; } } 有三个通用的方法: 1.convert方法,转换、统一处理网络请求,将公共处理部分放在这方法里面。 2.execute方法,只执行,不做任何处理操作,适用于一些不能统一处理的接口。 3.createRequestBody方法,就是统一创建请求的RequestBody。
四、具体网络请求业务类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 /** * 登录的业务类 */ public class LoginPresenter extends AppPresenter { /** * 登录的接口 */ public void login(LoginData data, Observer<LoginBean> observer) { Observable<ResponseBean<LoginBean>> login = mApi.login(createRequestBody(data)); // 转换 convert(login, observer); } }
五、测试和使用
1 2 3 4 5 6 7 8 9 10 11 12 13 /** * 调用登录接口 */ public void open(View view) { LoginData loginData = new LoginData("135****5219", "12***56"); presenter.login(loginData, new DialogObserver<LoginBean>(getAppActivity()) { @Override public void onNext(LoginBean data) { // TODO 做登录的成功的操作 Toast.makeText(getAppActivity(), "" + data.userInfo.nickName, Toast.LENGTH_SHORT).show(); } }); }
六、补充,对于Observer需要再次封装。
1 2 3 4 1.如调用登录要显示Dialog 2.如Activity销毁要取消请求。 3.结合加载数据各种状态页面,如:加载中、加载失败、网络异常、数据为空等等 这些都是可以统一封装在Observer<T > 里面。
工具类原文:https://www.jianshu.com/p/7f843d65a7c6