Android 教你一步步搭建MVP+Retrofit+RxJava網(wǎng)絡(luò)請(qǐng)求框架

Android 教你一步步搭建MVP+Retrofit+RxJava網(wǎng)絡(luò)請(qǐng)求框架

轉(zhuǎn)載請(qǐng)注明出處http://m.itdecent.cn/p/7b839b7c5884

之前公司的項(xiàng)目用到了MVP+Retrofit+RxJava的框架進(jìn)行網(wǎng)絡(luò)請(qǐng)求,所以今天特此寫(xiě)一篇文章以做總結(jié)。相信很多人都聽(tīng)說(shuō)過(guò)MVP、Retrofit、以及RxJava,有的人已經(jīng)開(kāi)始用了,有的人可能還不知道這是什么,以及到底怎么用。不過(guò)沒(méi)關(guān)系,接下來(lái)我將為你一一揭開(kāi)他們的神秘面紗,然后利用這三個(gè)家伙搭建一個(gè)網(wǎng)絡(luò)請(qǐng)求框架

1.什么是MVP?

MVP(Model View Presenter)其實(shí)就是一種項(xiàng)目的整體框架,能讓你的代碼變得更加簡(jiǎn)潔,說(shuō)起框架大家可能還會(huì)想到MVC、MVVM。由于篇幅原因,這里我們先不講MVVM,先來(lái)看一下MVC。其實(shí)Android本身就采用的是MVC(Model View Controllor)模式、其中Model指的是數(shù)據(jù)邏輯和實(shí)體模型;View指的是布局文件、Controllor指的是Activity。對(duì)于很多Android初學(xué)者可能會(huì)有這樣的經(jīng)歷,寫(xiě)代碼的時(shí)候,不管三七二十一都往Activity中寫(xiě),當(dāng)然我當(dāng)初也是這么干的,根本就沒(méi)有什么框架的概念,只要能實(shí)現(xiàn)某一個(gè)功能就很開(kāi)心了,沒(méi)有管這么多。當(dāng)然項(xiàng)目比較小還好,一旦項(xiàng)目比較大,你會(huì)發(fā)現(xiàn),Activity所承擔(dān)的任務(wù)其實(shí)是很重的,它既要負(fù)責(zé)頁(yè)面的展示和交互,還得負(fù)責(zé)數(shù)據(jù)的請(qǐng)求和業(yè)務(wù)邏輯之類(lèi)的工作,相當(dāng)于既要打理家庭,又要教育自己調(diào)皮的孩子,真是又當(dāng)?shù)之?dāng)媽。。。那該怎么辦呢?這時(shí)候Presenter這個(gè)繼父來(lái)到了這個(gè)家庭。Presenter對(duì)Activity說(shuō),我來(lái)了,以后你就別這么辛苦了,你就好好打理好View這個(gè)家,我專(zhuān)門(mén)來(lái)負(fù)責(zé)教育Model這孩子,有什么情況我會(huì)向你反映的。這時(shí)Activity流下了幸福的眼淚,從此,Model、View(Activity)、Presenter一家三口過(guò)上了幸福的生活。。。好了磕個(gè)藥繼續(xù),由于Presenter(我們自己建的類(lèi))的出現(xiàn),可以使View(Activity)不用直接和Model打交道,View(Activity)只用負(fù)責(zé)頁(yè)面的顯示和交互,剩下的和Model交互的事情都交給Presenter做,比如一些網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)的獲取等,當(dāng)Presenter獲取到數(shù)據(jù)后再交給View(Activity)進(jìn)行展示,這樣,Activity的任務(wù)就大大減小了。這便是MVP(Model 還是指的數(shù)據(jù)邏輯和實(shí)體模型,View指的是Activity,P就是Presenter)框架的工作方式。

2.什么是Retrofit?

接下來(lái)我們看一下什么是Retrofit。在官網(wǎng)對(duì)Retrofit的描述是這樣的

A type-safe HTTP client for Android and Java說(shuō)人話(huà)就是“一個(gè)類(lèi)型安全的用于Android和Java網(wǎng)絡(luò)請(qǐng)求的客戶(hù)端”,其實(shí)就是一個(gè)封裝好的網(wǎng)絡(luò)請(qǐng)求庫(kù)。接下來(lái)就來(lái)看一下這個(gè)庫(kù)該怎么用。首先我在網(wǎng)上找了一個(gè)API接口用于測(cè)試:https://api.douban.com/v2/book/search?q=金瓶梅&tag=&start=0&count=1這是一個(gè)用于查詢(xún)一本書(shū)詳細(xì)信息的一個(gè)請(qǐng)求接口。如果直接用瀏覽器打開(kāi)的話(huà)會(huì)返回以下內(nèi)容:

