Android 教你一步步搭建MVP+Retrofit+RxJava網絡請求框架

分類:編程 時間:2017-02-17

之前公司的項目用到了MVP+Retrofit+RxJava的框架進行網絡請求,所以今天特此寫一篇文章以做總結。相信很多人都聽說過MVP、Retrofit、以及RxJava,有的人已經開始用了,有的人可能還不知道這是什麽,以及到底怎麽用。不過沒關系,接下來我將為你一一揭開他們的神秘面紗,然後利用這三個家夥搭建一個網絡請求框架

1.什麽是MVP?

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

2.什麽是Retrofit?

接下來我們看一下什麽是Retrofit。在官網對Retrofit的描述是這樣的
A type-safe HTTP client for Android and Java說人話就是“一個類型安全的用於Android和Java網絡請求的客戶端”,其實就是一個封裝好的網絡請求庫。接下來就來看一下這個庫該怎麽用。首先我在網上找了一個API接口用於測試:https://api.douban.com/v2/book/search?q=金瓶梅&tag=&start=0&count=1這是一個用於查詢一本書詳細信息的一個請求接口。如果直接用瀏覽器打開的話會返回以下內容:


瀏覽器中返回內容

接下來我們來看看如何用Retrofit將上面的請求下來。為了在Android Studio中添加Retrofit庫,我們需要添加如下依賴:

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

好了,添加完該庫,我們再來看看如何使用,首先我們來建一個實體類Book,用於裝網絡請求後返回的數據。這裏順帶說一下,有的人建一個實體類時可能會根據瀏覽器中返回中的數據一行一行敲,其實這樣非常麻煩,這裏教大家一個簡單的方法,瞬間生成一個實體類。沒錯有的人可能用過,我們需要一個插件GsonFormat。它的使用也很簡單,首先需要在Android Studio中下載,點擊左上角菜單欄中的File,然後點擊Settings,在彈窗中選擇Plugins,然後點擊下方的Browse repositories...


然後在新打開的窗口中搜索GsonFormat,點擊右側綠色按鈕就可以下載安裝了,安裝完需要重啟下studio,就可以用了。


它的用法也很簡單,比如你先建立一個新的空類取名Book,然後在裏面按Alt+insert,會有個小彈窗選擇GsonFormat,之後在彈出的編輯框中拷入在瀏覽器中請求下來的那一坨東西,然後一直點ok就會自動生成字段,以及set和get方法,一會兒我們用Retrofit請求下來的數據都會保存在這個實體類中,還是挺方便的。最後我們裏面添加一個toString()方法,用於後面顯示方便。

接下來,回到我們的Retrofit中上,實體類已經建好了,我們來看看這個Retrofit如何進行網絡請求,其實代碼也很簡單。首先我們需要定義一個接口,取名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然後進行網絡請求。這裏我們拼接的URL就是上文提到的測試URL:https://api.douban.com/v2/book/search?q=金瓶梅&tag=&start=0&count=1。聰明的你一定看出來了,在這個URL中book/search就是GET後的值,而?後的q、tag、start、count等入參就是這個方法的入參。有的朋友可能要問了,https://api.douban.com/v2/這麽一大串跑哪去了?其實我們在進行網絡請求時,在URL中前一部分是相對不變的。什麽意思呢,比如你打開間書網站,在間書中你打開不同的網頁,雖然它的URL不同,但你會發現,每個URL前面都是以http://www.jianshu.com/開頭,我們把這個不變的部分,也叫做baseUrl提出來,放到另一個地方,在下面我們會提到。這樣我們一個完整的URL就拼接好了。在方法的開頭我們可以看到有個GET的註釋,說明這個請求是GET方法,當然你也可以根據具體需要用POST、PUT、DELETE以及HEAD。他們的區別如下:

  • GET ----------查找資源(查)
  • POST --------修改資源(改)
  • PUT ----------上傳文件(增)
  • DELETE ----刪除文件(刪)
  • HEAD--------只請求頁面的首部

