鴻蒙系統的網絡請求框架—蒹葭
一、前言
蒹葭(JianJia)是一款鴻蒙系統上的網絡請求框架,其實就是將安卓的Retrofit移植到鴻蒙系統上,我將鴻蒙版的Retrofit命名為蒹葭(JianJia)。蒹葭不僅能實現Retrofit的功能,還會提供一些Retrofit沒有的功能。Retrofit不支持動態替換域名,國內的應用一般都是有多個域名的,蒹葭支持動態替換域名。
二、源碼
要想讀懂源碼,需要具備以下技能。
- 熟悉okhttp的常見用法 ;
- 熟悉面向接口編程、反射、泛型、注解;
- 熟悉構造者模式、適配器模式、工廠模式、策略模式、靜態代理、動態代理、責任鏈模式等設計模式。
三、混淆
如果項目開啟了混淆,請在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.**{*;}
四、添加依賴
4、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()
- }
- }
4、2 打開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'
4、3 在配置文件中添加如下的權限
- ohos.permission.INTERNET
五、具體用法,用法跟retrofit一樣
蒹葭提供了一系列的注解,在進行網絡請求的時候,就需要用到這些注解。
5、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());
- }
- });
5、2 BaseUrl注解
國內的應用一般都是有多個域名的,BaseUrl注解可以對某個接口設置單獨的域名。
- public interface Wan {
- @BaseUrl("https://api.apiopen.top")
- @GET("getJoke")
- Call<ResponseBody> getJoke(@QueryMap Map<String, String> param);
- }
5、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);
- }
5、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、5 QueryMap注解
QueryMap注解以map的形式添加查詢參數,被QueryMap注解修飾的參數類型必須是Map對象。
- public interface Wan {
- @GET("wxarticle/list/405/1/json")
- Call<ResponseBody> search(@QueryMap Map<String, String> param);
- }
5、6 SkipCallbackExecutor注解
在鴻蒙系統上,蒹葭默認會將服務端的響應回調到主線程,如果在方法上使用SkipCallbackExecutor注解,那就不會將服務端的結果回調到主線程。
- public interface Wan {
- @SkipCallbackExecutor
- @GET("wxarticle/list/405/1/json")
- Call<ResponseBody> search(@QueryMap Map<String, String> param);
- }
5、7 FormUrlEncoded注解和Field注解
FormUrlEncoded注解用于發送一個表單請求,使用該注解必須在方法的參數添加Field注解,被Field注解修飾的參數類型可以是數組、集合、字符串等。
- public interface Wan {
- @POST("user/login")
- @FormUrlEncoded
- Call<ResponseBody> login(@Field("username") String username, @Field("password") String password);
- }
5、8 FormUrlEncoded注解和FieldMap注解
有時候表單的參數會比較多,如果使用Field注解,方法的參數就會比較多,此時就可以使用FieldMap注解,FieldMap注解以鍵值對的形式發送一個表單請求。如果被FieldMap注解修飾的參數不是Map類型,就會拋異常。如果Map的鍵值對為空,也會拋異常。
- public interface Wan {
- @POST("user/login")
- @FormUrlEncoded
- Call<ResponseBody> login(@FieldMap Map<String, String> map);
- }
5、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);
- }
5、10 Url注解
Url注解用于添加接口的完整地址。在Retrofit里面,如果接口的域名與創建retrofit對象指定的域名不相同,那就會使用Url注解來解決問題。在蒹葭里面同樣可以使用Url注解來解決問題,但蒹葭還提供了BaseUrl來解決該問題。
- public interface Wan {
- @GET()
- Call<ResponseBody> getArticle(@Url String url);
- }
5、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);
- }
5、12 Header注解
Header注解是作用于參數上的注解,用于添加請求頭。
- public interface Wan {
- @GET()
- Call<ResponseBody> foo(@Header("Accept-Language") String lang);
- }
5、13 HeaderMap注解
HeaderMap注解是作用于參數上的注解,以map的形式添加請求頭,map中每一項的鍵和值都不能為空,否則會拋異常。
- public interface Wan {
- @GET("/search")
- Call<ResponseBody> list(@HeaderMap Map<String, String> headers);
- }
5、14 添加數據轉換器
之前我們在接口里面定義方法的時候,方法的返回值時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());
- }
- });
六、總結
本文介紹了蒹葭的用法,蒹葭的原理跟retrofit是一樣的,有興趣的同學可以去看下源碼。