瀏覽器中返回內(nèi)容

接下來(lái)我們來(lái)看看如何用Retrofit將上面的請(qǐng)求下來(lái)。為了在Android Studio中添加Retrofit庫(kù),我們需要添加如下依賴(lài):

compile 'com.squareup.retrofit2:retrofit:2.1.0'

好了,添加完該庫(kù),我們?cè)賮?lái)看看如何使用,首先我們來(lái)建一個(gè)實(shí)體類(lèi)Book,用于裝網(wǎng)絡(luò)請(qǐng)求后返回的數(shù)據(jù)。這里順帶說(shuō)一下,有的人建一個(gè)實(shí)體類(lèi)時(shí)可能會(huì)根據(jù)瀏覽器中返回中的數(shù)據(jù)一行一行敲,其實(shí)這樣非常麻煩,這里教大家一個(gè)簡(jiǎn)單的方法,瞬間生成一個(gè)實(shí)體類(lèi)。沒(méi)錯(cuò)有的人可能用過(guò),我們需要一個(gè)插件GsonFormat。它的使用也很簡(jiǎn)單,首先需要在Android Studio中下載,點(diǎn)擊左上角菜單欄中的File,然后點(diǎn)擊Settings,在彈窗中選擇Plugins,然后點(diǎn)擊下方的Browse repositories...

然后在新打開(kāi)的窗口中搜索GsonFormat,點(diǎn)擊右側(cè)綠色按鈕就可以下載安裝了,安裝完需要重啟下studio,就可以用了。

它的用法也很簡(jiǎn)單,比如你先建立一個(gè)新的空類(lèi)取名Book,然后在里面按Alt+insert,會(huì)有個(gè)小彈窗選擇GsonFormat,之后在彈出的編輯框中拷入在瀏覽器中請(qǐng)求下來(lái)的那一坨東西,然后一直點(diǎn)ok就會(huì)自動(dòng)生成字段,以及set和get方法,一會(huì)兒我們用Retrofit請(qǐng)求下來(lái)的數(shù)據(jù)都會(huì)保存在這個(gè)實(shí)體類(lèi)中,還是挺方便的。最后我們里面添加一個(gè)toString()方法,用于后面顯示方便。

接下來(lái),回到我們的Retrofit中上,實(shí)體類(lèi)已經(jīng)建好了,我們來(lái)看看這個(gè)Retrofit如何進(jìn)行網(wǎng)絡(luò)請(qǐng)求,其實(shí)代碼也很簡(jiǎn)單。首先我們需要定義一個(gè)接口,取名RetrofitService :

publicinterfaceRetrofitService{@GET("book/search")CallgetSearchBook(@Query("q")String name,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Query("tag")String tag,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Query("start")intstart,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Query("count")intcount);}

額。。想必有人要問(wèn)了,這是什么玩意?跟我們平時(shí)定義的接口類(lèi)很像,但又不一樣。別心急,我來(lái)一一解釋下,和別的接口類(lèi)一樣,我們?cè)谄渲卸x了一個(gè)方法getSearchBook,那么這個(gè)方法是做什么的呢?其實(shí)它干的事很簡(jiǎn)單,就是拼接一個(gè)URL然后進(jìn)行網(wǎng)絡(luò)請(qǐng)求。這里我們拼接的URL就是上文提到的測(cè)試URL:https://api.douban.com/v2/book/search?q=金瓶梅&tag=&start=0&count=1。聰明的你一定看出來(lái)了,在這個(gè)URL中book/search就是GET后的值,而?后的q、tag、start、count等入?yún)⒕褪沁@個(gè)方法的入?yún)?。有的朋友可能要?wèn)了,https://api.douban.com/v2/這么一大串跑哪去了?其實(shí)我們?cè)谶M(jìn)行網(wǎng)絡(luò)請(qǐng)求時(shí),在URL中前一部分是相對(duì)不變的。什么意思呢,比如你打開(kāi)間書(shū)網(wǎng)站,在間書(shū)中你打開(kāi)不同的網(wǎng)頁(yè),雖然它的URL不同,但你會(huì)發(fā)現(xiàn),每個(gè)URL前面都是以http://m.itdecent.cn/開(kāi)頭,我們把這個(gè)不變的部分,也叫做baseUrl提出來(lái),放到另一個(gè)地方,在下面我們會(huì)提到。這樣我們一個(gè)完整的URL就拼接好了。在方法的開(kāi)頭我們可以看到有個(gè)GET的注釋?zhuān)f(shuō)明這個(gè)請(qǐng)求是GET方法,當(dāng)然你也可以根據(jù)具體需要用POST、PUT、DELETE以及HEAD。他們的區(qū)別如下:

GET ----------查找資源(查)

POST --------修改資源(改)

PUT ----------上傳文件(增)

DELETE ----刪除文件(刪)

HEAD--------只請(qǐng)求頁(yè)面的首部

然后我們來(lái)看一下這個(gè)方法的返回值,它返回Call實(shí)體,一會(huì)我們要用它進(jìn)行具體的網(wǎng)絡(luò)請(qǐng)求,我們需要為它指定泛型為Book也就是我們數(shù)據(jù)的實(shí)體類(lèi)。接下來(lái),你會(huì)發(fā)現(xiàn)這個(gè)方法的入?yún)⒑臀覀兤綍r(shí)方法的入?yún)⑦€不大一樣。在每個(gè)入?yún)⑶斑€多了一個(gè)注解。比如第一個(gè)入?yún)Query("q") String name,Query表示把你傳入的字段拼接起來(lái),比如在測(cè)試url中我們可以看到q=金瓶梅的入?yún)ⅲ敲碤uery后面的值必須是q,要和url中保持不變,然后我們定義了String類(lèi)型的name,當(dāng)調(diào)用這個(gè)方法是,用于傳入字符串,比如可以傳入“金瓶梅”。那么這個(gè)方法就會(huì)自動(dòng)在q后面拼上這個(gè)字符串進(jìn)行網(wǎng)絡(luò)請(qǐng)求。以此類(lèi)推,這個(gè)url需要幾個(gè)入?yún)⒛憔驮谶@個(gè)方法中定義幾個(gè)入?yún)?,每個(gè)入?yún)⑶岸家由螿uery注解。當(dāng)然Retrofit除了Query這個(gè)注解外,還有其他幾個(gè)比如:@QueryMap、@Path、@Body、@FormUrlEncoded/@Field、@Header/@Headers。我們來(lái)看一下他們的區(qū)別:

@Query(GET請(qǐng)求):

用于在url后拼接上參數(shù),例如:

@GET("book/search")CallgetSearchBook(@Query("q")String name);//name由調(diào)用者傳入

相當(dāng)于:

@GET("book/search?q=name")Call getSearchBook();

@QueryMap(GET請(qǐng)求):

當(dāng)然如果入?yún)⒈容^多,就可以把它們都放在Map中,例如:

@GET("book/search")Call getSearchBook(@QueryMapMap options);

@Path(GET請(qǐng)求):

用于替換url中某個(gè)字段,例如:

@GET("group/{id}/users")CallgroupList(@Path("id")intgroupId);

像這種請(qǐng)求接口,在group和user之間有個(gè)不確定的id值需要傳入,就可以這種方法。我們把待定的值字段用{}括起來(lái),當(dāng)然?{}里的名字不一定就是id,可以任取,但需和@Path后括號(hào)里的名字一樣。如果在user后面還需要傳入?yún)?shù)的話(huà),就可以用Query拼接上,比如:

@GET("group/{id}/users")CallgroupList(@Path("id")intgroupId,@Query("sort")String sort);

當(dāng)我們調(diào)用這個(gè)方法時(shí),假設(shè)我們groupId傳入1,sort傳入“2”,那么它拼接成的url就是group/1/users?sort=2,當(dāng)然最后請(qǐng)求的話(huà)還會(huì)加上前面的baseUrl。

@Body(POST請(qǐng)求):

可以指定一個(gè)對(duì)象作為HTTP請(qǐng)求體,比如:

@POST("users/new")Call createUser(@Body User user);

它會(huì)把我們傳入的User實(shí)體類(lèi)轉(zhuǎn)換為用于傳輸?shù)腍TTP請(qǐng)求體,進(jìn)行網(wǎng)絡(luò)請(qǐng)求。

@Field(POST請(qǐng)求):

用于傳送表單數(shù)據(jù):

@FormUrlEncoded@POST("user/edit")Call updateUser(@Field("first_name") String first, @Field("last_name") String last);