然後我們來看一下這個方法的返回值,它返回Call實體,一會我們要用它進行具體的網絡請求,我們需要為它指定泛型為Book也就是我們數據的實體類。接下來,你會發現這個方法的入參和我們平時方法的入參還不大一樣。在每個入參前還多了一個註解。比如第一個入參@Query("q") String nameQuery表示把你傳入的字段拼接起來,比如在測試url中我們可以看到q=金瓶梅的入參,那麽Query後面的值必須是q,要和url中保持不變,然後我們定義了String類型的name,當調用這個方法是,用於傳入字符串,比如可以傳入“金瓶梅”。那麽這個方法就會自動在q後面拼上這個字符串進行網絡請求。以此類推,這個url需要幾個入參你就在這個方法中定義幾個入參,每個入參前都要加上Query註解。當然Retrofit除了Query這個註解外,還有其他幾個比如:@QueryMap、@Path、@Body、@FormUrlEncoded/@Field、@Header/@Headers。我們來看一下他們的區別:

@Query(GET請求):

用於在url後拼接上參數,例如:

@GET("book/search")
Call<Book> getSearchBook(@Query("q") String name);//name由調用者傳入

相當於:

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

@QueryMap(GET請求):

當然如果入參比較多,就可以把它們都放在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值需要傳入,就可以這種方法。我們把待定的值字段用{}括起來,當然 {}裏的名字不一定就是id,可以任取,但需和@Path後括號裏的名字一樣。如果在user後面還需要傳入參數的話,就可以用Query拼接上,比如:

@GET("group/{id}/users")
Call<Book> groupList(@Path("id") int groupId,@Query("sort") String sort);

當我們調用這個方法時,假設我們groupId傳入1,sort傳入“2”,那麽它拼接成的url就是group/1/users?sort=2,當然最後請求的話還會加上前面的baseUrl。

@Body(POST請求):

可以指定一個對象作為HTTP請求體,比如:

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

它會把我們傳入的User實體類轉換為用於傳輸的HTTP請求體,進行網絡請求。

@Field(POST請求):

用於傳送表單數據:

@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屬性設置為你傳入的authorization;當然你還可以用@Headers表示,作用是一樣的比如:

@Headers("Cache-Control: max-age=640000")
@GET("user")
Call<User> getUser()

當然你可以多個設置:

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

好了,這樣我們就把上面這個RetrofitService 接口類解釋的差不多了。我覺得,Retrofit最主要的也就是這個接口類的定義了。好了,有了這個接口類,我們來看一下,到底如何使用這個我們定義的接口來進行網絡請求。代碼如下:

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對象,然後給它設置一個我們前面說的baseUrlhttps://api.douban.com/v2/.因為接口返回的數據不是我們需要的實體類,我們需要調用addConverterFactory方法進行轉換。由於返回的數據為json類型,所以在這個方法中傳入Gson轉換工廠GsonConverterFactory.create(new GsonBuilder().create()),這裏我們需要在studio中添加Gson的依賴:

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

然後我們調用retrofit的create方法並傳入上面我們定義的接口的文件名RetrofitService.class,就可以得到RetrofitService 的實體對象。有了這個對象,我們就可以調用裏面之前定義好的請求方法了。比如:

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

它會返回一個Call實體類,然後就可以調用Call的enqueue方法進行異步請求,在enqueue方法中傳入一個回調CallBack,重寫裏面的onResponse和
onFailure方法,也就是請求成功和失敗的回調方法。當成功時,它會返回Response,裏邊封裝了請求結果的所有信息,包括報頭,返回碼,還有主體等。比如調用它的body()方法就可獲得Book對象,也就是我們需要的數據。這裏我們就把返回的Book,顯示屏幕上。如下圖:


Book中的數據

好了,到這裏我們就基本了解了Retrofit的整個工作流程。

3.RxJava

我們這篇文章主要介紹搭建整體網絡請求框架,所以關於RxJava的基礎知識,我這就不再詳細介紹了,網上也有很多文章,對RxJava還不是很了解的同學,推薦你看一下拋物線的這篇文章給 Android 開發者的 RxJava 詳解

下面我們來看一下RxJava和retrofit的結合使用,為了使Rxjava與retrofit結合,我們需要在Retrofit對象建立的時候添加一句代碼addCallAdapterFactory(RxJavaCallAdapterFactory.create()),當然你還需要在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,也就是被觀察者。其他都沒變。然後就是創建RetrofitService 實體類:

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

和上面一樣,創建完RetrofitService ,就可以調用裏面的方法了:

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

其實這一步,就是創建了一個rxjava中observable,即被觀察者,有了被觀察者,就需要一個觀察者,且訂閱它:

