暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

鸿蒙版Retrofit,​久等了!

鸿蒙技术社区 2021-05-08
285

蒹葭(JianJia)是一款鸿蒙系统上的网络请求框架,其实就是将安卓的 Retrofit 移植到鸿蒙系统上,我将鸿蒙版的 Retrofit 命名为蒹葭(JianJia)。


蒹葭不仅能实现 Retrofit 的功能,还会提供一些 Retrofit 没有的功能。


Retrofit 不支持动态替换域名,国内的应用一般都是有多个域名的,蒹葭支持动态替换域名。


01

源码


源码地址:
https://gitee.com/zhongte/JianJia


要想读懂源码,需要具备以下技能:

  • 熟悉 okhttp 的常见用法。

  • 熟悉面向接口编程、反射、泛型、注解。

  • 熟悉构造者模式、适配器模式、工厂模式、策略模式、静态代理、动态代理、责任链模式等设计模式。


02

混淆


如果项目开启了混淆,请在 proguard-rules.pro 添加如下的代码
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
-dontwarn javax.annotation.**
-keepattributes Signature, InnerClasses, EnclosingMethod, Exceptions
# 蒹葭
-dontwarn poetry.jianjia.**
-keep class poetry.jianjia.** { *; }
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
-keepclassmembers,allowshrinking,allowobfuscation interface * {
    @poetry.jianjia.http.* <methods>;
}

# OkHttp3
-dontwarn okhttp3.logging.**
-keep class okhttp3.internal.**{*;}
-dontwarn okio.**

# gson
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keepattributes *Annotation*
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}
# 在我的示例代码中,com.poetry.jianjia.bean这个包下面的类实现了Serialized接口,
# 实现了Serialized接口的类不能被混淆,请把com.poetry.jianjia.bean这个包名替换成你自己的包名
-keep class com.poetry.jianjia.bean.**{*;}


关于混淆,可以查看《鸿蒙代码配置混淆》


03

添加依赖


在项目根目录下的 build.gradle 文件中添加 mavenCentral() 仓库。


打开项目根目录下的 build.gradle 文件,在 build.gradle 文件的 repositories 闭包下面添加 mavenCentral():
buildscript {
    repositories {
        // 添加maven中央仓库
        mavenCentral()
        maven {
            url 'https://mirrors.huaweicloud.com/repository/maven/'
        }
        maven {
            url 'https://developer.huawei.com/repo/'
        }
        maven {
            url 'http://maven.aliyun.com/nexus/content/repositories/central/'
        }
        jcenter()
    }
    dependencies {
        classpath 'com.huawei.ohos:hap:2.4.2.5'
        classpath 'com.huawei.ohos:decctest:1.0.0.6'
    }
}

allprojects {
    repositories {
        // 添加maven中央仓库
        mavenCentral()
        maven {
            url 'https://mirrors.huaweicloud.com/repository/maven/'
        }
        maven {
            url 'https://developer.huawei.com/repo/'
        }
        maven {
            url 'http://maven.aliyun.com/nexus/content/repositories/central/'
        }
        jcenter()
    }
}


打开 entry 目录下的 build.gradle 文件中,在 build.gradle 文件中的 dependencies 闭包下添加下面的依赖:
// 蒹葭的核心代码
implementation 'io.gitee.zhongte:jianjia:1.0.0'
// 数据转换器,数据转换器使用gson来帮我们解析json,不需要我们手动解析json
implementation 'io.gitee.zhongte:converter-gson:1.0.0'
implementation "com.google.code.gson:gson:2.8.2"
// 日志拦截器,通过日志拦截器可以看到请求头、请求体、响应头、响应体
implementation 'com.squareup.okhttp3:logging-interceptor:3.7.0'


在配置文件中添加如下的权限:
ohos.permission.INTERNET


04

具体用法,用法跟 retrofit 一样


蒹葭提供了一系列的注解,在进行网络请求的时候,就需要用到这些注解。


①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());
    }
});


②BaseUrl 注解


国内的应用一般都是有多个域名的,BaseUrl 注解可以对某个接口设置单独的域名。
public interface Wan {

    @BaseUrl("https://api.apiopen.top")
    @GET("getJoke")
    Call<ResponseBody> getJoke(@QueryMap Map<StringString> param);

}


③Path 注解


Path 注解在路径中替换指定的参数值,定义下面的方法。


可以看到我们定义了一个 getArticle 方法,方法接收一个 page 参数,并且我们的 @GET 注解中使用 {page} 声明了访问路径。