注意開(kāi)頭必須多加上@FormUrlEncoded這句注釋?zhuān)蝗粫?huì)報(bào)錯(cuò)。表單自然是有多組鍵值對(duì)組成,這里的first_name就是鍵,而具體傳入的first就是值啦。

@Header/@Headers(POST請(qǐng)求):

用于添加請(qǐng)求頭部:

@GET("user")Call getUser(@Header("Authorization") String authorization)

表示將頭部Authorization屬性設(shè)置為你傳入的authorization;當(dāng)然你還可以用@Headers表示,作用是一樣的比如:

@Headers("Cache-Control: max-age=640000")@GET("user")CallgetUser()

當(dāng)然你可以多個(gè)設(shè)置:

@Headers({"Accept: application/vnd.github.v3.full+json","User-Agent: Retrofit-Sample-App"})@GET("user")CallgetUser()

好了,這樣我們就把上面這個(gè)RetrofitService 接口類(lèi)解釋的差不多了。我覺(jué)得,Retrofit最主要的也就是這個(gè)接口類(lèi)的定義了。好了,有了這個(gè)接口類(lèi),我們來(lái)看一下,到底如何使用這個(gè)我們定義的接口來(lái)進(jìn)行網(wǎng)絡(luò)請(qǐng)求。代碼如下:

Retrofit retrofit =newRetrofit.Builder()? ? ? ? .baseUrl("https://api.douban.com/v2/")? ? ? ? .addConverterFactory(GsonConverterFactory.create(newGsonBuilder().create()))? ? ? ? .build();RetrofitService service = retrofit.create(RetrofitService.class);Call call =? service.getSearchBook("金瓶梅",null,0,1);call.enqueue(newCallback() {@OverridepublicvoidonResponse(Call call, Response response){? ? ? ? text.setText(response.body()+"");? ? }@OverridepublicvoidonFailure(Call call, Throwable t){? ? }});

這里我們可以看到,先新建了一個(gè)Retrofit對(duì)象,然后給它設(shè)置一個(gè)我們前面說(shuō)的baseUrlhttps://api.douban.com/v2/.因?yàn)榻涌诜祷氐臄?shù)據(jù)不是我們需要的實(shí)體類(lèi),我們需要調(diào)用addConverterFactory方法進(jìn)行轉(zhuǎn)換。由于返回的數(shù)據(jù)為json類(lèi)型,所以在這個(gè)方法中傳入Gson轉(zhuǎn)換工廠GsonConverterFactory.create(new GsonBuilder().create()),這里我們需要在studio中添加Gson的依賴(lài):

compile 'com.squareup.retrofit2:converter-gson:2.1.0'

然后我們調(diào)用retrofit的create方法并傳入上面我們定義的接口的文件名RetrofitService.class,就可以得到RetrofitService 的實(shí)體對(duì)象。有了這個(gè)對(duì)象,我們就可以調(diào)用里面之前定義好的請(qǐng)求方法了。比如:

Call call =? service.getSearchBook("金瓶梅",null,0,1);

它會(huì)返回一個(gè)Call實(shí)體類(lèi),然后就可以調(diào)用Call的enqueue方法進(jìn)行異步請(qǐng)求,在enqueue方法中傳入一個(gè)回調(diào)CallBack,重寫(xiě)里面的onResponse和

onFailure方法,也就是請(qǐng)求成功和失敗的回調(diào)方法。當(dāng)成功時(shí),它會(huì)返回Response,里邊封裝了請(qǐng)求結(jié)果的所有信息,包括報(bào)頭,返回碼,還有主體等。比如調(diào)用它的body()方法就可獲得Book對(duì)象,也就是我們需要的數(shù)據(jù)。這里我們就把返回的Book,顯示屏幕上。如下圖:

Book中的數(shù)據(jù)

好了,到這里我們就基本了解了Retrofit的整個(gè)工作流程。

3.RxJava

我們這篇文章主要介紹搭建整體網(wǎng)絡(luò)請(qǐng)求框架,所以關(guān)于RxJava的基礎(chǔ)知識(shí),我這就不再詳細(xì)介紹了,網(wǎng)上也有很多文章,對(duì)RxJava還不是很了解的同學(xué),推薦你看一下扔物線(xiàn)的這篇文章給 Android 開(kāi)發(fā)者的 RxJava 詳解

下面我們來(lái)看一下RxJava和retrofit的結(jié)合使用,為了使Rxjava與retrofit結(jié)合,我們需要在Retrofit對(duì)象建立的時(shí)候添加一句代碼addCallAdapterFactory(RxJavaCallAdapterFactory.create()),當(dāng)然你還需要在build.gradle文件中添加如下依賴(lài):

compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'

完整的代碼如下:

Retrofit retrofit =newRetrofit.Builder()? ? ? ? .baseUrl("https://api.douban.com/v2/")? ? ? ? .addConverterFactory(GsonConverterFactory.create(newGsonBuilder().create()))? ? ? ? .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//支持RxJava.build();

然后我們還需要修改RetrofitService 中的代碼:

publicinterfaceRetrofitService{@GET("book/search")ObservablegetSearchBook(@Query("q")String name,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Query("tag")String tag, @Query("start")intstart,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Query("count")intcount);

可以看到,在原來(lái)的RetrofitService 中我們把getSearchBook方法返回的類(lèi)型Call改為了Observable,也就是被觀察者。其他都沒(méi)變。然后就是創(chuàng)建RetrofitService 實(shí)體類(lèi):

RetrofitService service = retrofit.create(RetrofitService.class);

和上面一樣,創(chuàng)建完RetrofitService ,就可以調(diào)用里面的方法了:

Observable observable =? service.getSearchBook("金瓶梅",null,0,1);

其實(shí)這一步,就是創(chuàng)建了一個(gè)rxjava中observable,即被觀察者,有了被觀察者,就需要一個(gè)觀察者,且訂閱它:

observable.subscribeOn(Schedulers.io())//請(qǐng)求數(shù)據(jù)的事件發(fā)生在io線(xiàn)程.observeOn(AndroidSchedulers.mainThread())//請(qǐng)求完成后在主線(xiàn)程更顯UI.subscribe(newObserver() {//訂閱@OverridepublicvoidonCompleted(){//所有事件都完成,可以做些操作。。。}@OverridepublicvoidonError(Throwable e){? ? ? ? ? ? ? ? ? e.printStackTrace();//請(qǐng)求過(guò)程中發(fā)生錯(cuò)誤}@OverridepublicvoidonNext(Book book){//這里的book就是我們請(qǐng)求接口返回的實(shí)體類(lèi)? ? }? ? ? ? ? }

在上面中我們可以看到,事件的消費(fèi)在Android主線(xiàn)程,所以我們還要在build.gradle中添加如下依賴(lài):

compile 'io.reactivex:rxandroid:1.2.0'

這樣我們就引入了RxAndroid,RxAndroid其實(shí)就是對(duì)RxJava的擴(kuò)展。比如上面這個(gè)Android主線(xiàn)程在RxJava中就沒(méi)有,因此要使用的話(huà)就必須得引用RxAndroid。

4.實(shí)踐

接下來(lái)我們就看看,在一個(gè)項(xiàng)目中上面三者是如何配合的。我們打開(kāi)Android Studio,新建一個(gè)項(xiàng)目取名為MVPDemo。這個(gè)demo的功能也很簡(jiǎn)單,就是點(diǎn)擊按鈕調(diào)用上面的那個(gè)測(cè)試接口,將請(qǐng)求下來(lái)書(shū)的信息顯示在屏幕上。首先我們來(lái)看一下這個(gè)工程的目錄結(jié)構(gòu):

工程目錄

我們可以看到,在項(xiàng)目的包名下,我們建了三個(gè)主要的文件夾:app、service、ui。當(dāng)然根據(jù)項(xiàng)目的需要你也可以添加更多其他的文件夾,比如一些工具類(lèi)等。其中app文件夾中可以建一個(gè)Application類(lèi),用于設(shè)置應(yīng)用全局的一些屬性,這里為了使項(xiàng)目更加簡(jiǎn)單就沒(méi)有添加;然后,我們?cè)賮?lái)看看ui文件夾下,這個(gè)文件夾下主要放一些關(guān)于界面的東西。在里面我們又建了三個(gè)文件夾:activity、adapter、fragment,我想看名字你就清楚里面要放什么了。最后我們?cè)谥攸c(diǎn)看看service文件夾中的東西。首先我們來(lái)看看里面重要的兩個(gè)類(lèi):RetrofitHelper和RetrofitService。RetrofitHelper主要用于Retrofit的初始化:

publicclassRetrofitHelper{privateContext mCntext;? ? OkHttpClient client =newOkHttpClient();? ? GsonConverterFactory factory = GsonConverterFactory.create(newGsonBuilder().create());privatestaticRetrofitHelper instance =null;privateRetrofit mRetrofit =null;publicstaticRetrofitHelpergetInstance(Context context){if(instance ==null){? ? ? ? ? ? instance =newRetrofitHelper(context);? ? ? ? }returninstance;? ? }privateRetrofitHelper(Context mContext){? ? ? ? mCntext = mContext;? ? ? ? init();? ? }privatevoidinit(){? ? ? ? resetApp();? ? }privatevoidresetApp(){? ? ? ? mRetrofit =newRetrofit.Builder()? ? ? ? ? ? ? ? .baseUrl("https://api.douban.com/v2/")? ? ? ? ? ? ? ? .client(client)? ? ? ? ? ? ? ? .addConverterFactory(factory)? ? ? ? ? ? ? ? .addCallAdapterFactory(RxJavaCallAdapterFactory.create())? ? ? ? ? ? ? ? .build();? ? }publicRetrofitServicegetServer(){returnmRetrofit.create(RetrofitService.class);? ? }}

代碼并不復(fù)雜,其中resetApp方法,就是前面介紹的Retrofit的創(chuàng)建,getServer方法就是為了獲取RetrofitService接口類(lèi)的實(shí)例化。然后定義了一個(gè)靜態(tài)方法getInstance用于獲取自身RetrofitHelper的實(shí)例化,并且只會(huì)實(shí)例化一次。

接下來(lái),看一下RetrofitService,其中代碼還是上面一樣:

publicinterfaceRetrofitService{@GET("book/search")ObservablegetSearchBooks(@Query("q")String name,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Query("tag")String tag, @Query("start")intstart,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Query("count")intcount);}

然后我們依次來(lái)看一下service文件夾下的四個(gè)文件夾:entity、manager、presenter和view。其中entity下放我們請(qǐng)求的實(shí)體類(lèi),這里就是Book。接下來(lái)我們來(lái)看一下manager中DataManager。這個(gè)類(lèi)其實(shí)就是為了讓你更方便的調(diào)用RetrofitService 中定義的方法:

publicclassDataManager{privateRetrofitService mRetrofitService;publicDataManager(Context context){this.mRetrofitService = RetrofitHelper.getInstance(context).getServer();? ? }publicObservablegetSearchBooks(String name,String tag,intstart,intcount){returnmRetrofitService.getSearchBooks(name,tag,start,count);? ? }}

可以看到,在它的構(gòu)造方法中,我們得到了RetrofitService 的實(shí)例化,然后定義了一個(gè)和RetrofitService 中同名的方法,里面其實(shí)就是調(diào)用RetrofitService 中的這個(gè)方法。這樣,把RetrofitService 中定義的方法都封裝到DataManager 中,以后無(wú)論在哪個(gè)要調(diào)用方法時(shí)直接在DataManager 中調(diào)用就可以了,而不是重復(fù)建立RetrofitService 的實(shí)例化,再調(diào)用其中的方法。

好了,我們?cè)賮?lái)看一下presenter和view,我們?cè)谇懊嬲f(shuō)過(guò),presenter主要用于網(wǎng)絡(luò)的請(qǐng)求以及數(shù)據(jù)的獲取,view就是將presenter獲取到的數(shù)據(jù)進(jìn)行展示。首先我們先來(lái)看view,我們看到我們建了兩個(gè)接口類(lèi)View和BookView,其中View是空的,主要用于和Android中的View區(qū)別開(kāi)來(lái):

publicinterfaceView{}

然后讓BookView繼承自我們自己定義的View :

publicinterfaceBookViewextendsView{voidonSuccess(Book mBook);voidonError(String result);}

可以看到在里面定義兩個(gè)方法,一個(gè)onSuccess,如果presenter請(qǐng)求成功,將向該方法傳入請(qǐng)求下來(lái)的實(shí)體類(lèi),也就是Book,view拿到這個(gè)數(shù)據(jù)實(shí)體類(lèi)后,就可以進(jìn)行關(guān)于這個(gè)數(shù)據(jù)的展示或其他的一些操作。如果請(qǐng)求失敗,就會(huì)向這個(gè)view傳入失敗信息,你可以彈個(gè)Toast來(lái)提示請(qǐng)求失敗。通常這兩個(gè)方法比較常用,當(dāng)然你可以根據(jù)項(xiàng)目需要來(lái)定義一些其他的方法。接下來(lái)我們看看presenter是如何進(jìn)行網(wǎng)絡(luò)請(qǐng)求的 。我們也定義了一個(gè)基礎(chǔ)Presenter:

publicinterfacePresenter{voidonCreate();voidonStart();//暫時(shí)沒(méi)用到voidonStop();voidpause();//暫時(shí)沒(méi)用到voidattachView(View view);voidattachIncomingIntent(Intent intent);//暫時(shí)沒(méi)用到}

里面我們可以看到,定義了一些方法,前面幾個(gè)onCreate、onStart等方法對(duì)應(yīng)著Activity中生命周期的方法,當(dāng)然沒(méi)必要寫(xiě)上Activity生命周期中所有回調(diào)方法,通常也就用到了onCreate和onStop,除非需求很復(fù)雜,在Activity不同生命周期請(qǐng)求的情況不同。接著我們定義了一個(gè)attachView方法,用于綁定我們定義的View。也就是,你想把請(qǐng)求下來(lái)的數(shù)據(jù)實(shí)體類(lèi)給哪個(gè)View就傳入哪個(gè)View。下面這個(gè)attachIncomingIntent暫且沒(méi)用到,就不說(shuō)了。好了,我們來(lái)看一下BookPresenter具體是怎么實(shí)現(xiàn)的:

publicclassBookPresenterimplementsPresenter{privateDataManager manager;privateCompositeSubscription mCompositeSubscription;privateContext mContext;privateBookView mBookView;privateBook mBook;publicBookPresenter(Context mContext){this.mContext = mContext;? ? }@OverridepublicvoidonCreate(){? ? ? ? manager =newDataManager(mContext);? ? ? ? mCompositeSubscription =newCompositeSubscription();? ? }@OverridepublicvoidonStart(){? ? }@OverridepublicvoidonStop(){if(mCompositeSubscription.hasSubscriptions()){? ? ? ? ? ? mCompositeSubscription.unsubscribe();? ? ? ? }? ? }@Overridepublicvoidpause(){? ? }@OverridepublicvoidattachView(View view){? ? ? ? mBookView = (BookView)view;? ? }@OverridepublicvoidattachIncomingIntent(Intent intent){? ? }publicvoidgetSearchBooks(String name,String tag,intstart,intcount){? ? ? ? mCompositeSubscription.add(manager.getSearchBooks(name,tag,start,count)? ? ? ? ? ? ? ? .subscribeOn(Schedulers.io())? ? ? ? ? ? ? ? .observeOn(AndroidSchedulers.mainThread())? ? ? ? ? ? ? ? .subscribe(newObserver() {@OverridepublicvoidonCompleted(){if(mBook !=null){? ? ? ? ? ? ? ? ? ? ? ? ? ? mBookView.onSuccess(mBook);? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? }@OverridepublicvoidonError(Throwable e){? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? ? ? ? ? ? ? mBookView.onError("請(qǐng)求失?。?!");? ? ? ? ? ? ? ? ? ? }@OverridepublicvoidonNext(Book book){? ? ? ? ? ? ? ? ? ? ? ? mBook = book;? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? })? ? ? ? );? ? }}

BookPresenter實(shí)現(xiàn)了我們定義的基礎(chǔ)Presenter,在onCreate中我們創(chuàng)建了DataManager的實(shí)體類(lèi),便于調(diào)用RetrofitService中的方法,還新建了一個(gè)CompositeSubscription對(duì)象,CompositeSubscription是用來(lái)存放RxJava中的訂閱關(guān)系的。注意請(qǐng)求完數(shù)據(jù)要及時(shí)清掉這個(gè)訂閱關(guān)系,不然會(huì)發(fā)生內(nèi)存泄漏??稍趏nStop中通過(guò)調(diào)用CompositeSubscription的unsubscribe方法來(lái)取消這個(gè)訂閱關(guān)系,不過(guò)一旦調(diào)用這個(gè)方法,那么這個(gè)CompositeSubscription也就無(wú)法再用了,要想再用只能重新new一個(gè)。然后我們可以看到在attachView中,我們把BookView傳進(jìn)去。也就是說(shuō)我們要把請(qǐng)求下來(lái)的實(shí)體類(lèi)交給BookView來(lái)處理。接下來(lái)我們定義了一個(gè)方法getSearchBooks,名字和入?yún)⒍己驼?qǐng)求接口RetrofitService中的方法相同。這里的這個(gè)方法也就是請(qǐng)求的具體實(shí)現(xiàn)過(guò)程。其實(shí)也很簡(jiǎn)單,就是向CompositeSubscription添加一個(gè)訂閱關(guān)系。上面我們已經(jīng)說(shuō)過(guò)manager.getSearchBooks就是調(diào)用RetrofitService的getSearchBooks方法,而這個(gè)方法返回的是一個(gè)泛型為Book的Observable,即被觀察者,然后通過(guò)subscribeOn(Schedulers.io())來(lái)定義請(qǐng)求事件發(fā)生在io線(xiàn)程,然后通過(guò)observeOn(AndroidSchedulers.mainThread())來(lái)定義事件在主線(xiàn)程消費(fèi),即在主線(xiàn)程進(jìn)行數(shù)據(jù)的處理,最后通過(guò)subscribe使觀察者訂閱它。在觀察者中有三個(gè)方法:onNext、onCompleted、onError。當(dāng)請(qǐng)求成功話(huà),就會(huì)調(diào)用onNext,并傳入請(qǐng)求返回的Book實(shí)體類(lèi),我們?cè)趏nNext中,把請(qǐng)求下來(lái)的Book實(shí)體類(lèi)存到內(nèi)存中,當(dāng)請(qǐng)求結(jié)束后會(huì)調(diào)用onCompleted,我們把請(qǐng)求下來(lái)的Book實(shí)體類(lèi)交給BookView處理就可以了,如果請(qǐng)求失敗,那么不會(huì)調(diào)用onCompleted而調(diào)用onError,這樣我們可以向BookView傳遞錯(cuò)誤消息。

