先扯兩句
隔了這么長(zhǎng)時(shí)間晶通,深深的負(fù)罪感終于督促我寫(xiě)下了這篇博客璃氢,當(dāng)然,之前的時(shí)間也不全是在玩狮辽,參加了一個(gè)面試一也,進(jìn)行了我人生中的第一次霸面,對(duì)方有個(gè)要求就是完成他們的demo隘竭,就可以得到面試機(jī)會(huì)塘秦,結(jié)果我完成以后單純的就去了,再然后就沒(méi)有然后了动看。
不過(guò)至少在這次demo中尊剔,我的基本框架得到了應(yīng)用,還是讓自己很欣慰的菱皆,另外還使用了一些博客后續(xù)將要與大家分享的內(nèi)容须误,算是一次提前的實(shí)戰(zhàn)吧,效果自認(rèn)為還算滿意仇轻。
如果關(guān)注我博客的大家應(yīng)該知道京痢,我前段時(shí)間寫(xiě)的就是Retrofit的內(nèi)容,所以今天就從Retrofit的封裝開(kāi)始寫(xiě)起吧篷店。
閑言少敘祭椰,老規(guī)矩還是先上我的Git庫(kù),然后開(kāi)始正文疲陕。
MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)
另外方淤,也把我做的這個(gè)小demo也發(fā)到的了庫(kù)中,也沒(méi)太深的東西內(nèi)容蹄殃,大家如果感興趣的話也而已去看看:
https://github.com/BanShouWeng/IYuBaTestApplication
Retrofit的Header封裝部分放在了《一個(gè)Android工程的從零開(kāi)始》階段總結(jié)與修改1-base携茂,如有需要的朋友可以去其中查看。
正文
關(guān)于Retrofit的相關(guān)內(nèi)容呢诅岩,如果大家有什么不太清楚的地方讳苦,可以看一下我的上一篇博客Android開(kāi)發(fā)相關(guān)——Retrofit+RxJava+OkHttp(下)使用,或許有人會(huì)疑問(wèn)吩谦,為什么給了下鸳谜,沒(méi)給上,主要還是因?yàn)橄虏攀鞘褂檬酵ⅲ暇唧w是什么咐扭,好奇的也可以去看一下Android開(kāi)發(fā)相關(guān)——Retrofit+RxJava+OkHttp(上)閑扯,雖然我估計(jì)有一部分人,看到“閑扯”二字草描,或許就沒(méi)興趣了,不過(guò)剛步入android世界中的大家還是可以去看看的策严,或許會(huì)有收獲也說(shuō)不定穗慕。
下面就正是開(kāi)始封裝的部分:
分析
其是封裝,說(shuō)白了就是為了我們?cè)谶\(yùn)用的時(shí)候能夠更方便妻导,從我個(gè)人的角度出發(fā)還是兩個(gè)字——偷懶逛绵!
從《Android開(kāi)發(fā)相關(guān)——Retrofit+RxJava+OkHttp(下)使用》中,大家或許也知道了倔韭,Retrofit網(wǎng)絡(luò)訪問(wèn)框架需要的東西:
- 訪問(wèn)數(shù)據(jù)回調(diào)接口
- 訪問(wèn)方法
所以想要封裝术浪,我們需要做的事,自然就是從這兩大塊入手寿酌,看看其中有哪些是可以直接復(fù)用的內(nèi)容胰苏,而這些內(nèi)容就是我們可以拿來(lái)偷懶的點(diǎn):
訪問(wèn)數(shù)據(jù)回調(diào)接口
public interface Movie2Service {
@GET("top250")
Observable<ResponseBody> getTop250(@Query("start") int start, @Query("count")int count);
}
從上面的接口中,我們可以看得出來(lái)醇疼,其中我們可以操作的點(diǎn)硕并,有三個(gè):
- 尾址
- 參數(shù)
- 返回?cái)?shù)據(jù)model(也就是Bean對(duì)象)
當(dāng)然,如果你一定要說(shuō)接口名也算的話秧荆,我也不反對(duì)倔毙。至少,在我一會(huì)說(shuō)的“封裝方法一”中是不會(huì)反對(duì)的乙濒,具體為什么陕赃,我這里先賣(mài)個(gè)關(guān)子,一會(huì)再聊颁股。而這三個(gè)可操作的點(diǎn)呢么库,在“封裝方法二”中才會(huì)用到,所以依然賣(mài)個(gè)關(guān)子豌蟋,我們一會(huì)再聊廊散,大家只需要暫時(shí)知道這三個(gè)點(diǎn)一會(huì)可以操作就行。
訪問(wèn)方法
private void getMovie2() {
String baseUrl = "https://api.douban.com/v2/movie/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
Movie2Service movieService = retrofit.create(Movie2Service.class);
movieService.getTopMovie(10, 10)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ResponseBody responseBody) {
try {
String responseString = responseBody.string();
Log.i("responseString", responseString);
LogUtil.info("response", responseString);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onError(@NonNull Throwable e) {
}
@Override
public void onComplete() {
}
});
}
這個(gè)訪問(wèn)方法中梧疲,可以修改的點(diǎn)允睹,同樣顯而易見(jiàn):
- baseUrl
- 結(jié)果回調(diào),也就是subscribe傳入的接口
至于其他的部分幌氮,比如解析方式是json還是xml啊缭受,是否使用RxJava啊,又或者線程的方式之類的參數(shù)自然也可以動(dòng)態(tài)設(shè)置该互,只是對(duì)于一個(gè)工程項(xiàng)目而言,除非十分必要的情況下蔓搞,考慮到開(kāi)發(fā)難度以及開(kāi)發(fā)周期等等諸多因素,很少會(huì)故意難為開(kāi)發(fā)人員喂分,而是采用一種萬(wàn)能的模式即可锦庸,所以這里我就將這些因素都忽略掉了甘萧,如果你真的遇到一個(gè)這么變態(tài)的產(chǎn)品,我只能在這里為你默默祈福了梆掸。
而這兩部分中扬卷,baseUrl也算是比較特殊的存在怪得,一般情況下钝鸽,比較小的項(xiàng)目中基本只適用一個(gè)baseUrl就可以結(jié)束戰(zhàn)斗拔恰,哪怕大的項(xiàng)目,最多也就是每個(gè)模塊一個(gè)baseUrl财岔,如果再大的匠璧,暫時(shí)還沒(méi)接觸到,但是想來(lái)也不會(huì)多多少侄刽。畢竟涉及到域名指黎、端口等等問(wèn)題醋安,當(dāng)然吓揪,在我看來(lái)這些都不是原因柠辞,主要還是后臺(tái)的戰(zhàn)友們,也懶啊放棒!
好吧间螟,以上都是玩笑話厢破,不過(guò)baseUrl很少有在網(wǎng)絡(luò)封裝的方法中體現(xiàn)的摩泪,無(wú)腦一點(diǎn)的方法就是封裝Utils包下的Const類中的靜態(tài)常量中劫谅,而相對(duì)正式點(diǎn)的玩法則是封裝到app mudule的build.grade中捏检,并且可以去BuildConfig文件中查找贯城,具體的玩法說(shuō)來(lái)也簡(jiǎn)單能犯,不過(guò)誰(shuí)讓我懶悲雳,還是直接上代碼以及BuildConfig目錄位置合瓢。
build.grade配置信息
signingConfigs {
debug {
storeFile file("./bsw.keystore")
storePassword "bswbsw"
keyAlias "wsbsw"
keyPassword "wsbswhhh"
}
release {
storeFile file("./bsw.keystore")
storePassword "bswbsw"
keyAlias "wsbsw"
keyPassword "wsbswhhh"
}
}
buildTypes {
release {
debug{
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField "String", "BASE_URL_NAME", "\"baseUrl地址\""
signingConfig signingConfigs.debug
}
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField "String", "BASE_URL_NAME", "\"baseUrl地址\""
signingConfig signingConfigs.release
}
}
BuildConfig位置
BuildConfig內(nèi)信息
package com.banshouweng.mybaseapplication;
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.banshouweng.mybaseapplication";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
// Fields from build type: debug
public static final String BASE_URL_NAME = "baseUrl地址";
}
// Fields from build type: debug下的內(nèi)容是我們自行添加的信息
build.grade配置信息
其中minifyEnabled 與proguardFiles 是在創(chuàng)建項(xiàng)目的時(shí)候自帶的顿苇,分別代表是否混淆纪岁,而proguardFiles 太專業(yè)了,我也說(shuō)不明白漩氨,大家找自己看一下吧
proguardFiles這部分有兩段叫惊,前一部分代表系統(tǒng)默認(rèn)的android程序的混淆文件霍狰,該文件已經(jīng)包含了基本的混淆聲明蔗坯,免去了我們很多事宾濒,這個(gè)文件的目錄在 /tools/proguard/proguard-android.txt , 后一部分是我們項(xiàng)目里的自定義的混淆文件屏箍,目錄就在 app/proguard-rules.txt , 如果你用Studio 1.0創(chuàng)建的新項(xiàng)目默認(rèn)生成的文件名是 proguard-rules.pro , 這個(gè)名字沒(méi)關(guān)系铣除,在這個(gè)文件里你可以聲明一些第三方依賴的一些混淆規(guī)則尚粘。
而其他部分則是我們都需要添加的了郎嫁,buildConfigField中就是我們要添加的baseUrl泽铛,格式已經(jīng)給出盔腔,但是需要切記的一點(diǎn)是月褥,這里添加的都是字符串宁赤,例如:
//輸入
buildConfigField "String", "BASE_URL_NAME", "\"baseUrl地址\""
buildConfigField "int", "COUNT", "0"
buildConfigField "boolean", "ISOK", "true"
//顯示
// Fields from build type: debug
public static final String BASE_URL_NAME = "baseUrl地址";
public static final boolean ISOK = true;
public static final int COUNT = 0;
//引用
String url = DebugConfig.BASE_URL_NAME;
int count = DebugConfig.COUNT;
boolean isOk= DebugConfig.ISOK;
可以看得出來(lái)决左,如果我們賦值的內(nèi)容都需要傳遞的是字符串佛猛,顯示的才是我們需要的內(nèi)容挚躯,或者說(shuō)AS用的是更無(wú)腦的玩法,那就是去掉最外層的引號(hào)感挥,所以當(dāng)創(chuàng)建String類型的參數(shù)時(shí)触幼,需要使用的是""baseUrl地址""的形式(加粗斜體的所有部分)置谦,亿傅,如果只加一層引號(hào)的話葵擎,就會(huì)出現(xiàn)如下效果:
當(dāng)然签餐,如果我們將int或boolean的引號(hào)去掉盯串,又會(huì)是另一個(gè)效果:
ssigningConfig signingConfigs.release這行就是以上的內(nèi)容冠摄,將會(huì)在何時(shí)觸發(fā)加載到BuildConfig文件中耗拓,我這里是分兩種情況:1、debug(自行調(diào)試的時(shí)候)樟插;2黄锤、release(發(fā)行的時(shí)候)鸵熟。
而ssigningConfig 中則是對(duì)應(yīng)兩種情況的簽名信息:
- storeFile file("./bsw.keystore"):簽名文件位置(../xxx.keystore(或者xxx.jks))
- storePassword "bswbsw"簽名文件密碼
- keyAlias "wsbsw" 簽名別名
- keyPassword "wsbswhhh" 別名密碼
這些全都配置好流强,我們點(diǎn)擊Sync Now的時(shí)候打月,編譯結(jié)束奏篙,在BuildConfig中才會(huì)有我們要的內(nèi)容秘通,如果只添加了release而不添加debug肺稀,那么就只有在發(fā)行包中才會(huì)在BuildConfig文件下生成對(duì)應(yīng)靜態(tài)常量盹靴,而我們平時(shí)開(kāi)所處的環(huán)境是debug狀態(tài)稿静,所以這個(gè)時(shí)候改备,是我們是無(wú)法在BuildConfig中看到對(duì)應(yīng)的靜態(tài)常量的悬钳,所以開(kāi)發(fā)時(shí)也找不到默勾,調(diào)試時(shí)也用也會(huì)報(bào)錯(cuò)母剥,所以開(kāi)發(fā)時(shí)一定要對(duì)應(yīng)添加debug和release才可以
關(guān)于build.gradle环疼,文件自然還有許多其他的妙用炫隶,這里就先不列舉了伪阶,我們還是回歸到正文栅贴,也就是說(shuō)筹误,baseUrl通過(guò)這上述的Const或者DebugConfig這兩種方法集成即可厨剪,就不需要額外花時(shí)間了祷膳。
所以重頭戲也就都在結(jié)果回調(diào)的接口上了直晨。
封裝方法1——最無(wú)腦的封裝
看了這哥標(biāo)題一定會(huì)有人問(wèn)勇皇,什么叫最無(wú)腦的封裝敛摘,很好理解啊兄淫,那就是封裝起來(lái)特別簡(jiǎn)單捕虽,用起來(lái)泄私,相當(dāng)麻煩挖滤。又有人會(huì)問(wèn)斩松,既然麻煩為什么還要去這么封裝惧盹,其實(shí)很簡(jiǎn)單钧椰,這種封裝的用途就是為了應(yīng)付那些腦洞打開(kāi)的產(chǎn)品的嫡霞,萬(wàn)一他們真想出來(lái)什么喪心病狂的需求诊沪,我們還無(wú)法不去完成的情況下端姚,自然就需要用這種麻煩的封裝了渐裸。
至于為什么不直接使用Retrofit昏鹃,一定還要封裝一下洞渤,自然是萬(wàn)一產(chǎn)品下次又爆發(fā)了一個(gè)相類似的腦洞的情況下您宪,我們可以稍做修改,便能直接拿來(lái)用溜畅,一旦直接使用Retrofit了慈格,下次還得重新寫(xiě)浴捆,或者再去找之前加到哪里了选泻,麻煩页眯!
而這種封裝所需要的包(對(duì)包的部分不太清楚的參見(jiàn)《一個(gè)Android工程的從零開(kāi)始》-1前期準(zhǔn)備)便是apimagager以及service窝撵。
service自不必說(shuō)碌奉,看名字也能知道赐劣,它肯定是存放回調(diào)接口的椭岩,而回調(diào)接口我們自然也能加一定的處理判哥,從Retrofit官網(wǎng)中塌计,我們可以發(fā)現(xiàn)其中有兩種玩法很有趣锌仅,可以拿來(lái)用一下,我們先來(lái)看一下效果:
public interface Movie2Service {
@GET("{action}")
Observable<ResponseBody> getTopMovie(@Path("action") String action, @QueryMap Map<String, String> params);
}
顯而易見(jiàn),就是@Path以及@QueryMap魁衙,先說(shuō)@QueryMap剖淀,其實(shí)對(duì)比一下之前的@Query就會(huì)發(fā)現(xiàn)纵隔,它只不過(guò)是在后面加了一個(gè)Map巨朦,至于功能糊啡,還是傳遞的參數(shù),只不過(guò)原本的玩法需要傳遞一個(gè)名字為name的String參數(shù)John梭依,那就要先定義一個(gè)String變量役拴,命名name河闰,然后賦值姜性。這樣原本來(lái)說(shuō)也不麻煩部念,可是一旦讓傳遞大量參數(shù)的時(shí)候妓湘,就顯得有些不便了多柑,所以就使用了Map去傳遞竣灌,同樣的傳遞John初嘹,只需要一個(gè)map.put("name", "john");即可屯烦,大量數(shù)據(jù)的時(shí)候,繼續(xù)put就好翁狐,什么時(shí)候都put好了露懒,將這個(gè)map傳遞進(jìn)來(lái)就達(dá)到了目的。
接下來(lái)就是@Path砂心,先看看官方的說(shuō)法:
A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }. A corresponding parameter must be annotated with @Path using the same string.
嗯懈词,除了看不懂沒(méi)有別的缺點(diǎn)了!所以我也不打算逐字去翻譯了辩诞,其實(shí)看例子也能看出來(lái)坎弯,這就是一個(gè)占位符,用“{占位符名}”站好位译暂,然后@Path("占位符名")按照其中的占位符名,將傳遞進(jìn)來(lái)的內(nèi)容內(nèi)容放置在對(duì)應(yīng)的占位符名所占的位置上秧秉。所以傳進(jìn)來(lái)的action就會(huì)被當(dāng)做我們的尾址來(lái)使用褐桌。而我們的返回Bean則依照要求取就好衰抑,我這里是使用的OKHttp的ResponseBody象迎。如此,Service就完成了。
apimagager砾淌,這部分就完整看產(chǎn)品的需求了啦撮,基本框架在上面,自行添加修改一下就好汪厨,當(dāng)然返回值是肯定需要處理一下的赃春,不過(guò)這部分我會(huì)在下一個(gè)封裝方法中詳細(xì)說(shuō)明,這里只管調(diào)用即可劫乱。
封裝方法2
這次沒(méi)給額外的說(shuō)明织中,也是我最后的一種封裝方法,就是打算弄一個(gè)一招鮮吃遍天的玩法衷戈,雖然封裝起來(lái)會(huì)麻煩許多狭吼,但是有點(diǎn)也很明顯,用著方便殖妇,至于方便什么刁笙,必然是方便偷懶了。
先說(shuō)說(shuō)我給這個(gè)封裝方法找的位置谦趣,作為一個(gè)懶人疲吸,雖然這個(gè)方法也可以放置在ApiMageger中,但是調(diào)用的時(shí)候前鹅,竟然還得讓我new個(gè)對(duì)象摘悴,有這時(shí)間我給自己new個(gè)女朋友好不〗⒒妫或者說(shuō)是用靜態(tài)方法烦租,不過(guò)說(shuō)實(shí)話,靜態(tài)的方法或者常量變量還是盡量減少使用除盏,畢竟靜態(tài)內(nèi)存也不富裕叉橱,再說(shuō)不考慮這個(gè),我們不還是需要ApiMageger.getXXX嗎者蠕。有這時(shí)間窃祝,我研究要追那個(gè)妹子好不。
總之踱侣,在偷懶心理作祟下粪小,我選擇了將這個(gè)網(wǎng)絡(luò)訪問(wèn)的封裝放置在了BaseActivity以及BaseFragment中,用的時(shí)候直接掉方法即可抡句。
不過(guò)如果遇到個(gè)別的需要在封裝的Adapter中掉訪問(wèn)網(wǎng)絡(luò)的方法時(shí)就比較尷尬探膊,需要多費(fèi)些周折,例如發(fā)個(gè)EvenBus之類的待榔,說(shuō)起來(lái)也不算麻煩逞壁。
具體是封裝在APIManager中還是封裝在BaseActivity/BaseFragment就看你個(gè)人的需求情況流济,我下面的內(nèi)容就先按照封裝在BaseActivity/BaseFragment進(jìn)行,相信提出來(lái)放在APIManager的操作腌闯,對(duì)大家來(lái)說(shuō)還是小菜一碟的绳瘟。
數(shù)據(jù)回調(diào)1
//網(wǎng)絡(luò)訪問(wèn)
public <T extends BaseBean> void get1(final String action, final Class<T> cls) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
GetService1 getService = retrofit.create(GetService1.class);
if (params == null) {
params = new HashMap<>();
}
getService.get(action, params)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ResponseBody responseBody) {
try {
T t = new Gson().fromJson(responseBody.string(), cls);
success(action, t);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onError(@NonNull Throwable e) {
error(action, e);
}
@Override
public void onComplete() {
}
});
}
//成功方法
public void success(String action, BaseBean baseBean) {
}
//失敗方法
public void error(String action, Throwable e) {
}
在上述方法之前,先創(chuàng)建和一個(gè)共有的Map類進(jìn)行傳參姿骏,由于是繼承的我們的BaseActivity/BaseFragment糖声,所以這里不需要我們傳遞Map參數(shù)了,直接調(diào)用即可分瘦。因?yàn)檫@里我們要使用一個(gè)萬(wàn)能的封裝法蘸泻,很遺憾的是,在使用Retrofit的時(shí)候嘲玫,直接使用泛型T傳遞的時(shí)候蟋恬,會(huì)報(bào)錯(cuò)Observer< T >不能直接使用,所以我暫時(shí)的解決方法只能是傳遞OkHttp中的ResponseBody類趁冈,然后通過(guò)string()方法獲取其中的json串歼争,再通過(guò)GSON解析成我們需要的對(duì)應(yīng)類。并將其傳遞到success方法中渗勘,在訪問(wèn)的Activity重寫(xiě)該方法即可沐绒,至于為什么要將action也傳遞回來(lái),主要是在進(jìn)行多次網(wǎng)絡(luò)請(qǐng)求的時(shí)候旺坠,用來(lái)分辨對(duì)應(yīng)區(qū)分是誰(shuí)發(fā)出的請(qǐng)求乔遮,隨后做出對(duì)應(yīng)的處理。
不過(guò)這個(gè)方法的缺點(diǎn)就是:1取刃、我們需要額外重寫(xiě)成功或者失敗的方法蹋肮,還是有點(diǎn)麻煩;2璧疗、success方法中的參數(shù)是BaseBean(參見(jiàn)附錄1)坯辩,而不是我們想要生成的Bean對(duì)象,還需要強(qiáng)轉(zhuǎn)一下崩侠。
當(dāng)然以上屬于吹毛求疵漆魔,不過(guò)為了偷懶,所以我就想到了下面的方法2却音。
封裝方法2
這次沒(méi)給額外的說(shuō)明改抡,也是我最后的一種封裝方法,就是打算弄一個(gè)一招鮮吃遍天的玩法系瓢,雖然封裝起來(lái)會(huì)麻煩許多阿纤,但是有點(diǎn)也很明顯,用著方便夷陋,至于方便什么欠拾,必然是方便偷懶了胰锌。
先說(shuō)說(shuō)我給這個(gè)封裝方法找的位置,作為一個(gè)懶人清蚀,雖然這個(gè)方法也可以放置在ApiMageger中,但是調(diào)用的時(shí)候爹谭,竟然還得讓我new個(gè)對(duì)象枷邪,有這時(shí)間我給自己new個(gè)女朋友好不∨捣玻或者說(shuō)是用靜態(tài)方法东揣,不過(guò)說(shuō)實(shí)話,靜態(tài)的方法或者常量變量還是盡量減少使用腹泌,畢竟靜態(tài)內(nèi)存也不富裕嘶卧,再說(shuō)不考慮這個(gè),我們不還是需要ApiMageger.getXXX嗎凉袱。有這時(shí)間芥吟,我研究要追那個(gè)妹子好不。
總之专甩,在偷懶心理作祟下钟鸵,我選擇了將這個(gè)網(wǎng)絡(luò)訪問(wèn)的封裝放置在了BaseNetActivity以及BaseNetFragment中,用的時(shí)候直接掉方法即可涤躲。
不過(guò)如果遇到個(gè)別的需要在封裝的Adapter中掉訪問(wèn)網(wǎng)絡(luò)的方法時(shí)就比較尷尬棺耍,需要多費(fèi)些周折,例如發(fā)個(gè)EvenBus之類的种樱,說(shuō)起來(lái)也不算麻煩蒙袍。
具體是封裝在APIManager中還是封裝在BaseNetActivity/BaseNetFragment就看你個(gè)人的需求情況,我下面的內(nèi)容就先按照封裝在BaseNetActivity/BaseNetFragment進(jìn)行嫩挤,相信提出來(lái)放在APIManager的操作害幅,對(duì)大家來(lái)說(shuō)還是小菜一碟的。
對(duì)于封裝岂昭,第一件事就是需要我們創(chuàng)建一個(gè)BaseNetActivity/BaseNetFragment矫限,至于為什么沒(méi)有按照本篇博客修改之前放到BaseActivity/BaseFragment中,主要是有一些界面還是不需要網(wǎng)絡(luò)訪問(wèn)的佩抹,雖然少一些叼风,但是集成這些東西還是很好資源的,另外就是之前我們的BaseActivity中已經(jīng)放了很多內(nèi)容棍苹,如果網(wǎng)絡(luò)訪問(wèn)的內(nèi)容也放到其中難免有寫(xiě)太冗雜了无宿,所以網(wǎng)絡(luò)訪問(wèn)的部分就單獨(dú)拿出來(lái)了一個(gè)BaseNetActivity/BaseNetFragment。當(dāng)然枢里,與之前的Activity/Fragment不同的是孽鸡,BaseNetActivity/BaseNetFragment不需要我們關(guān)聯(lián)布局文件蹂午,因?yàn)樗墓ぷ髦皇蔷W(wǎng)絡(luò)訪問(wèn)而已,BaseNetActivity/BaseNetFragment繼承BaseActivity/BaseFragment這樣就可以使用到網(wǎng)絡(luò)與布局的雙封裝了彬碱。
看了前面的封裝方法一豆胸,很顯然,它的作用就是對(duì)應(yīng)每一個(gè)創(chuàng)建對(duì)應(yīng)Service巷疼,既然如此晚胡,大家一定猜到了,這部分的封裝就是一個(gè)通用的方法了嚼沿,也就是無(wú)論你想要用什么樣子的Bean都隨你心情估盘。
先上Service代碼:
public interface RetrofitGetService {
@GET("{action}")
Observable<ResponseBody> getResult(@Path("action") String action, @QueryMap Map<String, String> params);
}
大家可以看得出來(lái),這個(gè)部分與我們上面的方法一舉例用的是完全相同的骡尽,而為什么值用ResponseBody遣妥,主要還是為了其提供的string()方法可以獲得JSON串,方便我們自行轉(zhuǎn)換攀细。
再就是創(chuàng)建初始化retrofit的方法:
private void initBaseData() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(5, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(builder.build())
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
其中OkHttpClient的作用就是connectTimeout方法箫踩,用于設(shè)置5s鏈接超時(shí)的處理。
隨后就是結(jié)果回調(diào)的接口谭贪,用于將得到的結(jié)果傳回去 班套,這部分其實(shí)用單純的方法也可以,在Activity中重寫(xiě)就可以得到結(jié)果參數(shù)故河,可是懶人我實(shí)在懶得去記需要重寫(xiě)的方法名吱韭,用接口的就可以很好的回避掉這點(diǎn)了。
public interface ResultCallBack<T extends BaseBean> {
void success(String action, T t);
void error(String action, Throwable e);
}
萬(wàn)事俱備鱼的,下面就該進(jìn)行正式的封裝部分了
/**
* Get請(qǐng)求
*
* @param action 請(qǐng)求接口的尾址理盆,如“top250”
* @param clazz 要轉(zhuǎn)換的Bean類型(需繼承BaseBean)
* @param callBack 結(jié)果回調(diào)接口
* @param <T> 用于繼承BaseBean的占位變量
*/
public <T extends BaseBean> void get(final String action, final Class<T> clazz, final ResultCallBack callBack) {
if (getService == null) {
getService = retrofit.create(RetrofitGetService.class);
}
if (params == null) {
params = new HashMap<>();
}
getService.getResult(action, params)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ResponseBody responseBody) {
try {
callBack.success(action, new Gson().fromJson(responseBody.string(), clazz));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onError(@NonNull Throwable e) {
callBack.error(action, e);
}
@Override
public void onComplete() {
}
});
}
/**
* Post請(qǐng)求
*
* @param action 請(qǐng)求接口的尾址,如“top250”
* @param clazz 要轉(zhuǎn)換的Bean類型(需繼承BaseBean)
* @param callBack 結(jié)果回調(diào)接口
* @param <T> 用于繼承BaseBean的占位變量
*/
public <T extends BaseBean> void post(final String action, final Class<T> clazz, final ResultCallBack callBack) {
if (postService == null) {
postService = retrofit.create(RetrofitPostService.class);
}
if (params == null) {
params = new HashMap<>();
}
postService.postResult(action, params)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ResponseBody responseBody) {
try {
callBack.success(action, new Gson().fromJson(responseBody.string(), clazz));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onError(@NonNull Throwable e) {
callBack.error(action, e);
}
@Override
public void onComplete() {
}
});
}
可以看到凑阶,JSON轉(zhuǎn)換成Bean使用的是Gson解析猿规,其余沒(méi)有變化,如果有一些各個(gè)接口都需要添加的參數(shù)宙橱,就可以對(duì)應(yīng)的方法中直接添加姨俩,以免重復(fù)操作。
疑問(wèn)##
雖然我的封裝達(dá)到了目的师郑,可是Retrofit本身不應(yīng)該出現(xiàn)前面出現(xiàn)的問(wèn)題环葵,我這里也有一個(gè)疑問(wèn),究竟是我還沒(méi)有找到Retrofit的正確使用方式宝冕,還是Retrofit自身卻是存在這個(gè)漏洞张遭,也希望大家有所發(fā)現(xiàn)能為我指點(diǎn)迷津,在此先謝過(guò)各位了地梨。BaseNetActivity完整代碼參見(jiàn)附錄2
附錄
附錄1
BaseBean:
其實(shí)也沒(méi)什么特殊的部分菊卷,其中都是一些基礎(chǔ)的部分缔恳,比如網(wǎng)絡(luò)訪問(wèn)是否成功之類的處理,具體大家可以參考各種errorCode洁闰,就比如打擊熟悉的404就是其中的一種歉甚,當(dāng)然,這個(gè)錯(cuò)誤碼肯定與404不同扑眉,而是后臺(tái)定義的錯(cuò)誤碼纸泄,常用的場(chǎng)合就是登錄時(shí),用戶沒(méi)有注冊(cè)襟雷、賬戶密碼不正確之類的錯(cuò)誤情況判斷刃滓。
再有一點(diǎn)就是仁烹,將BaseBean序列號(hào)耸弄,便于Activity與Activity之間,或者Activity與Fragment之間傳值卓缰。
public class BaseBean implements Serializable {}
這里實(shí)現(xiàn)序列號(hào)的方法是實(shí)現(xiàn)Serializable 接口计呈,當(dāng)然還有一種玩法就是實(shí)現(xiàn)Parcelable接口,至于兩種的區(qū)別征唬,請(qǐng)參見(jiàn)Serializable 和 Parcelable 兩種序列化
附錄2
public class BaseNetActivity extends BaseActivity {
private String baseUrl = "https://api.douban.com/v2/movie/";
private RetrofitGetService getService;
private RetrofitPostService postService;
private Retrofit retrofit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initBaseData();
}
private void initBaseData() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(5, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(builder.build())
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
/**
* Get請(qǐng)求
*
* @param action 請(qǐng)求接口的尾址捌显,如“top250”
* @param clazz 要轉(zhuǎn)換的Bean類型(需繼承BaseBean)
* @param callBack 結(jié)果回調(diào)接口
* @param <T> 用于繼承BaseBean的占位變量
*/
public <T extends BaseBean> void get(final String action, final Class<T> clazz, final ResultCallBack callBack) {
if (getService == null) {
getService = retrofit.create(RetrofitGetService.class);
}
if (params == null) {
params = new HashMap<>();
}
params.put("start", "0");
params.put("count", "10");
getService.getResult(action, null, params)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ResponseBody responseBody) {
try {
String responseString = responseBody.string();
Log.i("responseString", "responseString get " + responseString);
callBack.success(action, new Gson().fromJson(responseBody.string(), clazz));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onError(@NonNull Throwable e) {
callBack.error(action, e);
}
@Override
public void onComplete() {
}
});
}
/**
* Post請(qǐng)求
*
* @param action 請(qǐng)求接口的尾址,如“top250”
* @param clazz 要轉(zhuǎn)換的Bean類型(需繼承BaseBean)
* @param callBack 結(jié)果回調(diào)接口
* @param <T> 用于繼承BaseBean的占位變量
*/
public <T extends BaseBean> void post(final String action, final Class<T> clazz, final ResultCallBack callBack) {
if (postService == null) {
postService = retrofit.create(RetrofitPostService.class);
}
if (params == null) {
params = new HashMap<>();
}
params.put("start", "0");
params.put("count", "10");
postService.postResult(action, null, params)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ResponseBody responseBody) {
try {
String responseString = responseBody.string();
Log.i("responseString", "responseString post " + responseString);
callBack.success(action, new Gson().fromJson(responseBody.string(), clazz));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onError(@NonNull Throwable e) {
callBack.error(action, e);
}
@Override
public void onComplete() {
}
});
}
public interface ResultCallBack<T extends BaseBean> {
void success(String action, T t);
void error(String action, Throwable e);
}
}