Android學習之Retrofit 的基本用法

android攻城獅獅獅 發佈 2022-05-02T15:34:12.703523+00:00

Retrofit基本用法最好用的網絡庫:Retrofit。它和OkHttp的定位完全不同,OkHttp側重的是底層通信的實現,而Retrofit側重的是上層接口的封裝。


Retrofit基本用法

最好用的網絡庫:Retrofit。它和OkHttp的定位完全不同,OkHttp側重的是底層通信的實現,而Retrofit側重的是上層接口的封裝。

Retrofit就是Square公司在OkHttp的基礎上進一步開發出來的應用層網絡通信庫,使得我們可以用更加面向對象的思維進行網絡操作。

Retrofit的基本設計思想

同一款應用程式中所發起的網絡請求絕大多數指向的是同一個伺服器域名。

伺服器提供的接口通常是可以根據功能來歸類的。將伺服器接口合理歸類能夠讓代碼結構變得更加合理,從而提高可閱讀性和可維護性。

開發者更加習慣於「調用一個接口,獲取它的返回值」這樣的編碼方式,其實大多數人並不關心網絡的具體通信細節,但是傳統網絡庫的用法卻需要編寫太多網絡相關的代碼。

而Retrofit的用法就是基於以上幾點來設計的。

首先我們可以配置好一個根路徑,然後在指定伺服器接口地址時只需要使用相對路徑即可,這樣就不用每次都指出完成的URL地址了。

另外,Retrofit允許我們對伺服器接口進行歸類,就功能同屬一類的伺服器接口定義到同一個接口文件中。

最後,我們也完全不用關心網絡通信的細節,只需要在接口文件中聲明一系列方法和返回值,然後通過註解的方式指定該方法對應哪個伺服器接口,以及需要提供哪些參數。

當我們在程序中調用該方法時,Retrofit會自動向對應的伺服器接口發起請求,並將相應的數據解析成返回值聲明的類型。這就使得我們可以用更加面向對象的思維來進行網絡操作。

Retrofit的基本用法

添加依賴

    implementation 'com.squareup.retrofit2:retrofit:2.6.1'
    implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
  • 第一條依賴是下載Retrofit、OkHttp和Okio這幾個庫,我們就不需要手動引入OkHttp庫了;
  • 第二條依賴是一個Retrofit的轉換庫,它是藉助GSON來解析JSON數據的,所以也會將GSON庫一起下載。

基本用法
新建一個App類:

class App(val id: String, val name: String, val version: String)

然後,根據伺服器接口的功能歸類,創建不同種類的接口文件,並定義具體伺服器接口的方法。不過目前本地伺服器上只有一個獲取JSON數據的接口,因此只需要定義一個接口文件,並包含一個方法即可,新建AppService接口:

interface AppService {
    
    @GET("get_data.json")
    fun getAppData(): Call<List<App>>
}

通常Retrofit的接口文件建議以具體的功能種類名開頭,並以Service結尾,這是一種比較好的命名習慣。

這裡需要注意兩點:

第一點:使用了一個@GET註解,表示當調用getAppData()方法時Retrofit會發起一條GET請求,請求的地址就是我們在@GET註解中傳入的具體參數。這裡只需要傳入相對路徑即可,根路徑在其他地方設置。

第二點:getAppData()方法的返回值必須聲明成Retrofit中內置的Call函數,並通過泛型來指定伺服器響應的數據應該轉換成什麼對象。由於伺服器響應的是一個包含App數據的JSON數組,所以聲明成List<App>。

定義好了AppService接口之後,接下來就是使用它,修改MainActivity中的代碼:

getAppDataBtn.setOnClickListener {
            val retrofit = Retrofit.Builder()
                .baseUrl("https://10.0.0.2/")
                .addConverterFactory(GsonConverterFactory.create())
                .build()
            val appService = retrofit.create(AppService::class.java)
            appService.getAppData().enqueue(object : Callback<List<App>> {
                override fun onResponse(call: Call<List<App>>, response: Response<List<App>>) {
                    val list = response.body()
                    if (list != null) {
                        for (app in list) {
                            Log.d("MainActivity", "id is ${app.id}")
                            Log.d("MainActivity", "name is ${app.name}")
                            Log.d("MainActivity", "version is ${app.version}")
                        }
                    }
                }

                override fun onFailure(call: Call<List<App>>, t: Throwable) {
                    t.printStackTrace()
                }
            })
        }

首先,使用Retrofit.Builder來構建一個Retrofit對象,其中baseUrl()方法指定所有Retrofit請求的根路徑,addConverterFactory()方法指定Retrofit在解析數據時所使用的轉換庫,這裡指定成GsonConverterFactory。這兩個方法是必須調用的。

有了Retrofit對象,調用它的create()方法,並傳入具體Service接口所對應的Class類型,創建一個該接口的動態代理對象。有了這個動態代理對象,就可以隨意調用接口中定義的所有方法了,而Retrofit會自動執行具體的處理。

當調用了AppService的getAppData()方法時,會返回一個Call<List<App>>對象,此時再調用一下它的enqueue()方法,Retrofit就會根據註解中配置的伺服器接口地址去進行網絡請求了,伺服器響應的數據會回調到enqueue()方法中傳入的Callback實現裡面。

注意:當發起請求時,Retrofit會自動在內部開啟子線程,當數據回調到Callback中之後,Retrofit又會自動切換回主線程。整個過程我們是不用考慮進程切換問題的。

