聲明:原創(chuàng)作品,轉(zhuǎn)載請注明出處http://www.reibang.com/p/7b839b7c5884
之前公司的項目用到了MVP+Retrofit+RxJava的框架進(jìn)行網(wǎng)絡(luò)請求,所以今天特此寫一篇文章以做總結(jié)雅宾。相信很多人都聽說過MVP篙螟、Retrofit洛口、以及RxJava慌烧,有的人已經(jīng)開始用了炫乓,有的人可能還不知道這是什么冀墨,以及到底怎么用闸衫。不過沒關(guān)系,接下來我將為你一一揭開他們的神秘面紗诽嘉,然后利用這三個家伙搭建一個網(wǎng)絡(luò)請求框架
1.什么是MVP蔚出?
MVP(Model View Presenter)其實就是一種項目的整體框架,能讓你的代碼變得更加簡潔虫腋,說起框架大家可能還會想到MVC身冬、MVVM。由于篇幅原因岔乔,這里我們先不講MVVM酥筝,先來看一下MVC。其實Android本身就采用的是MVC(Model View Controllor)模式雏门、其中Model指的是數(shù)據(jù)邏輯和實體模型嘿歌;View指的是布局文件、Controllor指的是Activity茁影。對于很多Android初學(xué)者可能會有這樣的經(jīng)歷宙帝,寫代碼的時候,不管三七二十一都往Activity中寫募闲,當(dāng)然我當(dāng)初也是這么干的步脓,根本就沒有什么框架的概念,只要能實現(xiàn)某一個功能就很開心了,沒有管這么多靴患。當(dāng)然項目比較小還好仍侥,一旦項目比較大,你會發(fā)現(xiàn)鸳君,Activity所承擔(dān)的任務(wù)其實是很重的农渊,它既要負(fù)責(zé)頁面的展示和交互,還得負(fù)責(zé)數(shù)據(jù)的請求和業(yè)務(wù)邏輯之類的工作或颊,相當(dāng)于既要打理家庭砸紊,又要教育自己調(diào)皮的孩子,真是又當(dāng)?shù)之?dāng)媽囱挑。醉顽。。那該怎么辦呢平挑?這時候Presenter這個繼父來到了這個家庭徽鼎。Presenter對Activity說,我來了弹惦,以后你就別這么辛苦了否淤,你就好好打理好View這個家,我專門來負(fù)責(zé)教育Model這孩子棠隐,有什么情況我會向你反映的石抡。這時Activity流下了幸福的眼淚,從此助泽,Model啰扛、View(Activity)、Presenter一家三口過上了幸福的生活嗡贺。隐解。。好了磕個藥繼續(xù)诫睬,由于Presenter(我們自己建的類)的出現(xiàn)煞茫,可以使View(Activity)不用直接和Model打交道,View(Activity)只用負(fù)責(zé)頁面的顯示和交互摄凡,剩下的和Model交互的事情都交給Presenter做续徽,比如一些網(wǎng)絡(luò)請求、數(shù)據(jù)的獲取等亲澡,當(dāng)Presenter獲取到數(shù)據(jù)后再交給View(Activity)進(jìn)行展示钦扭,這樣,Activity的任務(wù)就大大減小了床绪。這便是MVP(Model 還是指的數(shù)據(jù)邏輯和實體模型客情,View指的是Activity其弊,P就是Presenter)框架的工作方式。
2.什么是Retrofit膀斋?
接下來我們看一下什么是Retrofit梭伐。在官網(wǎng)對Retrofit的描述是這樣的
A type-safe HTTP client for Android and Java
說人話就是“一個類型安全的用于Android和Java網(wǎng)絡(luò)請求的客戶端”,其實就是一個封裝好的網(wǎng)絡(luò)請求庫概页。接下來就來看一下這個庫該怎么用籽御。首先我在網(wǎng)上找了一個API接口用于測試:https://api.douban.com/v2/book/search?q=金瓶梅&tag=&start=0&count=1
這是一個用于查詢一本書詳細(xì)信息的一個請求接口练慕。如果直接用瀏覽器打開的話會返回以下內(nèi)容:
接下來我們來看看如何用Retrofit將上面的請求下來惰匙。為了在Android Studio中添加Retrofit庫,我們需要添加如下依賴:
compile 'com.squareup.retrofit2:retrofit:2.1.0'
好了铃将,添加完該庫项鬼,我們再來看看如何使用,首先我們來建一個實體類Book劲阎,用于裝網(wǎng)絡(luò)請求后返回的數(shù)據(jù)绘盟。這里順帶說一下,有的人建一個實體類時可能會根據(jù)瀏覽器中返回中的數(shù)據(jù)一行一行敲悯仙,其實這樣非常麻煩龄毡,這里教大家一個簡單的方法,瞬間生成一個實體類锡垄。沒錯有的人可能用過沦零,我們需要一個插件GsonFormat
。它的使用也很簡單货岭,首先需要在Android Studio中下載路操,點擊左上角菜單欄中的File
,然后點擊Settings
,在彈窗中選擇Plugins
,然后點擊下方的Browse repositories...
然后在新打開的窗口中搜索GsonFormat
千贯,點擊右側(cè)綠色按鈕就可以下載安裝了屯仗,安裝完需要重啟下studio,就可以用了搔谴。
它的用法也很簡單魁袜,比如你先建立一個新的空類取名Book,然后在里面按Alt+insert
,會有個小彈窗選擇GsonFormat
,之后在彈出的編輯框中拷入在瀏覽器中請求下來的那一坨東西敦第,然后一直點ok就會自動生成字段慌核,以及set和get方法,一會兒我們用Retrofit請求下來的數(shù)據(jù)都會保存在這個實體類中申尼,還是挺方便的垮卓。最后我們里面添加一個toString()方法,用于后面顯示方便师幕。
接下來粟按,回到我們的Retrofit中上诬滩,實體類已經(jīng)建好了,我們來看看這個Retrofit如何進(jìn)行網(wǎng)絡(luò)請求灭将,其實代碼也很簡單疼鸟。首先我們需要定義一個接口,取名RetrofitService :
public interface RetrofitService {
@GET("book/search")
Call<Book> getSearchBook(@Query("q") String name,
@Query("tag") String tag,
@Query("start") int start,
@Query("count") int count);
}
額庙曙。空镜。想必有人要問了,這是什么玩意捌朴?跟我們平時定義的接口類很像吴攒,但又不一樣。別心急砂蔽,我來一一解釋下洼怔,和別的接口類一樣,我們在其中定義了一個方法getSearchBook
左驾,那么這個方法是做什么的呢镣隶?其實它干的事很簡單,就是拼接一個URL然后進(jìn)行網(wǎng)絡(luò)請求诡右。這里我們拼接的URL就是上文提到的測試URL:https://api.douban.com/v2/book/search?q=金瓶梅&tag=&start=0&count=1
安岂。聰明的你一定看出來了,在這個URL中book/search就是GET后的值帆吻,而?后的q域那、tag、start桅锄、count等入?yún)⒕褪沁@個方法的入?yún)⒘瘀āS械呐笥芽赡芤獑柫耍?code>https://api.douban.com/v2/這么一大串跑哪去了?其實我們在進(jìn)行網(wǎng)絡(luò)請求時友瘤,在URL中前一部分是相對不變的翠肘。什么意思呢,比如你打開間書
網(wǎng)站辫秧,在間書
中你打開不同的網(wǎng)頁束倍,雖然它的URL不同,但你會發(fā)現(xiàn)盟戏,每個URL前面都是以http://www.reibang.com/
開頭,我們把這個不變的部分绪妹,也叫做baseUrl提出來,放到另一個地方,在下面我們會提到柿究。這樣我們一個完整的URL就拼接好了邮旷。在方法的開頭我們可以看到有個GET的注釋,說明這個請求是GET方法蝇摸,當(dāng)然你也可以根據(jù)具體需要用POST婶肩、PUT办陷、DELETE以及HEAD。他們的區(qū)別如下:
- GET ----------查找資源(查)
- POST --------修改資源(改)
- PUT ----------上傳文件(增)
- DELETE ----刪除文件(刪)
- HEAD--------只請求頁面的首部
然后我們來看一下這個方法的返回值律歼,它返回Call實體民镜,一會我們要用它進(jìn)行具體的網(wǎng)絡(luò)請求,我們需要為它指定泛型為Book也就是我們數(shù)據(jù)的實體類险毁。接下來制圈,你會發(fā)現(xiàn)這個方法的入?yún)⒑臀覀兤綍r方法的入?yún)⑦€不大一樣。在每個入?yún)⑶斑€多了一個注解畔况。比如第一個入?yún)?code>@Query("q") String name鲸鹦,Query
表示把你傳入的字段拼接起來,比如在測試url中我們可以看到q=金瓶梅
的入?yún)⑽是裕敲?code>Query后面的值必須是q亥鬓,要和url中保持不變完沪,然后我們定義了String類型的name域庇,當(dāng)調(diào)用這個方法是,用于傳入字符串覆积,比如可以傳入“金瓶梅”听皿。那么這個方法就會自動在q后面拼上這個字符串進(jìn)行網(wǎng)絡(luò)請求。以此類推宽档,這個url需要幾個入?yún)⒛憔驮谶@個方法中定義幾個入?yún)⑽疽蹋總€入?yún)⑶岸家由?code>Query注解。當(dāng)然Retrofit除了Query這個注解外吗冤,還有其他幾個比如:@QueryMap又厉、@Path、@Body椎瘟、@FormUrlEncoded/@Field覆致、@Header/@Headers。我們來看一下他們的區(qū)別:
@Query(GET請求):
用于在url后拼接上參數(shù)肺蔚,例如:
@GET("book/search")
Call<Book> getSearchBook(@Query("q") String name);//name由調(diào)用者傳入
相當(dāng)于:
@GET("book/search?q=name")
Call<Book> getSearchBook();
@QueryMap(GET請求):
當(dāng)然如果入?yún)⒈容^多煌妈,就可以把它們都放在Map中,例如:
@GET("book/search")
Call<Book> getSearchBook(@QueryMap Map<String, String> options);
@Path(GET請求):
用于替換url中某個字段宣羊,例如:
@GET("group/{id}/users")
Call<Book> groupList(@Path("id") int groupId);
像這種請求接口璧诵,在group和user之間有個不確定的id值需要傳入,就可以這種方法仇冯。我們把待定的值字段用{}
括起來之宿,當(dāng)然 {}
里的名字不一定就是id,可以任取苛坚,但需和@Path
后括號里的名字一樣比被。如果在user后面還需要傳入?yún)?shù)的話坪创,就可以用Query拼接上,比如:
@GET("group/{id}/users")
Call<Book> groupList(@Path("id") int groupId,@Query("sort") String sort);
當(dāng)我們調(diào)用這個方法時姐赡,假設(shè)我們groupId傳入1莱预,sort傳入“2”,那么它拼接成的url就是group/1/users?sort=2
项滑,當(dāng)然最后請求的話還會加上前面的baseUrl依沮。
@Body(POST請求):
可以指定一個對象作為HTTP請求體,比如:
@POST("users/new")
Call<User> createUser(@Body User user);
它會把我們傳入的User實體類轉(zhuǎn)換為用于傳輸?shù)腍TTP請求體,進(jìn)行網(wǎng)絡(luò)請求枪狂。
@Field(POST請求):
用于傳送表單數(shù)據(jù):
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
注意開頭必須多加上@FormUrlEncoded
這句注釋危喉,不然會報錯。表單自然是有多組鍵值對組成州疾,這里的first_name就是鍵辜限,而具體傳入的first就是值啦。
@Header/@Headers(POST請求):
用于添加請求頭部:
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
表示將頭部Authorization屬性設(shè)置為你傳入的authorization严蓖;當(dāng)然你還可以用@Headers表示,作用是一樣的比如:
@Headers("Cache-Control: max-age=640000")
@GET("user")
Call<User> getUser()
當(dāng)然你可以多個設(shè)置:
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("user")
Call<User> getUser()
好了薄嫡,這樣我們就把上面這個RetrofitService 接口類解釋的差不多了。我覺得颗胡,Retrofit最主要的也就是這個接口類的定義了毫深。好了,有了這個接口類毒姨,我們來看一下哑蔫,到底如何使用這個我們定義的接口來進(jìn)行網(wǎng)絡(luò)請求。代碼如下:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.douban.com/v2/")
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
.build();
RetrofitService service = retrofit.create(RetrofitService.class);
Call<Book> call = service.getSearchBook("金瓶梅", null, 0, 1);
call.enqueue(new Callback<Book>() {
@Override
public void onResponse(Call<Book> call, Response<Book> response) {
text.setText(response.body()+"");
}
@Override
public void onFailure(Call<Book> call, Throwable t) {
}
});
這里我們可以看到弧呐,先新建了一個Retrofit對象闸迷,然后給它設(shè)置一個我們前面說的baseUrlhttps://api.douban.com/v2/
.因為接口返回的數(shù)據(jù)不是我們需要的實體類,我們需要調(diào)用addConverterFactory方法進(jìn)行轉(zhuǎn)換俘枫。由于返回的數(shù)據(jù)為json類型腥沽,所以在這個方法中傳入Gson轉(zhuǎn)換工廠GsonConverterFactory.create(new GsonBuilder().create())
,這里我們需要在studio中添加Gson的依賴:
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
然后我們調(diào)用retrofit的create方法并傳入上面我們定義的接口的文件名RetrofitService.class崩哩,就可以得到RetrofitService 的實體對象巡球。有了這個對象,我們就可以調(diào)用里面之前定義好的請求方法了邓嘹。比如:
Call<Book> call = service.getSearchBook("金瓶梅", null, 0, 1);
它會返回一個Call實體類酣栈,然后就可以調(diào)用Call的enqueue方法進(jìn)行異步請求,在enqueue方法中傳入一個回調(diào)CallBack汹押,重寫里面的onResponse和
onFailure方法矿筝,也就是請求成功和失敗的回調(diào)方法。當(dāng)成功時棚贾,它會返回Response窖维,里邊封裝了請求結(jié)果的所有信息榆综,包括報頭,返回碼铸史,還有主體等鼻疮。比如調(diào)用它的body()方法就可獲得Book對象,也就是我們需要的數(shù)據(jù)琳轿。這里我們就把返回的Book判沟,顯示屏幕上。如下圖:
好了崭篡,到這里我們就基本了解了Retrofit的整個工作流程挪哄。
3.RxJava
我們這篇文章主要介紹搭建整體網(wǎng)絡(luò)請求框架,所以關(guān)于RxJava的基礎(chǔ)知識琉闪,我這就不再詳細(xì)介紹了迹炼,網(wǎng)上也有很多文章,對RxJava還不是很了解的同學(xué)颠毙,推薦你看一下扔物線的這篇文章給 Android 開發(fā)者的 RxJava 詳解
下面我們來看一下RxJava和retrofit的結(jié)合使用斯入,為了使Rxjava與retrofit結(jié)合,我們需要在Retrofit對象建立的時候添加一句代碼addCallAdapterFactory(RxJavaCallAdapterFactory.create())
吟秩,當(dāng)然你還需要在build.gradle文件中添加如下依賴:
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
完整的代碼如下:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.douban.com/v2/")
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//支持RxJava
.build();
然后我們還需要修改RetrofitService 中的代碼:
public interface RetrofitService {
@GET("book/search")
Observable<Book> getSearchBook(@Query("q") String name,
@Query("tag") String tag, @Query("start") int start,
@Query("count") int count);
可以看到咱扣,在原來的RetrofitService 中我們把getSearchBook方法返回的類型Call改為了Observable绽淘,也就是被觀察者涵防。其他都沒變。然后就是創(chuàng)建RetrofitService 實體類:
RetrofitService service = retrofit.create(RetrofitService.class);
和上面一樣沪铭,創(chuàng)建完RetrofitService 壮池,就可以調(diào)用里面的方法了:
Observable<Book> observable = service.getSearchBook("金瓶梅", null, 0, 1);
其實這一步,就是創(chuàng)建了一個rxjava中observable杀怠,即被觀察者,有了被觀察者椰憋,就需要一個觀察者,且訂閱它:
observable.subscribeOn(Schedulers.io())//請求數(shù)據(jù)的事件發(fā)生在io線程
.observeOn(AndroidSchedulers.mainThread())//請求完成后在主線程更顯UI
.subscribe(new Observer<Book>() {//訂閱
@Override
public void onCompleted() {
//所有事件都完成赔退,可以做些操作橙依。。硕旗。
}
@Override
public void onError(Throwable e) {
e.printStackTrace(); //請求過程中發(fā)生錯誤
}
@Override
public void onNext(Book book) {//這里的book就是我們請求接口返回的實體類
}
}
在上面中我們可以看到窗骑,事件的消費在Android主線程,所以我們還要在build.gradle中添加如下依賴:
compile 'io.reactivex:rxandroid:1.2.0'
這樣我們就引入了RxAndroid漆枚,RxAndroid其實就是對RxJava的擴(kuò)展创译。比如上面這個Android主線程在RxJava中就沒有,因此要使用的話就必須得引用RxAndroid墙基。
4.實踐
接下來我們就看看软族,在一個項目中上面三者是如何配合的刷喜。我們打開Android Studio,新建一個項目取名為MVPDemo立砸。這個demo的功能也很簡單掖疮,就是點擊按鈕調(diào)用上面的那個測試接口,將請求下來書的信息顯示在屏幕上颗祝。首先我們來看一下這個工程的目錄結(jié)構(gòu):
我們可以看到氮墨,在項目的包名下,我們建了三個主要的文件夾:app吐葵、service规揪、ui。當(dāng)然根據(jù)項目的需要你也可以添加更多其他的文件夾温峭,比如一些工具類等猛铅。其中app文件夾中可以建一個Application類,用于設(shè)置應(yīng)用全局的一些屬性凤藏,這里為了使項目更加簡單就沒有添加奸忽;然后,我們再來看看ui文件夾下揖庄,這個文件夾下主要放一些關(guān)于界面的東西栗菜。在里面我們又建了三個文件夾:activity、adapter蹄梢、fragment疙筹,我想看名字你就清楚里面要放什么了。最后我們在重點看看service文件夾中的東西禁炒。首先我們來看看里面重要的兩個類:RetrofitHelper和RetrofitService而咆。RetrofitHelper主要用于Retrofit的初始化:
public class RetrofitHelper {
private Context mCntext;
OkHttpClient client = new OkHttpClient();
GsonConverterFactory factory = GsonConverterFactory.create(new GsonBuilder().create());
private static RetrofitHelper instance = null;
private Retrofit mRetrofit = null;
public static RetrofitHelper getInstance(Context context){
if (instance == null){
instance = new RetrofitHelper(context);
}
return instance;
}
private RetrofitHelper(Context mContext){
mCntext = mContext;
init();
}
private void init() {
resetApp();
}
private void resetApp() {
mRetrofit = new Retrofit.Builder()
.baseUrl("https://api.douban.com/v2/")
.client(client)
.addConverterFactory(factory)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
public RetrofitService getServer(){
return mRetrofit.create(RetrofitService.class);
}
}
代碼并不復(fù)雜,其中resetApp方法幕袱,就是前面介紹的Retrofit的創(chuàng)建暴备,getServer方法就是為了獲取RetrofitService接口類的實例化。然后定義了一個靜態(tài)方法getInstance用于獲取自身RetrofitHelper的實例化们豌,并且只會實例化一次涯捻。
接下來,看一下RetrofitService望迎,其中代碼還是上面一樣:
public interface RetrofitService {
@GET("book/search")
Observable<Book> getSearchBooks(@Query("q") String name,
@Query("tag") String tag, @Query("start") int start,
@Query("count") int count);
}
然后我們依次來看一下service文件夾下的四個文件夾:entity障癌、manager、presenter和view擂煞。其中entity下放我們請求的實體類混弥,這里就是Book。接下來我們來看一下manager中DataManager。這個類其實就是為了讓你更方便的調(diào)用RetrofitService 中定義的方法:
public class DataManager {
private RetrofitService mRetrofitService;
public DataManager(Context context){
this.mRetrofitService = RetrofitHelper.getInstance(context).getServer();
}
public Observable<Book> getSearchBooks(String name,String tag,int start,int count){
return mRetrofitService.getSearchBooks(name,tag,start,count);
}
}
可以看到蝗拿,在它的構(gòu)造方法中晾捏,我們得到了RetrofitService 的實例化,然后定義了一個和RetrofitService 中同名的方法哀托,里面其實就是調(diào)用RetrofitService 中的這個方法惦辛。這樣,把RetrofitService 中定義的方法都封裝到DataManager 中仓手,以后無論在哪個要調(diào)用方法時直接在DataManager 中調(diào)用就可以了胖齐,而不是重復(fù)建立RetrofitService 的實例化,再調(diào)用其中的方法嗽冒。
好了呀伙,我們再來看一下presenter和view,我們在前面說過添坊,presenter主要用于網(wǎng)絡(luò)的請求以及數(shù)據(jù)的獲取剿另,view就是將presenter獲取到的數(shù)據(jù)進(jìn)行展示。首先我們先來看view贬蛙,我們看到我們建了兩個接口類View和BookView,其中View是空的雨女,主要用于和Android中的View區(qū)別開來:
public interface View {
}
然后讓BookView繼承自我們自己定義的View :
public interface BookView extends View {
void onSuccess(Book mBook);
void onError(String result);
}
可以看到在里面定義兩個方法,一個onSuccess阳准,如果presenter請求成功氛堕,將向該方法傳入請求下來的實體類,也就是Book野蝇,view拿到這個數(shù)據(jù)實體類后讼稚,就可以進(jìn)行關(guān)于這個數(shù)據(jù)的展示或其他的一些操作。如果請求失敗浪耘,就會向這個view傳入失敗信息乱灵,你可以彈個Toast來提示請求失敗。通常這兩個方法比較常用七冲,當(dāng)然你可以根據(jù)項目需要來定義一些其他的方法。接下來我們看看presenter是如何進(jìn)行網(wǎng)絡(luò)請求的 规婆。我們也定義了一個基礎(chǔ)Presenter:
public interface Presenter {
void onCreate();
void onStart();//暫時沒用到
void onStop();
void pause();//暫時沒用到
void attachView(View view);
void attachIncomingIntent(Intent intent);//暫時沒用到
}
里面我們可以看到澜躺,定義了一些方法,前面幾個onCreate抒蚜、onStart等方法對應(yīng)著Activity中生命周期的方法掘鄙,當(dāng)然沒必要寫上Activity生命周期中所有回調(diào)方法,通常也就用到了onCreate和onStop嗡髓,除非需求很復(fù)雜操漠,在Activity不同生命周期請求的情況不同。接著我們定義了一個attachView方法,用于綁定我們定義的View浊伙。也就是撞秋,你想把請求下來的數(shù)據(jù)實體類給哪個View就傳入哪個View。下面這個attachIncomingIntent暫且沒用到嚣鄙,就不說了吻贿。好了,我們來看一下BookPresenter具體是怎么實現(xiàn)的:
public class BookPresenter implements Presenter {
private DataManager manager;
private CompositeSubscription mCompositeSubscription;
private Context mContext;
private BookView mBookView;
private Book mBook;
public BookPresenter (Context mContext){
this.mContext = mContext;
}
@Override
public void onCreate() {
manager = new DataManager(mContext);
mCompositeSubscription = new CompositeSubscription();
}
@Override
public void onStart() {
}
@Override
public void onStop() {
if (mCompositeSubscription.hasSubscriptions()){
mCompositeSubscription.unsubscribe();
}
}
@Override
public void pause() {
}
@Override
public void attachView(View view) {
mBookView = (BookView)view;
}
@Override
public void attachIncomingIntent(Intent intent) {
}
public void getSearchBooks(String name,String tag,int start,int count){
mCompositeSubscription.add(manager.getSearchBooks(name,tag,start,count)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Book>() {
@Override
public void onCompleted() {
if (mBook != null){
mBookView.onSuccess(mBook);
}
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
mBookView.onError("請求失斞谱印>肆小!");
}
@Override
public void onNext(Book book) {
mBook = book;
}
})
);
}
}
BookPresenter實現(xiàn)了我們定義的基礎(chǔ)Presenter卧蜓,在onCreate中我們創(chuàng)建了DataManager的實體類帐要,便于調(diào)用RetrofitService中的方法,還新建了一個CompositeSubscription對象弥奸,CompositeSubscription是用來存放RxJava中的訂閱關(guān)系的宠叼。注意請求完數(shù)據(jù)要及時清掉這個訂閱關(guān)系,不然會發(fā)生內(nèi)存泄漏其爵∶岸可在onStop中通過調(diào)用CompositeSubscription的unsubscribe方法來取消這個訂閱關(guān)系,不過一旦調(diào)用這個方法摩渺,那么這個CompositeSubscription也就無法再用了简烤,要想再用只能重新new一個。然后我們可以看到在attachView中摇幻,我們把BookView傳進(jìn)去横侦。也就是說我們要把請求下來的實體類交給BookView來處理。接下來我們定義了一個方法getSearchBooks绰姻,名字和入?yún)⒍己驼埱蠼涌赗etrofitService中的方法相同枉侧。這里的這個方法也就是請求的具體實現(xiàn)過程。其實也很簡單狂芋,就是向CompositeSubscription添加一個訂閱關(guān)系榨馁。上面我們已經(jīng)說過manager.getSearchBooks就是調(diào)用RetrofitService的getSearchBooks方法,而這個方法返回的是一個泛型為Book的Observable帜矾,即被觀察者翼虫,然后通過subscribeOn(Schedulers.io())來定義請求事件發(fā)生在io線程,然后通過observeOn(AndroidSchedulers.mainThread())來定義事件在主線程消費屡萤,即在主線程進(jìn)行數(shù)據(jù)的處理珍剑,最后通過subscribe使觀察者訂閱它。在觀察者中有三個方法:onNext死陆、onCompleted招拙、onError。當(dāng)請求成功話,就會調(diào)用onNext别凤,并傳入請求返回的Book實體類饰序,我們在onNext中,把請求下來的Book實體類存到內(nèi)存中闻妓,當(dāng)請求結(jié)束后會調(diào)用onCompleted菌羽,我們把請求下來的Book實體類交給BookView處理就可以了,如果請求失敗由缆,那么不會調(diào)用onCompleted而調(diào)用onError注祖,這樣我們可以向BookView傳遞錯誤消息。
好了均唉,這樣我們我們就可以調(diào)用這個接口方法來進(jìn)行網(wǎng)絡(luò)的請求了是晨,我們先寫一下頁面的布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:orientation="vertical"
android:paddingTop="@dimen/activity_vertical_margin">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<Button
android:id="@+id/button"
android:onClick="getFollowers"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="請求"/>
</LinearLayout>
界面很簡單,一共兩個控件舔箭,一個Button罩缴,點擊時進(jìn)行網(wǎng)絡(luò)請求,一個TextView层扶,用于顯示請求下來的數(shù)據(jù)箫章。然后我么看一下Activity中代碼:
public class MainActivity extends AppCompatActivity {
private TextView text;
private Button button;
private BookPresenter mBookPresenter = new BookPresenter(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView)findViewById(R.id.text);
button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mBookPresenter.getSearchBooks("金瓶梅", null, 0, 1);
}
});
mBookPresenter.onCreate();
mBookPresenter.attachView(mBookView);
}
private BookView mBookView = new BookView() {
@Override
public void onSuccess(Book mBook) {
text.setText(mBook.toString());
}
@Override
public void onError(String result) {
Toast.makeText(MainActivity.this,result, Toast.LENGTH_SHORT).show();
}
};
@Override
protected void onDestroy(){
super.onDestroy();
mBookPresenter.onStop();
}
}
邏輯并不復(fù)雜,我們先創(chuàng)建了一個BookPresenter 對象镜会,然后調(diào)用它的onCreate方法進(jìn)行初始化檬寂,接著調(diào)用attachView來綁定BookView。BookView的實現(xiàn)也很簡單戳表,在onSuccess方法中將Book 中內(nèi)容顯示在TextView上桶至,在onError中彈出一個Toast提示。然后點擊按鈕的時候就調(diào)用BookPresenter中g(shù)etSearchBooks方法匾旭,同時傳入必要的入?yún)⒘鸵佟_@樣網(wǎng)絡(luò)請求就開始了,如果請求成功就會回調(diào)BookView 中的onSuccess方法价涝,失敗就回調(diào)onError方法女蜈。當(dāng)活動銷毀時記得調(diào)用BookPresenter的onStop方法來釋放訂閱關(guān)系,防止內(nèi)存泄漏飒泻。
最后別忘了在AndroidManifest中添加網(wǎng)絡(luò)權(quán)限:
<uses-permission android:name="android.permission.INTERNET"/>
好了鞭光,我們運行一下看一下效果:
代碼已上傳github:MVPDemo