observable.subscribeOn(Schedulers.io())//請求數據的事件發生在io線程
          .observeOn(AndroidSchedulers.mainThread())//請求完成後在主線程更顯UI
          .subscribe(new Observer<Book>() {//訂閱
              @Override
              public void onCompleted() {
                  //所有事件都完成,可以做些操作。。。
              }
              @Override
              public void onError(Throwable e) {
                  e.printStackTrace(); //請求過程中發生錯誤
              }
              @Override
              public void onNext(Book book) {//這裏的book就是我們請求接口返回的實體類    
              }
           }

在上面中我們可以看到,事件的消費在Android主線程,所以我們還要在build.gradle中添加如下依賴:

compile 'io.reactivex:rxandroid:1.2.0'

這樣我們就引入了RxAndroid,RxAndroid其實就是對RxJava的擴展。比如上面這個Android主線程在RxJava中就沒有,因此要使用的話就必須得引用RxAndroid。

4.實踐

接下來我們就看看,在一個項目中上面三者是如何配合的。我們打開Android Studio,新建一個項目取名為MVPDemo。這個demo的功能也很簡單,就是點擊按鈕調用上面的那個測試接口,將請求下來書的信息顯示在屏幕上。首先我們來看一下這個工程的目錄結構:


工程目錄


我們可以看到,在項目的包名下,我們建了三個主要的文件夾:app、service、ui。當然根據項目的需要你也可以添加更多其他的文件夾,比如一些工具類等。其中app文件夾中可以建一個Application類,用於設置應用全局的一些屬性,這裏為了使項目更加簡單就沒有添加;然後,我們再來看看ui文件夾下,這個文件夾下主要放一些關於界面的東西。在裏面我們又建了三個文件夾: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);
    }
}

代碼並不復雜,其中resetApp方法,就是前面介紹的Retrofit的創建,getServer方法就是為了獲取RetrofitService接口類的實例化。然後定義了一個靜態方法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。這個類其實就是為了讓你更方便的調用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);
    }
}

可以看到,在它的構造方法中,我們得到了RetrofitService 的實例化,然後定義了一個和RetrofitService 中同名的方法,裏面其實就是調用RetrofitService 中的這個方法。這樣,把RetrofitService 中定義的方法都封裝到DataManager 中,以後無論在哪個要調用方法時直接在DataManager 中調用就可以了,而不是重復建立RetrofitService 的實例化,再調用其中的方法。

好了,我們再來看一下presenter和view,我們在前面說過,presenter主要用於網絡的請求以及數據的獲取,view就是將presenter獲取到的數據進行展示。首先我們先來看view,我們看到我們建了兩個接口類View和BookView,其中View是空的,主要用於和Android中的View區別開來:

public interface View {
}

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

public interface BookView extends View {
    void onSuccess(Book mBook);
    void onError(String result);
}

可以看到在裏面定義兩個方法,一個onSuccess,如果presenter請求成功,將向該方法傳入請求下來的實體類,也就是Book,view拿到這個數據實體類後,就可以進行關於這個數據的展示或其他的一些操作。如果請求失敗,就會向這個view傳入失敗信息,你可以彈個Toast來提示請求失敗。通常這兩個方法比較常用,當然你可以根據項目需要來定義一些其他的方法。接下來我們看看presenter是如何進行網絡請求的 。我們也定義了一個基礎Presenter:

public interface Presenter {
    void onCreate();

    void onStart();//暫時沒用到

    void onStop();

    void pause();//暫時沒用到

    void attachView(View view);

    void attachIncomingIntent(Intent intent);//暫時沒用到
}

裏面我們可以看到,定義了一些方法,前面幾個onCreate、onStart等方法對應著Activity中生命周期的方法,當然沒必要寫上Activity生命周期中所有回調方法,通常也就用到了onCreate和onStop,除非需求很復雜,在Activity不同生命周期請求的情況不同。接著我們定義了一個attachView方法,用於綁定我們定義的View。也就是,你想把請求下來的數據實體類給哪個View就傳入哪個View。下面這個attachIncomingIntent暫且沒用到,就不說了。好了,我們來看一下BookPresenter具體是怎麽實現的:

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 = 
            ads
            
ads

相關文章
ads

相關文章

ad