鸿蒙(HarmonyOS)版Retrofit网络请求框架

注意

从3.0开始,官方已经废弃Java了。鸿蒙最终选择了高效简洁的JS/eTS语言为主要开发语言,即从3.0 Beta开始,鸿蒙将重心主要放在JS类Web式、eTS声明式两大类开发范式,兼容C/C++类。Java类API不再演进,但是会持续运营维护。我还是会维护该库,但推荐大家去学JS/eTS。

一、介绍

蒹葭(JianJia)是一款鸿蒙系统上的网络请求框架,其实就是将安卓的Retrofit移植到鸿蒙系统上,我将鸿蒙版的Retrofit命名为蒹葭(JianJia)。蒹葭不仅能实现Retrofit的功能,还会提供一些Retrofit没有的功能。Retrofit不支持动态替换域名,国内的应用一般都是有多个域名的,蒹葭支持动态替换域名。

二、添加依赖

2、1 在项目根目录下的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()
    }
}

2、2 打开entry目录下的build.gradle文件中,在build.gradle文件中的dependencies闭包下添加下面的依赖。

// 蒹葭的核心代码
implementation 'io.gitee.zhongte:jianjia:1.0.3'
// 数据转换器,数据转换器使用gson来帮我们解析json,不需要我们手动解析json
implementation 'io.gitee.zhongte:converter-gson:1.0.2'
implementation "com.google.code.gson:gson:2.8.2"
// 日志拦截器,通过日志拦截器可以看到请求头、请求体、响应头、响应体
implementation 'com.squareup.okhttp3:logging-interceptor:3.7.0'
// 如果服务端返回的json有特殊字符,比如中文的双引号,gson在解析的时候会对特殊字符进行转义
// 这时就需要将转义后的字符串进行反转义,commons-lang可以对特殊字符进行转义和反转义
implementation 'commons-lang:commons-lang:2.6'

2、3 在配置文件中添加如下的权限

ohos.permission.INTERNET

三、用法

创建接口,在方法里面使用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());
    }
});

在上面的示例代码中,接口里面定义的方法的方法的返回值是Call对象,泛型是ResponseBody。 在这种情况下,服务端返回给端上的数据就会在ResponseBody里面,端上需要手动解析json, 将json解析成一个实体类。其实,我们没必要手动解析json,可以让gson帮我们解析json。 蒹葭支持添加数据转换器,在创建对象的时候添加数据转换器,也就是把gson添加进来。 在onResponse方法里面就可以直接得到实体类对象了,gson帮我们把json解析成了一个实体对象。 如下代码,在onResponse方法里面调用response.body()body方法直接返回了一个banner对象。 gson帮我们把服务端返回的json解析成banner对象,这样我们就可以直接使用banner对象,不需要我们手动解析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()) {
                // 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());
    }
});

四、示例代码效果

在源码的entry目录下提供了示例代码,代码运行结果请查看上图。 上图显示的一个网页上的内容,端上使用蒹葭网络库访问该网站提供的接口来 获取首页的文章列表,当请求成功后,将文章列表显示在页面上。 目前只获取了第一页的文章列表,有兴趣的同学可以自行实现分页加载。 demo刚运行的时候页面白屏,那是因为此时正在请求网络,正常情况下,应当加个进度条, 只不过示例中没有进度条。在运行示例代码时,如果在安装的时候出现INSTALL_PARSE_FAILED_USESDK_ERROR, 请把config.json文件中的"releaseType": "Beta1"删除。

五、示例代码讲解

5、1 在示例代码中的com.poetry.jianjia.net包下面创建了如下的接口,把所有的请求放在一个接口里面即可, 没必要创建多个接口类。

/**
 * @author 裴云飞
 * @date 2021/1/23
 */
public interface Wan {

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

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

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

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

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

    @GET("article/list/0/json")
    Call<ResponseBody> getArticle(@QueryMap Map<String, String> param);

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

    @POST("user/login")
    @FormUrlEncoded
    Call<ResponseBody> login(@Field("username") String username, @Field("password") String password);

    @POST("user/login")
    @FormUrlEncoded
    Call<ResponseBody> login(@FieldMap Map<String, String> map);

    @GET("banner/json")
    Call<Banner> getBanner();
}

5、2 创建jianjia对象,整个项目只需一个jianjia对象即可,如何确保只有一个 jianjia对象?当代码运行起来后,首先会创建AbilityPackage对象, 调用AbilityPackageonInitialize方法,AbilityPackage执行完成后 才会启动AbilityAbilityPackage就是一个全局的单例,所以在 AbilityPackage里面创建的对象就是一个单例对象。只需在AbilityPackage 里面创建jianjia对象,就能确保整个项目只有一个jianjia对象。 AbilityPackage这个类不需要手动创建,在创建的项目的时候, 编译器会自动创建一个继承于AbilityPackage的类。

public class BaseApplication extends AbilityPackage {

    private static BaseApplication instance;

    private JianJia mJianJia;
    private Wan mWan;

    public static BaseApplication getInstance() {
        return instance;
    }

    /**
     * 获取全局的蒹葭对象
     *
     * @return 全局的蒹葭对象
     */
    public JianJia getJianJia() {
        return mJianJia;
    }

    /**
     * 获取全局的接口实例对象
     *
     * @return 全局的接口实例对象
     */
    public Wan getWan() {
        return mWan;
    }

    @Override
    public void onInitialize() {
        super.onInitialize();
        instance = this;
        // 创建全局的蒹葭对象
        mJianJia = new JianJia.Builder()
                .baseUrl("https://www.wanandroid.com")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        mWan = mJianJia.create(Wan.class);
    }
}

如上代码,BaseApplication继承AbilityPackage,在onInitialize 方法创建全局的蒹葭对象。同时,整个项目只创建了一个接口类,所以可以在创建完蒹葭对象后 直接调用蒹葭的create方法来创建接口的实例对象。其它地方只需要通过下面的方式即可获取蒹葭对象和接口实例对象。

// 获取全局的蒹葭对象
BaseApplication.getInstance().getJianJia();
// 获取全局的接口实例对象
BaseApplication.getInstance().getWan();

5、3 在MainAbilitySlice里面添加ListContainer,关于ListContainer的用法,请查看 官方文档 ,这里不过多介绍了。接着调用getHomeArticle方法请求服务器,getHomeArticle方法会获取在AbilityPackage里面创建在接口实例对象来执行网络请求, 请求成功后调用setHomeArticle方法来刷新页面。

public class MainAbilitySlice extends AbilitySlice {

    private ListContainer mListContainer;
    private HomeArticleProvider mHomeArticleProvider;
    List<Article.Data.Datas> mDatas;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main);
        mListContainer = (ListContainer) findComponentById(ResourceTable.Id_list);
        mDatas = new ArrayList<>();
        mHomeArticleProvider = new HomeArticleProvider(this, mDatas);
        mListContainer.setItemProvider(mHomeArticleProvider);
        // 从服务端获取数据
        getHomeArticle();
    }

    /**
     * 从服务端获取数据
     */
    public void getHomeArticle() {
        BaseApplication.getInstance().getWan().getHomeArticle(0).enqueue(new Callback<Article>() {
            @Override
            public void onResponse(Call<Article> call, Response<Article> response) {
                if (response.isSuccessful()) {
                    // 请求成功
                    setHomeArticle(response.body());
                }
            }

            @Override
            public void onFailure(Call<Article> call, Throwable t) {
                // 请求失败
                LogUtils.info("yunfei", t.getMessage());
            }
        });
    }

    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }

    public void setHomeArticle(Article article) {
        if (article == null || article.data == null || article.data.datas == null) {
            return;
        }
        mDatas.addAll(article.data.datas);
        // 刷新列表
        mHomeArticleProvider.notifyDataChanged();
    }

}

5、4 解决转义字符问题。如果服务端返回的json有特殊字符,比如中文的双引号。 gson在解析的时候会对特殊字符进行转义,这时就需要将转义后的字符串进行反转义。如下图所示

如何将转义后的字符串进行反转义?commons-lang这个库可以将转义后的字符串进行反转义 在build.gradle文件添加下的依赖

// commons-lang可以对特殊字符进行转义和反转义
implementation 'commons-lang:commons-lang:2.6'

调用StringEscapeUtilsunescapeHtml方法,如果字符串中没有转义字符,unescapeHtml方法 会直接返回原字符串,否则会对字符串进行反转义。具体的代码可查看示例代码中的HomeArticleProvider类。

// json里面有一些特殊符号,特殊符号会被gson转义,
// StringEscapeUtils可以对转义的字符串进行反转义
String title = StringEscapeUtils.unescapeHtml(data.title);
componentHolder.title.setText(title);

反转义之后,特殊字符正常显示

六、获取请求和响应日志

6、1 可以通过charles、fiddler等抓包工具来查看到完整的请求和响应信息。关于抓包,可以自行搜索相关文章。

6、2 使用拦截器打印日志,添加下面的依赖

implementation 'com.squareup.okhttp3:logging-interceptor:3.7.0'

给OkHttp添加拦截器。

// 创建日志拦截器
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
// 为OKHTTP添加日志拦截器
OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .addInterceptor(logging)
    .build();
// 创建全局的蒹葭对象
mJianJia = new JianJia.Builder()
    // 使用自定义的okHttpClient对象
    .callFactory(okHttpClient)
    .baseUrl("https://www.wanandroid.com")
    .addConverterFactory(GsonConverterFactory.create())
    .build();
mWan = mJianJia.create(Wan.class);

经过上面的配置,就可以打印日志了

七、混淆

如果项目开启了混淆,请在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.**{*;}

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ......

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ......

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ......

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题

2.性能优化方向

3.架构方向

4.鸿蒙开发系统底层方向

5.鸿蒙音视频开发方向

6.鸿蒙车载开发方向

7.鸿蒙南向开发方向

相关推荐
sanzk9 分钟前
华为鸿蒙应用开发
华为·harmonyos
热爱跑步的恒川2 小时前
【论文复现】基于图卷积网络的轻量化推荐模型
网络·人工智能·开源·aigc·ai编程
云飞云共享云桌面3 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
SoraLuna4 小时前
「Mac畅玩鸿蒙与硬件28」UI互动应用篇5 - 滑动选择器实现
macos·ui·harmonyos
音徽编程5 小时前
Rust异步运行时框架tokio保姆级教程
开发语言·网络·rust
ClkLog-开源埋点用户分析6 小时前
ClkLog企业版(CDP)预售开启,更有鸿蒙SDK前来助力
华为·开源·开源软件·harmonyos
mg6686 小时前
鸿蒙系统的优势 开发 环境搭建 开发小示例
华为·harmonyos
模拟IC攻城狮6 小时前
华为海思招聘-芯片与器件设计工程师-模拟芯片方向- 机试题-真题套题题目——共8套(每套四十题)
嵌入式硬件·华为·硬件架构·芯片
lqj_本人6 小时前
鸿蒙next选择 Flutter 开发跨平台应用的原因
flutter·华为·harmonyos
lqj_本人6 小时前
使用 Flutter 绘制一个棋盘
harmonyos