好了,這樣我們我們就可以調(diào)用這個(gè)接口方法來(lái)進(jìn)行網(wǎng)絡(luò)的請(qǐng)求了,我們先寫(xiě)一下頁(yè)面的布局:

界面很簡(jiǎn)單,一共兩個(gè)控件,一個(gè)Button,點(diǎn)擊時(shí)進(jìn)行網(wǎng)絡(luò)請(qǐng)求,一個(gè)TextView,用于顯示請(qǐng)求下來(lái)的數(shù)據(jù)。然后我么看一下Activity中代碼:

publicclassMainActivityextendsAppCompatActivity{privateTextView text;privateButton button;privateBookPresenter mBookPresenter =newBookPresenter(this);@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);? ? ? ? setContentView(R.layout.activity_main);? ? ? ? text = (TextView)findViewById(R.id.text);? ? ? ? button = (Button)findViewById(R.id.button);? ? ? ? button.setOnClickListener(newView.OnClickListener() {@OverridepublicvoidonClick(View v){? ? ? ? ? ? ? ? mBookPresenter.getSearchBooks("金瓶梅",null,0,1);? ? ? ? ? ? }? ? ? ? });? ? ? ? mBookPresenter.onCreate();? ? ? ? mBookPresenter.attachView(mBookView);? ? }privateBookView mBookView =newBookView() {@OverridepublicvoidonSuccess(Book mBook){? ? ? ? ? ? text.setText(mBook.toString());? ? ? ? }@OverridepublicvoidonError(String result){? ? ? ? ? ? Toast.makeText(MainActivity.this,result, Toast.LENGTH_SHORT).show();? ? ? ? }? ? };@OverrideprotectedvoidonDestroy(){super.onDestroy();? ? ? ? mBookPresenter.onStop();? ? }}

邏輯并不復(fù)雜,我們先創(chuàng)建了一個(gè)BookPresenter 對(duì)象,然后調(diào)用它的onCreate方法進(jìn)行初始化,接著調(diào)用attachView來(lái)綁定BookView。BookView的實(shí)現(xiàn)也很簡(jiǎn)單,在onSuccess方法中將Book 中內(nèi)容顯示在TextView上,在onError中彈出一個(gè)Toast提示。然后點(diǎn)擊按鈕的時(shí)候就調(diào)用BookPresenter中g(shù)etSearchBooks方法,同時(shí)傳入必要的入?yún)?。這樣網(wǎng)絡(luò)請(qǐng)求就開(kāi)始了,如果請(qǐng)求成功就會(huì)回調(diào)BookView 中的onSuccess方法,失敗就回調(diào)onError方法。當(dāng)活動(dòng)銷(xiāo)毀時(shí)記得調(diào)用BookPresenter的onStop方法來(lái)釋放訂閱關(guān)系,防止內(nèi)存泄漏。

最后別忘了在AndroidManifest中添加網(wǎng)絡(luò)權(quán)限:

好了,我們運(yùn)行一下看一下效果:

演示

代碼已上傳github:MVPDemo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容