这里你可以把 {page} 当做占位符,而实际运行中会通过 @Path("page") 所标注的参数进行替换。
public interface Wan {

    @GET("article/list/{page}/json")
    Call<ResponseBody> getArticle(@Path("page") int page);

}


④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);

}


⑤QueryMap 注解


QueryMap 注解以 map 的形式添加查询参数,被 QueryMap 注解修饰的参数类型必须是 Map 对象。
public interface Wan {

    @GET("wxarticle/list/405/1/json")
    Call<ResponseBody> search(@QueryMap Map<StringString> param);

}


⑥SkipCallbackExecutor 注解


在鸿蒙系统上,蒹葭默认会将服务端的响应回调到主线程,如果在方法上使用 SkipCallbackExecutor 注解,那就不会将服务端的结果回调到主线程。
public interface Wan {
    @SkipCallbackExecutor
    @GET("wxarticle/list/405/1/json")
    Call<ResponseBody> search(@QueryMap Map<StringString> param);

}


⑦FormUrlEncoded 注解和 Field 注解


FormUrlEncoded 注解用于发送一个表单请求,使用该注解必须在方法的参数添加 Field 注解,被 Field 注解修饰的参数类型可以是数组、集合、字符串等。
public interface Wan {
    @POST("user/login")
    @FormUrlEncoded
    Call<ResponseBody> login(@Field("username") String username, @Field("password") String password);

}


⑧FormUrlEncoded 注解和 FieldMap 注解


有时候表单的参数会比较多,如果使用 Field 注解,方法的参数就会比较多,此时就可以使用 FieldMap 注解,FieldMap 注解以键值对的形式发送一个表单请求。


如果被 FieldMap 注解修饰的参数不是 Map 类型,就会抛异常。如果 Map 的键值对为空,也会抛异常。
public interface Wan {
    @POST("user/login")
    @FormUrlEncoded
    Call<ResponseBody> login(@FieldMap Map<StringString> map);

}


⑨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);
}


⑩Url 注解


Url 注解用于添加接口的完整地址。在 Retrofit 里面,如果接口的域名与创建 retrofit 对象指定的域名不相同,那就会使用 Url 注解来解决问题。


在蒹葭里面同样可以使用 Url 注解来解决问题,但蒹葭还提供了 BaseUrl 来解决该问题。
public interface Wan {

    @GET()
    Call<ResponseBody> getArticle(@Url String url);

}


⑪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);

}


⑫Header 注解


Header 注解是作用于参数上的注解,用于添加请求头。
public interface Wan {

    @GET()
   Call<ResponseBody> foo(@Header("Accept-Language") String lang);

}


⑬HeaderMap 注解


HeaderMap 注解是作用于参数上的注解,以 map 的形式添加请求头,map 中每一项的键和值都不能为空,否则会抛异常。
public interface Wan {

    @GET("/search")
   Call<ResponseBody> list(@HeaderMap Map<StringString> headers);

}


⑭添加数据转换器


之前我们在接口里面定义方法的时候,方法的返回值时 Call 对象,泛型是 ResponseBody。


在这种情况下,服务端返回给端上的数据就会在 ResponseBody 里面,端上需要手动解析 json,将 json 解析成一个实体类。


其实,我们没必要手动解析 json,可以让 gson 帮我们解析 json。蒹葭支持添加数据转换器,在创建对象的时候添加数据转换器,也就是把 gson 添加进来。


在 onResponse 方法里面就可以直接得到实体类对象了,gson 帮我们把 json 解析成了一个实体对象。


首先在 build.gradle 文件添加数据转换器的依赖:

// 数据转换器,数据转换器使用gson来帮我们解析json,不需要我们手动解析json
implementation 'io.gitee.zhongte:converter-gson:1.0.0'
implementation "com.google.code.gson:gson:2.8.2"


在代码中使用数据转换器:
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()) {
                // json已经被解析成banner对象了
                Banner banner = response.body();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onFailure(Call<Banner> call, Throwable t) {
        LogUtils.info("yunfei", t.getMessage());
    }
});


05

总结


本文介绍了蒹葭的用法,蒹葭的原理跟 retrofit 是一样的,有兴趣的同学可以去看下源码。


👇点击关注鸿蒙技术社区👇

专注开源技术,共建鸿蒙生态


“阅读原文”了解更多

文章转载自鸿蒙技术社区,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论