鴻蒙系統(tǒng)的網(wǎng)絡(luò)請(qǐng)求框架—蒹葭
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
一、前言
蒹葭(JianJia)是一款鴻蒙系統(tǒng)上的網(wǎng)絡(luò)請(qǐng)求框架,其實(shí)就是將安卓的Retrofit移植到鴻蒙系統(tǒng)上,我將鴻蒙版的Retrofit命名為蒹葭(JianJia)。蒹葭不僅能實(shí)現(xiàn)Retrofit的功能,還會(huì)提供一些Retrofit沒(méi)有的功能。Retrofit不支持動(dòng)態(tài)替換域名,國(guó)內(nèi)的應(yīng)用一般都是有多個(gè)域名的,蒹葭支持動(dòng)態(tài)替換域名。
二、源碼
要想讀懂源碼,需要具備以下技能。
- 熟悉okhttp的常見(jiàn)用法 ;
- 熟悉面向接口編程、反射、泛型、注解;
- 熟悉構(gòu)造者模式、適配器模式、工廠模式、策略模式、靜態(tài)代理、動(dòng)態(tài)代理、責(zé)任鏈模式等設(shè)計(jì)模式。
三、混淆
如果項(xiàng)目開(kāi)啟了混淆,請(qǐng)?jiān)趐roguard-rules.pro添加如下的代碼。關(guān)于混淆,可以查看鴻蒙代碼配置混淆
- -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這個(gè)包下面的類實(shí)現(xiàn)了Serialized接口,
- # 實(shí)現(xiàn)了Serialized接口的類不能被混淆,請(qǐng)把com.poetry.jianjia.bean這個(gè)包名替換成你自己的包名
- -keep class com.poetry.jianjia.bean.**{*;}
四、添加依賴
4、1 在項(xiàng)目根目錄下的build.gradle文件中添加mavenCentral()倉(cāng)庫(kù),打開(kāi)項(xiàng)目根目錄下的build.gradle文件,在build.gradle文件的repositories閉包下面添加mavenCentral()
- buildscript {
- repositories {
- // 添加maven中央倉(cāng)庫(kù)
- 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中央倉(cāng)庫(kù)
- 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 打開(kāi)entry目錄下的build.gradle文件中,在build.gradle文件中的dependencies閉包下添加下面的依賴
- // 蒹葭的核心代碼
- implementation 'io.gitee.zhongte:jianjia:1.0.0'
- // 數(shù)據(jù)轉(zhuǎn)換器,數(shù)據(jù)轉(zhuǎn)換器使用gson來(lái)幫我們解析json,不需要我們手動(dòng)解析json
- implementation 'io.gitee.zhongte:converter-gson:1.0.0'
- implementation "com.google.code.gson:gson:2.8.2"
- // 日志攔截器,通過(guò)日志攔截器可以看到請(qǐng)求頭、請(qǐng)求體、響應(yīng)頭、響應(yīng)體
- implementation 'com.squareup.okhttp3:logging-interceptor:3.7.0'
4、3 在配置文件中添加如下的權(quán)限
- ohos.permission.INTERNET
五、具體用法,用法跟retrofit一樣
蒹葭提供了一系列的注解,在進(jìn)行網(wǎng)絡(luò)請(qǐng)求的時(shí)候,就需要用到這些注解。
5、1 GET注解
創(chuàng)建接口,在方法里面使用GET注解,GET注解用于標(biāo)識(shí)這是一個(gè)GET請(qǐng)求,方法的返回值是Call對(duì)象,泛型是ResponseBody,其實(shí)泛型也可以是具體的實(shí)體對(duì)象,這個(gè)后面再說(shuō)。蒹葭如何完成網(wǎng)絡(luò)請(qǐng)求?使用構(gòu)造者模式創(chuàng)建jianjia對(duì)象,baseUrl就是域名,在創(chuàng)建jianjia對(duì)象的時(shí)候就必須指定域名。調(diào)用create方法來(lái)生成接口的實(shí)例,調(diào)用wan.getBanner().enqueue來(lái)執(zhí)行網(wǎng)絡(luò)請(qǐng)求,請(qǐng)求成功就會(huì)回調(diào)onResponse方法,請(qǐng)求失敗就會(huì)回調(diào)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注解
國(guó)內(nèi)的應(yīng)用一般都是有多個(gè)域名的,BaseUrl注解可以對(duì)某個(gè)接口設(shè)置單獨(dú)的域名。
- public interface Wan {
- @BaseUrl("https://api.apiopen.top")
- @GET("getJoke")
- Call<ResponseBody> getJoke(@QueryMap Map<String, String> param);
- }
5、3 Path注解
Path注解在路徑中替換指定的參數(shù)值,定義下面的方法??梢钥吹轿覀兌x了一個(gè)getArticle方法,方法接收一個(gè)page參數(shù),并且我們的@GET注解中使用{page}聲明了訪問(wèn)路徑,這里你可以把{page}當(dāng)做占位符,而實(shí)際運(yùn)行中會(huì)通過(guò)@Path("page")所標(biāo)注的參數(shù)進(jìn)行替換。
- public interface Wan {
- @GET("article/list/{page}/json")
- Call<ResponseBody> getArticle(@Path("page") int page);
- }
5、4 Query注解
Query注解用于給get請(qǐng)求添加請(qǐng)求參數(shù),被Query注解修飾的參數(shù)類型可以是數(shù)組、集合、字符串等。
- 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的形式添加查詢參數(shù),被QueryMap注解修飾的參數(shù)類型必須是Map對(duì)象。
- public interface Wan {
- @GET("wxarticle/list/405/1/json")
- Call<ResponseBody> search(@QueryMap Map<String, String> param);
- }
5、6 SkipCallbackExecutor注解
在鴻蒙系統(tǒng)上,蒹葭默認(rèn)會(huì)將服務(wù)端的響應(yīng)回調(diào)到主線程,如果在方法上使用SkipCallbackExecutor注解,那就不會(huì)將服務(wù)端的結(jié)果回調(diào)到主線程。
- public interface Wan {
- @SkipCallbackExecutor
- @GET("wxarticle/list/405/1/json")
- Call<ResponseBody> search(@QueryMap Map<String, String> param);
- }
5、7 FormUrlEncoded注解和Field注解
FormUrlEncoded注解用于發(fā)送一個(gè)表單請(qǐng)求,使用該注解必須在方法的參數(shù)添加Field注解,被Field注解修飾的參數(shù)類型可以是數(shù)組、集合、字符串等。
- public interface Wan {
- @POST("user/login")
- @FormUrlEncoded
- Call<ResponseBody> login(@Field("username") String username, @Field("password") String password);
- }
5、8 FormUrlEncoded注解和FieldMap注解
有時(shí)候表單的參數(shù)會(huì)比較多,如果使用Field注解,方法的參數(shù)就會(huì)比較多,此時(shí)就可以使用FieldMap注解,F(xiàn)ieldMap注解以鍵值對(duì)的形式發(fā)送一個(gè)表單請(qǐng)求。如果被FieldMap注解修飾的參數(shù)不是Map類型,就會(huì)拋異常。如果Map的鍵值對(duì)為空,也會(huì)拋異常。
- public interface Wan {
- @POST("user/login")
- @FormUrlEncoded
- Call<ResponseBody> login(@FieldMap Map<String, String> map);
- }
5、9 Body注解
服務(wù)端會(huì)要求端上把json字符串作為請(qǐng)求體發(fā)給服務(wù)端。此時(shí)就可以使用Body注解定義的參數(shù)可以直接傳入一個(gè)實(shí)體類,內(nèi)部會(huì)把該實(shí)體序列化并將序列化后的結(jié)果直接作為請(qǐng)求體發(fā)送出去。
如果被Body注解修飾的參數(shù)的類型是RequestBody對(duì)象,那調(diào)用者可以不添加數(shù)據(jù)轉(zhuǎn)換器,內(nèi)部會(huì)使用默認(rèn)的數(shù)據(jù)轉(zhuǎn)換器。
如果被Body注解修飾的參數(shù)的類型不是RequestBody對(duì)象,是一個(gè)具體的實(shí)體類,那調(diào)用者需要自定義一個(gè)類,并且繼承Converter.Factory。
- public interface Wan {
- /**
- * 被Body注解修飾的參數(shù)的類型是RequestBody對(duì)象,那調(diào)用者可以不添加數(shù)據(jù)轉(zhuǎn)換器,內(nèi)部會(huì)使用默認(rèn)的數(shù)據(jù)轉(zhuǎn)換器
- *
- * @param body
- * @return
- */
- @POST("user/register")
- Call<ResponseBody> register(@Body RequestBody body);
- /**
- * 被Body注解修飾的參數(shù)的類型不是RequestBody對(duì)象,是一個(gè)具體的實(shí)體類,那調(diào)用者需要自定義一個(gè)類,并且繼承Converter.Factory
- *
- * @param user
- * @return
- */
- @POST("user/register")
- Call<ResponseBody> register(@Body User user);
- }
5、10 Url注解
Url注解用于添加接口的完整地址。在Retrofit里面,如果接口的域名與創(chuàng)建retrofit對(duì)象指定的域名不相同,那就會(huì)使用Url注解來(lái)解決問(wèn)題。在蒹葭里面同樣可以使用Url注解來(lái)解決問(wèn)題,但蒹葭還提供了BaseUrl來(lái)解決該問(wèn)題。
- public interface Wan {
- @GET()
- Call<ResponseBody> getArticle(@Url String url);
- }
5、11 Headers注解
Headers注解是作用于方法上的注解,用于添加一個(gè)或多個(gè)請(qǐng)求頭。
- 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注解是作用于參數(shù)上的注解,用于添加請(qǐng)求頭。
- public interface Wan {
- @GET()
- Call<ResponseBody> foo(@Header("Accept-Language") String lang);
- }
5、13 HeaderMap注解
HeaderMap注解是作用于參數(shù)上的注解,以map的形式添加請(qǐng)求頭,map中每一項(xiàng)的鍵和值都不能為空,否則會(huì)拋異常。
- public interface Wan {
- @GET("/search")
- Call<ResponseBody> list(@HeaderMap Map<String, String> headers);
- }
5、14 添加數(shù)據(jù)轉(zhuǎn)換器
之前我們?cè)诮涌诶锩娑x方法的時(shí)候,方法的返回值時(shí)Call對(duì)象,泛型是ResponseBody。在這種情況下,服務(wù)端返回給端上的數(shù)據(jù)就會(huì)在ResponseBody里面,端上需要手動(dòng)解析json,將json解析成一個(gè)實(shí)體類。
其實(shí),我們沒(méi)必要手動(dòng)解析json,可以讓gson幫我們解析json。蒹葭支持添加數(shù)據(jù)轉(zhuǎn)換器,在創(chuàng)建對(duì)象的時(shí)候添加數(shù)據(jù)轉(zhuǎn)換器,也就是把gson添加進(jìn)來(lái)。在onResponse方法里面就可以直接得到實(shí)體類對(duì)象了,gson幫我們把json解析成了一個(gè)實(shí)體對(duì)象。
首先在build.gradle文件添加數(shù)據(jù)轉(zhuǎn)換器的依賴。
- // 數(shù)據(jù)轉(zhuǎn)換器,數(shù)據(jù)轉(zhuǎn)換器使用gson來(lái)幫我們解析json,不需要我們手動(dòng)解析json
- implementation 'io.gitee.zhongte:converter-gson:1.0.0'
- implementation "com.google.code.gson:gson:2.8.2"
在代碼中使用數(shù)據(jù)轉(zhuǎn)換器
- 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已經(jīng)被解析成banner對(duì)象了
- Banner banner = response.body();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- @Override
- public void onFailure(Call<Banner> call, Throwable t) {
- LogUtils.info("yunfei", t.getMessage());
- }
- });
六、總結(jié)
本文介紹了蒹葭的用法,蒹葭的原理跟retrofit是一樣的,有興趣的同學(xué)可以去看下源碼。
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)