本期我們為大家帶來的是開發者裴云飛投稿的“HarmonyOS網絡請求框架實現”,這個網絡請求框架被命名為“蒹葭(JianJia)”。其原理是將Retrofit移植到HarmonyOS上,同時還實現一些Retrofit所不具備的功能。 比如:Retrofit不支持動態替換域名。眾所周知,國內的應用一般都是有多個域名的,蒹葭的一大優勢就是支持動態替換域名。
如想了解更多內容,可以直接復制鏈接看源碼哦:https://gitee.com/zhongte/JianJia 接下來我們將挑選幾個關鍵點做解釋,希望能給你的HarmonyOS開發之旅帶來一些啟發~ 在敲代碼之前,需要大家下載安裝“Huawei DevEco Studio”,如有疑問,可參考官網指南。
● Huawei DevEco Studio安裝指南: https://developer.harmonyos.com/cn/docs/documentation/doc-guides/software_install-0000001053582415 為了保證順利使用,我們先來看一下使用前的一些配置,這里我們主要介紹兩個方面配置,一個是針對代碼混淆的處理,一個是添加依賴。
01
使用前的配置
1.代碼混淆處理
如果項目開啟了代碼混淆,請在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.**{*;}
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.ohos2.4.2.5’ classpath ‘com.huawei.ohos1.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)在build.gradle文件的dependencies閉包下添加依賴
打開entry目錄下的build.gradle文件,在build.gradle文件中的dependencies閉包下添加下面的依賴。
// 蒹葭的核心代碼implementation ‘io.gitee.zhongte1.0.1’// 數據轉換器,數據轉換器使用gson來幫我們解析json,不需要我們手動解析jsonimplementation ‘io.gitee.zhongte1.0.1’implementation “com.google.code.gson2.8.2”// 日志攔截器,通過日志攔截器可以看到請求頭、請求體、響應頭、響應體implementation ‘com.squareup.okhttp33.7.0’// 如果服務端返回的json有特殊字符,比如中文的雙引號,gson在解析的時候會對特殊字符進行轉義// 這時就需要將轉義后的字符串進行反轉義,commons-lang可以對特殊字符進行轉義和反轉義implementation ‘commons-lang2.6’
3)在配置文件中添加以下權限
ohos.permission.INTERNET
02
用法
蒹葭與Retrofit用法相同,并且蒹葭提供一系列的注解。“注解”可以看作是對一個類/方法的一個擴展的模版,每個類/方法按照注解類中的規則,來為類/方法注解不同的參數,在用到的地方可以得到不同的類/方法中注解的各種參數與值。在進行網絡請求的時候,就需要用到這些注解,下文將介紹蒹葭中常用和需要注意的注解。
1.GET注解
創建接口,在方法里面使用GET注解,GET注解用于標識這是一個GET請求,方法的返回值是Call對象,泛型是ResponseBody,其實泛型也可以是具體的實體對象,我們將在下文“16.添加數據轉換器“中詳說。
蒹葭如何完成網絡請求?使用構造者模式創建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()); }});
2. BaseUrl注解
國內的應用一般都是有多個域名的,BaseUrl注解可以對某個接口設置單獨的域名。
public interface Wan { @BaseUrl(“https://api.apiopen.top”) @GET(“getJoke”) Call《ResponseBody》 getJoke(@QueryMap Map《String, String》 param);}
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.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);}
5.QueryMap注解
QueryMap注解以map的形式添加查詢參數,被QueryMap注解修飾的參數類型必須是Map對象。
public interface Wan {
@GET(“wxarticle/list/405/1/json”) Call《ResponseBody》 search(@QueryMap Map《String, String》 param);}
6.SkipCallbackExecutor注解
在HarmonyOS中,默認將服務端的響應回調到主線程,如果在方法上使用SkipCallbackExecutor注解,就不會將服務端的結果回調到主線程。
public interface Wan { @SkipCallbackExecutor @GET(“wxarticle/list/405/1/json”) Call《ResponseBody》 search(@QueryMap Map《String, String》 param);}
7.FormUrlEncoded注解和Field注解
“FormUrlEncoded注解”用于發送一個表單請求,使用該注解必須在方法的參數中添加Field注解,被Field注解修飾的參數類型可以是數組、集合、字符串等。
public interface Wan { @POST(“user/login”) @FormUrlEncoded Call《ResponseBody》 login(@Field(“username”) String username, @Field(“password”) String password);}
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);}
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);}
10.Url注解
Url注解用于添加接口的完整地址。在Retrofit里面,如果接口的域名與創建retrofit對象指定的域名不相同,就會使用Url注解來解決問題。在蒹葭里面不但可以使用Url注解來解決問題,還提供了BaseUrl來解決該問題。
public interface Wan {
@GET() Call《ResponseBody》 getArticle(@Url String url);
}
11.Header注解
Header注解是作用于參數上的注解,用于添加請求頭。
public interface Wan {
@GET() Call《ResponseBody》 foo(@Header(“Accept-Language”) String lang);
}
12.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);}
13.HeaderMap注解
HeaderMap注解是作用于參數上的注解,以Map的形式添加請求頭,Map中每一項的鍵和值都不能為空,否則會報異常。
public interface Wan {
@GET(“/search”) Call《ResponseBody》 list(@HeaderMap Map《String, String》 headers);
}
14. Multipart注解和Part注解
Multipart注解和Part注解用于上傳單個文件。 在配置文件添加下面兩個權限,這兩個權限需要動態申請。
ohos.permission.READ_MEDIAohos.permission.WRITE_MEDIA
定義一個upload方法,方法參數使用Part注解修飾,參數類型必須是MultipartBody.Part。
public interface Wan {
/** * 上傳文件,需要使用Multipart注解和Part注解 * * @param photo 本地文件的路徑 * @return */ @Multipart @POST() Call《ResponseBody》 upload(@Part MultipartBody.Part photo);}// 文件路徑File file = new File(getExternalCacheDir(), “icon.png”);// 創建請求體對象RequestBody photoBody = RequestBody.create(MediaType.parse(“image/png”), file);// MultipartBody.Part photo = MultipartBody.Part.createFormData(“photos”, “icon.png”, photoBody);
JianJia jianJia = new JianJia.Builder() .baseUrl(“https://www.wanandroid.com”) .build();
Wan wan = jianJia.create(Wan.class);// 上傳文件wan.upload(photo).enqueue(new Callback《ResponseBody》() { @Override public void onResponse(Call《ResponseBody》 call, Response《ResponseBody》 response) { // 上傳成功 }
@Override public void onFailure(Call《ResponseBody》 call, Throwable t) { LogUtils.info(“yunfei”, t.getMessage()); }});
15.PartMap注解和Multipart注解
PartMap注解和Multipart注解用于上傳多個文件在配置文件添加下面兩個權限,這兩個權限需要動態申請:
ohos.permission.READ_MEDIAohos.permission.WRITE_MEDIA
被PartMap注解修飾的Map,Map的第一個泛型必須是String:
public interface Wan {
/** * 使用PartMap注解上傳多個文件 * * @param params 第一個泛型必須是String * @return */ @Multipart @POST() Call《ResponseBody》 upload(@PartMap Map《String, RequestBody》 params);}File photoFile = new File(getExternalCacheDir(), “photo.png”);RequestBody photoBody = RequestBody.create(MediaType.parse(“image/png”), photoFile);
File avatarFile = new File(getExternalCacheDir(), “avatar.png”);RequestBody avatarBody = RequestBody.create(MediaType.parse(“image/png”), avatarFile);Map《String, RequestBody》 photos = new HashMap《》();photos.put(“photo”, photoBody);photos.put(“avatar”, avatarBody);
JianJia jianJia = new JianJia.Builder() .baseUrl(“https://www.wanandroid.com”) .build();
Wan wan = jianJia.create(Wan.class);// 上傳文件wan.upload(photos).enqueue(new Callback《ResponseBody》() { @Override public void onResponse(Call《ResponseBody》 call, Response《ResponseBody》 response) { // 上傳成功 }
@Override public void onFailure(Call《ResponseBody》 call, Throwable t) { LogUtils.info(“yunfei”, t.getMessage()); }});
16.添加數據轉換器
之前我們在接口里面定義方法(GET注解中)的時候,方法的返回值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()); }});
03
Demo演示
● 示例中的網頁地址:
https://www.wanandroid.com/blog/show/2 視頻內顯示的是一個網頁上的文章列表。客戶端通過使用蒹葭網絡庫訪問該網站提供的接口,以此來獲取網頁的文章列表,當請求成功后,文章列表將顯示在模擬器頁面上。
以下為Demo代碼關鍵步驟的詳解:
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();}
(左右滑動查看更多)
2.創建“jianjia”對象,整個項目只需一個jianjia對象即可。 如何確保只有一個 jianjia對象?當代碼運行起來后,首先會創建AbilityPackage對象,調用AbilityPackage的onInitialize方法,AbilityPackage執行完成后才會啟動Ability。AbilityPackage就是一個全局的單例,所以在 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();
3.在MainAbilitySlice里面添加ListContainer,關于ListContainer的用法,請查看 官方文檔 ,此處不再贅述。
● 官方文檔
https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-java-component-listcontainer-0000001060007847
接著調用“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(); }
}
(左右滑動查看更多)
04
關于轉義字符問題的解決
如果服務端返回的json有特殊字符,比如中文的雙引號。gson在解析的時候會對特殊字符進行轉義,這時就需要將轉義后的字符串進行反轉義。如下圖所示:
如何將轉義后的字符串進行反轉義?commons-lang這個庫可以將轉義后的字符串進行反轉義,在build.gradle文件添加下的依賴。
// commons-lang可以對特殊字符進行轉義和反轉義implementation ‘commons-lang2.6’
調用StringEscapeUtils的unescapeHtml方法,如果字符串中沒有轉義字符,unescapeHtml方法會直接返回原字符串,否則會對字符串進行反轉義。具體的代碼可查看示例代碼中的HomeArticleProvider類。
// json里面有一些特殊符號,特殊符號會被gson轉義,// StringEscapeUtils可以對轉義的字符串進行反轉義String title = StringEscapeUtils.unescapeHtml(data.title);componentHolder.title.setText(title);
反轉義之后,特殊字符即可正常顯示:
溫馨提示:如果開發者具備以下知識的基礎,看源碼的時會更加輕松哦。
熟悉okhttp的常見用法
熟悉面向接口編程、反射、泛型、注解
熟悉構造者模式、適配器模式、工廠模式、策略模式、靜態代理、動態代理、責任鏈模式等設計模式
Demo剛運行的時候頁面可能會出現白屏的情況,是因為此時正在請求網絡。可以增加一個進度條來提升用戶體驗,大家可以自行添加~
目前只獲取了第一頁的文章列表,有興趣的開發者可以自行實現分頁加載,讓內容更加豐富,由于篇幅有限,此處只展示簡單的示例。
本文作者:裴云飛,愛奇藝應用開發工程師
編輯:jq
-
轉換器
+關注
關注
27文章
8975瀏覽量
150983 -
服務器
+關注
關注
13文章
9723瀏覽量
87419 -
代碼
+關注
關注
30文章
4891瀏覽量
70333 -
接口編程
+關注
關注
0文章
9瀏覽量
8809 -
OpenHarmony
+關注
關注
28文章
3838瀏覽量
18238
原文標題:【開發者說】HarmonyOS如何實現一套網絡請求框架
文章出處:【微信號:HarmonyOS_Dev,微信公眾號:HarmonyOS開發者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
HarmonyOS開發指導類文檔更新速遞(上)

如何調試 HTTP 請求和響應
AUTOSAR通信框架的優勢 AUTOSAR通信實例與應用場景
HarmonyOS NEXT應用元服務開發Intents Kit(意圖框架服務)綜述
Vector推出一套基于Visual Studio Code的免費插件

HarmonyOS NEXT應用元服務開發Intents Kit(意圖框架服務)習慣推薦方案概述
卷積神經網絡的實現工具與框架
Python編程:處理網絡請求的代理技術
HarmonyOS NEXT應用元服務開發Intents Kit(意圖框架服務)本地搜索方案概述
AWTK 最新動態:支持鴻蒙系統(HarmonyOS Next)

一套電源ATE自動測試系統如何完成多型號電源模塊測試?

評論