在Callback的onResponse()方法中,調用response.body()方法將會得到Retrofit解析後的對象,也就是<List<App>>類型的數據,最後遍歷即可。

處理複雜的接口地址類型

先定義一個Data類,有id、content兩個欄位:

class Data(val id: String, val content: String)

比如伺服器的接口地址如下:

GET http://example.com/get_data.json

這是最簡單的一種情況,接口地址是靜態的,不會改變。那麼對應到Retrofit中,寫法如下:

interface ExampleService {
    @GET("get_data.json")
    fun getData(): Call<Data>
}

但是很顯然伺服器不可能總是提供靜態類型的接口,很多時候,接口地址中的部分內容是動態變化的,比如:

GET http://example.com/<page>/get_data.json

這個接口中,<page>代表頁數,傳入不同的頁數,伺服器返回的數據也不同。對應到Retrofit就該這麼寫:

interface ExampleService {
    @GET("{page}/get_data.json")
    fun getData(@Path("page") page: Int) : Call<Data>
}

在@GET註解指定的接口地址中,這裡使用了一個{page}的占位符,然後又在getData()方法中添加一個page參數,並使用@Path("page")註解來聲明這個參數。這樣當調用getData()方法發起請求時,Retrofit就會自動將page參數的值替換到占位符的位置,從而組成一個合法的請求地址。

另外,很多伺服器接口還會要求我們傳入一系列的參數,格式如下:

GET http://example.com//get_data.json?u=<user>&t=<token>

這是一種標準的帶參數GET請求格式。接口地址的最後用問號來連接參數部分,每個參數是用一個等號連接的鍵值對,多個參數之間用 』&『 符號分割。這種情況我們可以使用剛才的@Path註解方式,不過有些麻煩。還好Retrofit對這種帶參數的GET請求,專門提供了一種語法:

interface ExampleService {
    @GET("get_data.json")
    fun getData(@Query("u") user: String, @Query("t") token: String): Call<Data>
}

在getData()方法中添加了user和token參數,並使用@Query註解對它們進行聲明。這樣當發起網絡請求時,Retrofit就會自動按照帶參數GET請求的格式將這兩個參數構建到請求地址中。

掌握了以上內容,基本上可以應對各種千變萬化的伺服器接口地址了。

不過HTTP並不是只有GET請求這一種類型,還有POST、PUT、PATCH、DELETE等。它們之間的分工也很明確:

  • GET請求用於從伺服器獲取數據;
  • POST請求用於向伺服器提交數據;
  • PUT和PATCH請求用於修改伺服器上的數據;
  • DELETE請求用於刪除伺服器上的數據。

而Retrofit對所有常用的HTTP請求類型都進行了支持,使用@GET、@POST、@PUT、@PATCH、@DELETE註解,就可以讓Retrofit發出相應類型的請求了。

刪除數據

比如伺服器提供了如下接口地址:

DELETE http://example.com/data/<id>

意味著根據id刪除一條指定的數據,寫法如下:

interface ExampleService {
    @DELETE("data/{id}")
    fun deleteData(@Path("id") id: String): Call<ResponseBody>
}

這裡不用的是,將Call泛型指定成了ResponseBody,為什麼呢?

由於POST、PUT 、PATCH、DELETE這幾種請求類型與GET請求不同,它們更多是用於操作伺服器上的數據,而不是獲取伺服器上的數據,所以通常它們對於伺服器響應的數據並不關心。這個時候就可以使用ResponseBody,表示Retrofit能夠接收任意類型的響應數據,並且不會對響應數據進行解析。

提交數據

接口地址如下:

POST http://example.com/data/create
{"id": 1, "content": "This is a data."}

使用POST請求提交數據,需要將數據放到HTTP請求的body部分,在Retrofit中藉助@Body註解來完成:

interface ExampleService {
    @POST("data/create")
    fun createData(@Body data: Data): Call<ResponseBody>
}

在createData()方法中聲明了一個Data類型的參數,並給它加上了@Body註解。這樣當Retrofit發出POST請求時,就會自動將Data對象中的數據轉換成JSON格式的文本,並放到HTTP請求的body部分,伺服器在收到請求之後只需要從body中將這部分數據解析出來即可。這種寫法同樣也可以用來給PUT、PATCH、DELETE類型的請求提交數據。

指定header

有些伺服器接口會要求我們在HTTP請求的header中指定參數,比如:

GET http://example.com/get_data.json
User-Agent: okhttp
Cache-Control: max-age=0

這些header參數其實就是一個個鍵值對,只需要在Retrofit中用@Headers註解來聲明即可:

interface ExampleService {
    @Headers("User-Agent: okhttp", "Cache-Control: max-age=0")
    @GET("get_data.json")
    fun getData(): Call<Data>
}

但是這種寫法只適用於靜態header聲明,如果想要動態指定header的值,則需要用到@Header註解,如下:

interface ExampleService {
    @GET("get_data.json")
    fun getData(@Header("User-Agent") userAgent: String,
            @Header("Cache-Control") cacheControl: String): Call<Data>
}

現在發起網絡請求的時候,Retrofit就會自動將參數中傳入的值設置到User-Agent和Cache-Control這兩個Header中,實現了動態指定功能。

目前為止,已經將使用Retrofit處理複雜接口地址類型的內容基本學完了。

關鍵字: