一、亮点介绍:
自从鸿蒙手机版发布以来,我就一直在做移植的工作,将安卓代码移植到鸿蒙系统上。Retrofit是安卓系统上一款优秀的网络请求框架,鸿蒙系统并没有类似的网络请求框架。于是,我决定实现一套鸿蒙版的Retrofit。
蒹葭(JianJia)是一款鸿蒙系统上的网络请求框架,其实就是将安卓的Retrofit移植到鸿蒙系统上,我将鸿蒙版的Retrofit命名为蒹葭(JianJia)。蒹葭不仅能实现Retrofit的功能,还会提供一些Retrofit没有的功能。Retrofit不支持动态替换域名,国内的应用一般都是有多个域名的,蒹葭支持动态替换域名。Retrofit并不能够直接添加拦截器,只能通过okhttp来添加拦截器,蒹葭会支持添加拦截器。
二、Demo编译及效果呈现如下:
注:文档附件在最下面
三、源码:
https://gitee.com/zhongte/JianJia
要想读懂源码,需要具备以下技能。
熟悉okhttp的常见用法
熟悉面向接口编程、反射、泛型、注解
熟悉构造者模式、适配器模式、工厂模式、策略模式、静态代理、动态代理、责任链模式等设计模式
四、用法,用法跟Retrofit一样
蒹葭提供了一系列的注解,在进行网络请求的时候,就需要用到这些注解。
4.1 GET注解
创建接口,在方法里面使用GET注解,GET注解用于标识这是一个GET请求,方法的返回值是Call对象,泛型是ResponseBody,其实泛型也可以是具体的实体对象,这个后面再说。蒹葭如何完成网络请求?使用构造者模式创建jianjia对象,baseUrl就是域名,在创建jianjia对象的时候就必须指定域名。调用create方法来生成接口的实例,调用wan.getBanner().enqueue来执行网络请求,请求成功就会回调onResponse方法,请求失败就会回调onFailure方法
public interface Wan {
@GET("banner/json")
Call<ResponseBody> getBanner();
}
JianJia jianJia = new JianJia.Builder()
.baseUrl("https://www.wanandroid.com")
.build();
Wan wan = jianJia.create(Wan.class);
wan.getBanner().enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
String json = response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
LogUtils.info("yunfei", t.getMessage());
}
});
4.2 BaseUrl注解
国内的应用一般都是有多个域名的,BaseUrl注解可以对某个接口设置单独的域名。
public interface Wan {
@BaseUrl("https://api.apiopen.top")
@GET("getJoke")
Call<ResponseBody> getJoke(@QueryMap Map<String, String> param);
}
4.3 Path注解
Path注解在路径中替换指定的参数值,定义下面的方法。可以看到我们定义了一个getArticle方法,方法接收一个page参数,并且我们的@GET注解中使用{page}声明了访问路径,这里你可以把{page}当做占位符,而实际运行中会通过@Path("page")所标注的参数进行替换。
public interface Wan {
@GET("article/list/{page}/json")
Call<ResponseBody> getArticle(@Path("page") int page);
}
4.4 Query注解
Query注解用于给get请求添加请求参数,被Query注解修饰的参数类型可以是数组、集合、字符串等
public interface Wan {
@GET("wxarticle/list/405/1/json")
Call<ResponseBody> search(@Query("k") String k);
@GET("wxarticle/list/405/1/json")
Call<ResponseBody> search(@Query("k") String... k);
@GET("wxarticle/list/405/1/json")
Call<ResponseBody> search(@Query("k") List<String> k);
}
4.5 QueryMap注解
QueryMap注解以map的形式添加查询参数,被QueryMap注解修饰的参数类型必须是Map对象
public interface Wan {
@GET("wxarticle/list/405/1/json")
Call<ResponseBody> search(@QueryMap Map<String, String> param);
}
4.6 SkipCallbackExecutor注解
在鸿蒙系统下,默认会将服务端的响应回调到主线程,如果在方法上使用SkipCallbackExecutor注解,那就不会将服务端的结果回调到主线程
public interface Wan {
@SkipCallbackExecutor
@GET("wxarticle/list/405/1/json")
Call<ResponseBody> search(@QueryMap Map<String, String> param);
}
4.7 FormUrlEncoded注解和Field注解
FormUrlEncoded注解用于发送一个表单请求,使用该注解必须在方法的参数添加Field注解,被Field注解修饰的参数类型可以是数组、集合、字符串等
public interface Wan {
@POST("user/login")
@FormUrlEncoded
Call<ResponseBody> login(@Field("username") String username, @Field("password") String password);
}
4.8 FormUrlEncoded注解和FieldMap注解
有时候表单的参数会比较多,如果使用Field注解,方法的参数就会比较多,此时就可以使用FieldMap注解,FieldMap注解以map的形式发送一个表单请求。如果被FieldMap注解修饰的参数不是Map类型,就会抛异常。如果Map的键值对为空,也会抛异常。
public interface Wan {
@POST("user/login")
@FormUrlEncoded
Call<ResponseBody> login(@FieldMap Map<String, String> map);
}
4.9 Body注解
服务端会要求端上把json字符串作为请求体发给服务端。此时就可以使用Body注解定义的参数可以直接传入一个实体类,内部会把该实体序列化并将序列化后的结果直接作为请求体发送出去。
如果被Body注解修饰的参数的类型是RequestBody对象,那调用者可以不添加数据转换器,内部会使用默认的数据转换器
如果被Body注解修饰的参数的类型不是RequestBody对象,是一个具体的实体类,那调用者需要自定义一个类,并且继承Converter.Factory
public interface Wan {
/**
* 被Body注解修饰的参数的类型是RequestBody对象,那调用者可以不添加数据转换器,内部会使用默认的数据转换器
*
* @param body
* @return
*/
@POST("user/register")
Call<ResponseBody> register(@Body RequestBody body);
/**
* 被Body注解修饰的参数的类型不是RequestBody对象,是一个具体的实体类,那调用者需要自定义一个类,并且继承Converter.Factory
*
* @param user
* @return
*/
@POST("user/register")
Call<ResponseBody> register(@Body User user);
}
4.10 Url注解
Url注解用于添加接口的完整地址。在Retrofit里面,如果接口的域名与创建retrofit对象指定的域名不相同,那就会使用Url注解来解决问题。在蒹葭里面同样可以使用Url注解来解决问题,但蒹葭还提供了BaseUrl来解决该问题。
public interface Wan {
@GET()
Call<ResponseBody> getArticle(@Url String url);
}
4.11 Headers注解
Headers注解是作用于方法上的注解,用于添加一个或多个请求头。
public interface Wan {
@Headers("Cache-Control: max-age=640000")
@GET("/")
Call<ResponseBody> getArticle(@Url String url);
@Headers({
"X-Foo: Bar",
"X-Ping: Pong"
})
@GET("/")
Call<ResponseBody> getArticle(@Url String url);
}
4.12 Header注解
Header注解是作用于参数上的注解,用于添加请求头
public interface Wan {
@GET()
Call<ResponseBody> foo(@Header("Accept-Language") String lang);
}
4.13 HeaderMap注解
HeaderMap注解是作用于参数上的注解,以map的形式添加请求头,map中每一项的键和值都不能为空,否则会抛异常
public interface Wan {
@GET("/search")
Call<ResponseBody> list(@HeaderMap Map<String, String> headers);
}
五、添加数据转换器
之前我们在接口里面定义方法的时候,方法的返回值时Call对象,泛型是ResponseBody。在这种情况下,服务端返回给端上的数据就会在ResponseBody里面,端上需要手动解析json,将json解析成一个实体类。
其实,我们没必要手动解析json,可以让gson帮我们解析json。蒹葭支持添加数据转换器,在创建对象的时候添加数据转换器,也就是把gson添加进来。在onResponse方法里面就可以直接得到实体类对象了,gson帮我们把json解析成了一个实体类。
public interface Wan {
@GET("banner/json")
Call<Banner> getBanner();
}
JianJia jianJia = new JianJia.Builder()
.baseUrl("https://www.wanandroid.com")
.addConverterFactory(GsonConverterFactory.create())
.build();
Wan wan = jianJia.create(Wan.class);
wan.getBanner().enqueue(new Callback<Banner>() {
@Override
public void onResponse(Call<Banner> call, Response<Banner> response) {
try {
if (response.isSuccessful()) {
Banner banner = response.body();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<Banner> call, Throwable t) {
LogUtils.info("yunfei", t.getMessage());
}
});
六、蒹葭的后续工作
6、1 上传文件也需要使用注解,目前蒹葭还没有上传文件的注解,上传文件的注解正在开发当中。
6、2 蒹葭目前还不支持直接添加拦截器,后续会把拦截器的功能加上。
6、3 网络请求框架的二次封装,网络请求框架的二次封装应当隔离掉第三方网络框架,如果将来有更好的网络框架,或者公司要求使用公司自研的网络框架,那就可以很容易的替换掉之前的网络框架,而不需要大改特改。
6、4 由于疫情原因,我估计得在北京过年了,无法回老家过年。所以可以趁放假的时候,给大家录视频。手把手的叫大家实现自己的网络框架,希望能够帮助大家理解蒹葭的实现原理。当然了,蒹葭的实现原理跟Retrofit的原理是一模一样的。本身蒹葭就是从Retrofit移植过来的,只不过蒹葭可以运行在鸿蒙系统上。
原文链接:https://developer.huawei.com/consumer/cn/forum/topic/0204470884054220049?fid=0101303901040230869
原作者:义薄云天小关羽