1. 事前準備
市面上大多數的 Android 應用程式皆會連線至網際網路,以執行部分網路作業。例如從後端伺服器擷取電子郵件、訊息或類似資訊。Gmail、YouTube 及 Google 相簿等應用程式都是透過連線至網際網路,顯示使用者資料。
在這個程式碼研究室中,您將使用以開放原始碼開發的程式庫建構網路層,並從後端伺服器取得資料。這樣可大幅簡化資料擷取作業,還可讓應用程式符合 Android 最佳做法,例如在背景執行緒上執行作業。若網際網路連線速度緩慢或無法使用,您也可更新應用程式的使用者介面,以讓使用者隨時掌握任何網路連線問題。
須知事項
- 如何建立及使用片段。
- 如何使用 Android 架構元件
ViewModel
和LiveData
。 - 如何在 Gradle 檔案中新增依附元件。
課程內容
要執行的步驟
- 修改入門應用程式,以發出網路服務 API 要求和處理回應。
- 使用 Retrofit 程式庫為應用程式實作網路層。
- 使用 Moshi 程式庫,將網路服務中的 JSON 回應剖析至應用程式的
LiveData
物件。 - 使用 Retrofit 提供的協同程式支援簡化程式碼。
軟硬體需求
- 已安裝 Android Studio 的電腦。
- MarsPhotos 應用程式範例程式碼。
2. 應用程式總覽
在本課程中,您將使用名為 MarsPhotos 的範例應用程式,顯示火星表面的圖片。此應用程式會連線至網路服務,以擷取和顯示火星的相片。這些圖片是自 NASA 火星漫遊者擷取的火星實景相片。以下是最後一個應用程式的螢幕截圖,其中包含以 RecyclerView
建構的縮圖屬性圖片。
您在本程式碼研究室中建構的應用程式版本不會採用大量視覺閃光特效:其著重於應用程式的網路層部分,以連線至網際網路並透過網路服務下載原始屬性資料。為確保系統正確擷取和剖析資料,請直接在文字檢視區塊輸出從後端伺服器接收的相片數量:
3. 探索 MarsPhotos 入門應用程式
下載範例程式碼
本程式碼研究室提供範例程式碼,可延伸至本程式碼研究室所教授的功能。範例程式碼可能含有來自先前程式碼研究室,且對您來說感到熟悉或陌生的程式碼。您將在稍後的程式碼研究室中進一步瞭解關於陌生程式碼的資訊。
如果您使用 GitHub 中的範例程式碼,請注意資料夾名稱是 android-basics-kotlin-mars-photos-app
。在 Android Studio 中開啟專案時,請選取這個資料夾。
- 前往專案指定的 GitHub 存放區頁面。
- 驗證分支版本名稱與程式碼研究室中指定的分支版本名稱相符。例如,在下列螢幕截圖中,分支版本名稱為「main」。
- 在專案的 GitHub 頁面中,按一下「Code」按鈕,畫面上會出現彈出式視窗。
- 在彈出式視窗中,按一下「Download ZIP」按鈕,將專案儲存至電腦。等待下載作業完成。
- 在電腦上尋找該檔案 (可能位於「下載」資料夾中)。
- 按兩下 ZIP 檔案,將檔案解壓縮。這項操作會建立含有專案檔案的新資料夾。
在 Android Studio 中開啟專案
- 啟動 Android Studio。
- 在「Welcome to Android Studio」視窗中,按一下「Open」。
注意:如果 Android Studio 已開啟,請改為依序選取「File」>「Open」選單選項。
- 在檔案瀏覽器中,前往已解壓縮的專案資料夾所在的位置 (可能位於「Downloads」資料夾中)。
- 按兩下該專案資料夾。
- 等待 Android Studio 開啟專案。
- 按一下「Run」按鈕 即可建構並執行應用程式。請確認應用程式的建構符合預期。
執行範例程式碼
- 在 Android Studio 中開啟已下載的專案。專案的資料夾名稱為
android-basics-kotlin-mars-photos-app
。範例程式碼的資料夾結構應如下所示。 - 在「Android」窗格中,依序展開「app」(應用程式) >「java」。請注意,應用程式有名為「
overview
」的套件資料夾。這是應用程式的 UI 層。
- 執行應用程式。當您編譯和執行應用程式時,應會在下列畫面畫面中央看見預留位置文字。完成本程式碼研究室後,您會將此預留位置文字更新為已擷取的相片數量。
- 瀏覽檔案以瞭解範例程式碼。針對版面配置檔案,您可以使用右上角的「Split」(分割) 選項,同時查看版面配置和 XML 的預覽畫面。
範例程式碼逐步操作說明
在此工作中,您將會熟悉專案的結構。以下是關於專案中重要檔案與資料夾的逐步操作說明。
OverviewFragment:
- 此為
MainActivity
中顯示的片段。您在上個步驟中看到的預留位置文字會顯示於此片段。 - 在下一個程式碼研究室中,此片段會顯示自「火星」相片後端伺服器接收的資料。
- 此類別會保留
OverviewViewModel
物件的參照。 OverviewFragment
的onCreateView()
函式會使用「資料繫結」來加載fragment_overview
版面配置,將繫結生命週期擁有者設定為自己,並在其繫結物件中設定viewModel
變數。- 指派生命週期擁有者後,系統會自動觀察「資料繫結」中使用的任何
LiveData
是否有任何變更,並據以更新使用者介面。
OverviewViewModel:
- 此為
OverviewFragment
的對應檢視模型。 - 此類別包含名為
_status
的MutableLiveData
屬性及其幕後屬性。更新此屬性的值時,會一併更新畫面上顯示的預留位置文字。 getMarsPhotos()
方法會更新預留位置回應。稍後在程式碼研究室中,您將使用此程式碼來顯示從伺服器擷取的資料。本程式碼研究室的目標,在於使用從網際網路取得的實際資料來更新ViewModel
中的status
LiveData
。
res/layout/fragment_overview.xml
:
- 此版面配置已設為使用資料繫結,且由單一
TextView
組成。 - 其會宣告
OverviewViewModel
變數,然後將status
從ViewModel
繫結至TextView
。
MainActivity.kt
:此活動的唯一工作是載入該活動的版面配置 activity_main
。
layout/activity_main.xml:
主要活動版面配置具有指向 fragment_overview
的單一 FragmentContainerView
,總覽片段會在應用程式啟動時執行個體化。
4. 應用程式總覽
在本程式碼研究室中,您會建立網路服務層來與後端伺服器進行通訊,以及擷取必要的資料。您將使用名為 Retrofit 的第三方程式庫實作此步驟。您將在稍後進一步瞭解相關資訊。ViewModel
會直接與該網路層進行通訊,應用程式的其餘部分則會對此實作公開。
OverviewViewModel
負責執行網路通話以取得火星相片資料。在 ViewModel
中,您會使用 LiveData
搭配生命週期感知資料繫結,在資料變更時更新應用程式使用者介面。
5. 網路服務與 Retrofit
「火星」相片資料會儲存在網路伺服器中。如要讓應用程式取得此資料,您必須建立連線並與網際網路上的伺服器通訊。
現今的大部分網路伺服器會使用稱為 REST 的一般無狀態網路架構,其中 RE 為「Representational」(表示法) 的縮寫,S 為「State」(狀態) 的縮寫,T 為「Transfer」(傳輸) 的縮寫。提供此架構的網路服務,稱為符合 REST 樣式的服務。
系統會透過 URI 以標準化方式向符合 REST 樣式的網路服務發出要求。URI (統一資源識別項) 會依名稱來識別伺服器中的資源,而不會暗示其位置或存取方式。舉例來說,在本課程的應用程式中,您將使用下列伺服器 URI 來擷取圖片網址 (此伺服器會代管「火星」房地產和「火星」相片):
android-kotlin-fun-mars-server.appspot.com
「統一資源定位器」(URL) 是一種 URI,其會指定運作或取得資源表示法的方式,亦即同時指定的主要存取機制與網路位置。
例如:
下列網址會列出「火星」上所有可用的房地產資源!
https://android-kotlin-fun-mars-server.appspot.com/realestate
下列 URL 會取得火星相片的清單:
https://android-kotlin-fun-mars-server.appspot.com/photos
這些網址是指識別的資源,例如 /realestate 或 /photos,您可透過「超文本傳輸通訊協定」(http:) 從網路中取得。您將在本程式碼研究室中使用 /photos 端點。
網路服務要求
每個網路服務要求都包含一個 URI,並透過 Chrome 等網路瀏覽器使用的 HTTP 通訊協定傳輸至伺服器。HTTP 要求包含指示伺服器處置方式的作業。
常見的 HTTP 作業包括:
- GET 用於擷取伺服器資料
- POST 或 PUT 用於新增/建立/更新伺服器的新資料
- DELETE 用於刪除伺服器中的資料
應用程式會向伺服器傳送「火星」相片資訊的 HTTP GET 要求,接著伺服器會對應用程式傳回回應,包括圖片網址。
網路服務的回應通常會採用 XML 或 JSON 這種常見的網路格式進行格式化 (在鍵/值組合中代表結構化資料格式)。我們將在後續工作中進一步瞭解 JSON。
在這項工作中,您要建立與伺服器的連線、與伺服器通訊,以及接收 JSON 回應。您將使用已寫入的後端伺服器。在本程式碼研究室中,您將使用 Retrofit 程式庫第三方程式庫,來與後端伺服器進行通訊。
外部程式庫
外部程式庫或第三方程式庫就像是核心 Android API 的擴充功能。這些程式庫大多為開放原始碼、由社群開發,並由全球廣大 Android 社群集體貢獻心力負責維護。這讓包括您在內的 Android 開發人員能夠打造出更優異的應用程式。
Retrofit 程式庫
在本程式碼研究室中,您會使用 Retrofit 程式庫來與符合 REST 樣式的「火星」網路服務通訊,其為具備完善支援和維護的理想範例程式庫。只要瀏覽其 GitHub 網頁,查看尚未解決的問題 (部分為功能要求) 和已解決的問題,即可感受上述優勢。若開發人員有在解決問題並定期回應功能要求,表示此程式庫維護良好,且極為適合在應用程式中使用。此外,亦提供 Retrofit 說明文件網頁。
Retrofit 程式庫將與後端通訊。其會根據傳遞的參數,建立網路服務的 URI。您將在稍後的章節中看到更多內容。
新增 Retrofit 依附元件
Android Gradle 可讓您將外部程式庫新增至專案。除了程式庫依附元件外,亦應包括代管程式庫的存放區。諸如來自 Jetpack 程式庫的 ViewModel 和 LiveData 等 Google 程式庫,是由 Google 存放區負責代管。大部分的社群資料庫 (例如 Retrofit) 皆是由 Google 和 MavenCentral 存放區代管。
- 開啟專案的頂層
build.gradle(Project: MarsPhotos)
檔案。請注意在repositories
區塊下方列出的存放區。您應該會看到以下兩個存放區:google()
、mavenCentral()
。
repositories {
google()
mavenCentral()
}
- 開啟模組層級 Gradle 檔案
build.gradle (Module: MarsPhots.app)
。 - 在
dependencies
區段中,為 Retrofit 程式庫新增以下幾行:
// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Retrofit with Scalar Converter
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
第一個依附元件針對 Retrofit2 程式庫本身提供,第二個依附元件則用於 Retrofit 純量轉換工具。此轉換工具可讓 Retrofit 以 String
的形式傳回 JSON 結果。這兩個程式庫會搭配運作。
- 按一下「Sync Now」,使用新的依附元件重新建構專案。
新增 Java 8 語言功能支援
包括 Retrofit2 在內的眾多第三方程式庫,皆使用 Java 8 語言功能。Android Gradle 外掛程式提供使用特定 Java 8 語言功能的內建支援。
- 如要使用內建功能,您必須在模組的
build.gradle
檔案中加入下列程式碼。系統已為您完成此步驟,請確認在您的build.gradle(Module: MarsPhotos.app)
中顯示下列程式碼。
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
6. 正在連線至網際網路
您會使用 Retrofit 程式庫與「火星」網路服務進行通訊,並將原始 JSON 回應顯示為 String
。預留位置 TextView
會顯示傳回的 JSON 回應字串,或顯示連線錯誤的訊息。
Retrofit 會根據網路服務的內容為應用程式建立網路 API。其會從網路服務擷取資料,並透過獨立的轉換工具程式庫執行資料轉送,該程式庫瞭解資料解碼方式,且會以 String
等物件形式傳回資料。Retrofit 提供諸如 XML 和 JSON 等熱門資料格式的內建支援。Retrofit 最終會建立程式碼來呼叫和耗用這項服務,包括在背景執行緒執行要求之類的重要詳細資訊。
在此工作中,您會將網路層新增至 MarsPhotos 專案,以供 ViewModel
用於與網路服務通訊。您必須按照下列步驟實作 Retrofit 服務 API。
- 建立網路層 (
MarsApiService
類別)。 - 使用基準網址和轉換工具工廠來建立 Retrofit 物件。
- 建立用於說明 Retrofit 如何與網路伺服器通訊的介面。
- 建立 Retrofit 服務,並向應用程式的其餘部分公開 API 服務執行個體。
實作上述步驟:
- 建立名為網路的新套件。在「Android」專案窗格中,以滑鼠右鍵按一下套件
com.example.android.marsphotos
。依序選取「New」(新增) >「Package」(套件)。在彈出式視窗中,將 network 附加至建議的套件名稱結尾處。 - 在新套件 network 中,建立新的 Kotlin 檔案。將其命名為
MarsApiService.
- 開啟
network/MarsApiService.kt
。為網路服務的基準網址新增下列常數。
private const val BASE_URL =
"https://android-kotlin-fun-mars-server.appspot.com"
- 在該常數下方,新增 Retrofit 建構工具來建構和建立 Retrofit 物件。
private val retrofit = Retrofit.Builder()
在系統提示時匯入 retrofit2.Retrofit
。
- Retrofit 需要網路服務的基準 URI 和轉換工具工廠,以建構網路服務 API。轉換工具會向 Retrofit 告知如何處理從網路服務傳回的資料。在此範例中,您希望 Retrofit 從網路服務擷取 JSON 回應,並以
String
形式傳回。Retrofit 具備支援字串和其他原始類型的ScalarsConverter
,因此您會在具有ScalarsConverterFactory
執行個體的建構工具上呼叫addConverterFactory()
。
private val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
在系統提示時匯入 retrofit2.converter.scalars.ScalarsConverterFactory
。
- 使用
baseUrl()
方法新增網路服務的基準 URI。最後,呼叫build()
以建立 Retrofit 物件。
private val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl(BASE_URL)
.build()
- 在 Retrofit 建構工具的呼叫下方,定義名為
MarsApiService
的介面,此介面會定義 Retrofit 使用 HTTP 要求與網路伺服器通訊的方式。
interface MarsApiService {
}
- 在
MarsApiService
介面當中,新增名為getPhotos()
的函式,以從網路服務取得回應字串。
interface MarsApiService {
fun getPhotos()
}
- 使用
@GET
註解,向 Retrofit 表明此為 GET 要求,並指定該網路服務方法的端點。在此範例中,端點就是photos
。如上個工作中所述,您可在本程式碼研究室中使用 /photos 端點。
interface MarsApiService {
@GET("photos")
fun getPhotos()
}
依要求匯入 retrofit2.http.GET
。
- 叫用
getPhotos()
方法時,Retrofit 會將端點photos
附加至在 Retrofit 建構工具中定義的基準網址,以用於啟動要求。將函式的傳回類型新增至String
。
interface MarsApiService {
@GET("photos")
fun getPhotos(): String
}
物件宣告
在 Kotlin 中,物件宣告是用來宣告單例模式物件。單例模式可確保僅建立一個物件執行個體,且對該物件僅有一個全域存取點。物件宣告的初始化為執行緒安全,且會在初次存取時完成。
Kotlin 可讓您輕鬆宣告單例模式。以下是物件宣告及其存取權的示例。物件宣告在 object
關鍵字後方一律帶有名稱。
範例:
// Object declaration
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider>
get() = // ...
}
// To refer to the object, use its name directly.
DataProviderManager.registerDataProvider(...)
在 Retrofit 物件上呼叫 create() 函式的代價非常高,且應用程式只需要單一 Retrofit API 服務執行個體。因此,您可以使用物件宣告,向應用程式的其餘部分公開服務。
- 在
MarsApiService
介面宣告之外,定義名為MarsApi
的公開物件,以初始化 Retrofit 服務。這是可從應用程式其餘部分存取的公開單例模式物件。
object MarsApi {
}
- 在
MarsApi
物件宣告當中,新增名為retrofitService
的類型MarsApiService
延遲初始化 Retrofit 物件屬性。執行此延遲初始化的用意,在於確保其在第一次使用時已初始化。您將在後續步驟中修正錯誤。
object MarsApi {
val retrofitService : MarsApiService by lazy {
}
}
- 透過
MarsApiService
介面,使用retrofit.create()
方法初始化retrofitService
變數。
object MarsApi {
val retrofitService : MarsApiService by lazy {
retrofit.create(MarsApiService::class.java) }
}
Retrofit 設定完成!每當應用程式呼叫 MarsApi.retrofitService
時,呼叫端就會存取在第一次存取時建立的同個單例模式 Retrofit 物件來實作 MarsApiService
。在下一個工作中,您將使用先前實作的 Retrofit 物件。
在 OverviewViewModel 中呼叫網路服務
在此步驟中,您會實作 getMarsPhotos()
方法來呼叫 Retrofit 服務,然後處理傳回的 JSON 字串。
ViewModelScope
ViewModelScope
是在應用程式中為每個 ViewModel
定義的內建協同程式範圍。若已清除 ViewModel
,系統就會自動取消此範圍內啟動的所有協同程式。
您會使用 ViewModelScope
來啟動協同程式,並在背景執行 Retrofit 網路呼叫。
- 在
MarsApiService
中,將getPhotos()
設為暫停函式。這樣就能在協同程式中呼叫此方法。
@GET("photos")
suspend fun getPhotos(): String
- 開啟
overview/OverviewViewModel
。向下捲動至getMarsPhotos()
方法。刪除將狀態回應設為"Set the Mars API Response here!".
的行。方法getMarsPhotos()
應已空白。
private fun getMarsPhotos() {
}
- 在
getMarsPhotos()
當中,使用viewModelScope.launch
啟動協同程式。
private fun getMarsPhotos() {
viewModelScope.launch {
}
}
在系統提示時匯入 androidx.lifecycle.
viewModelScope
和 kotlinx.coroutines.launch
。
- 在
viewModelScope
當中,使用單例模式物件MarsApi
從retrofitService
介面呼叫getPhotos()
方法。將傳回的回應儲存於名為listResult
的val
。
viewModelScope.launch {
val listResult = MarsApi.retrofitService.getPhotos()
}
在系統提示時匯入 com.example.android.marsphotos.network.MarsApi
。
- 將剛從後端伺服器收到的結果指派至
_status.value.
val listResult = MarsApi.retrofitService.getPhotos()
_status.value = listResult
- 執行應用程式,請注意應用程式會立即關閉,且不一定會顯示錯誤彈出式視窗。
- 按一下 Android Studio 中的「Logcat」分頁標籤,然後記下記錄中的錯誤 (開頭為「
------- beginning of crash
」這一行)。
--------- beginning of crash 22803-22865/com.example.android.marsphotos E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher Process: com.example.android.marsphotos, PID: 22803 java.lang.SecurityException: Permission denied (missing INTERNET permission?) ...
此錯誤訊息代表應用程式可能缺少 INTERNET
權限。在下一個工作中,您會新增應用程式的網際網路權限來解決這個問題。
7. 新增網際網路權限與例外狀況處理
Android 權限
Android 系統的權限旨在保護 Android 使用者的隱私權。Android 應用程式必須宣告或要求權限,以存取諸如聯絡人、通話記錄等敏感使用者資料,以及諸如相機或網際網路等特定系統功能。
您必須具備 INTERNET
權限,才可讓應用程式存取網際網路。連線至網際網路後會引發安全性疑慮,因此根據預設,應用程式無網際網路連線。您必須明確宣告應用程式需要存取網際網路。這視為一般權限。如要進一步瞭解 Android 權限及其類型,請參閱說明文件。
在此步驟中,應用程式會在 AndroidManifest
檔案中加入 <uses-permission>
標記,以宣告所需的權限。
- 開啟
manifests/AndroidManifest.xml
。在<application>
標記前方加上這一行:
<uses-permission android:name="android.permission.INTERNET" />
- 編譯並再次執行應用程式。若您有可用的網際網路連線,應會看到內含火星相片相關資料的 JSON 文字。稍後您可在程式碼研究室中進一步瞭解 JSON 格式。
- 輕觸裝置或模擬器中的「Back」按鈕,關閉應用程式。
- 將裝置或模擬器設為飛航模式,以模擬網路連線錯誤。從最近用過的選單重新開啟應用程式,或從 Android Studio 重新啟動應用程式。
- 按一下 Android Studio 中的「Logcat」分頁標籤,然後記下記錄中的嚴重例外狀況,如下所示:
3302-3302/com.example.android.marsphotos E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.android.marsphotos, PID: 3302 java.net.SocketTimeoutException: timeout ...
此錯誤訊息代表應用程式嘗試連線和逾時。此類例外狀況是極為常見的即時例外狀況。在下一個步驟中,您將瞭解如何處理這類例外狀況。
例外狀況處理
「例外狀況」是指在執行階段期間 (非編譯期間) 可能發生的錯誤,會在未通知使用者的情況下突然終止應用程式。這會對使用者體驗造成負面影響。例外狀況處理是一種機制,可避免應用程式突然終止,並以使用者容易理解的方式處理。
發生例外狀況的原因可能很單純,例如以零為除數或網路發生錯誤。這些例外狀況與您在先前程式碼研究室中學到的 NumberFormatException
類似。
連線至伺服器時可能發生的問題範例:
- API 使用的網址或 URI 不正確。
- 伺服器無法使用,且應用程式無法連線至伺服器。
- 網路延遲問題。
- 裝置的網際網路連線狀況不良或無網際網路連線。
在編譯期間無法擷取這些例外狀況。您可以使用 try-catch
區塊來處理執行階段中的例外狀況。如要進一步瞭解,請參閱說明文件。
Try-catch 區塊的範例語法
try {
// some code that can cause an exception.
}
catch (e: SomeException) {
// handle the exception to avoid abrupt termination.
}
在 try
區塊當中,執行預期發生例外狀況的所在程式碼,這在應用程式中稱為網路呼叫。您必須在 catch
區塊中實作程式碼,以避免應用程式突然終止。若發生例外狀況,系統將執行 catch
區塊來復原錯誤,而不會突然終止應用程式。
- 開啟
overview/OverviewViewModel.kt
。向下捲動至getMarsPhotos()
方法。在啟動區塊當中,在MarsApi
呼叫周圍新增try
區塊來處理例外狀況。在try
區塊後方新增catch
區塊:
viewModelScope.launch {
try {
val listResult = MarsApi.retrofitService.getPhotos()
_status.value = listResult
} catch (e: Exception) {
}
}
- 在
catch {}
區塊當中處理失敗回應。將e.message
設為_status.
value
,以向使用者顯示錯誤訊息。
catch (e: Exception) {
_status.value = "Failure: ${e.message}"
}
- 開啟飛航模式,並再次執行應用程式。此時不會突然關閉應用程式,但會改為顯示錯誤訊息。
- 關閉手機或模擬器的飛航模式。執行並測試您的應用程式,確定一切運作正常,且您能夠查看 JSON 字串。
8. 使用 Moshi 剖析 JSON 回應
JSON
要求的資料通常為常用的資料格式,例如 XML 或 JSON。每次呼叫都會傳回結構化資料,而應用程式必須瞭解該結構的內容,才能讀取回應中的資料。
舉例來說,您將在此應用程式中從下列伺服器擷取資料:https:// android-kotlin-fun-mars-server.appspot.com/photos。若您在瀏覽器中輸入此網址,即會顯示 JSON 格式的火星表面 ID 和圖片網址清單!
範例 JSON 回應結構:
- JSON 回應為陣列,以方括號表示。陣列包含 JSON 物件。
- JSON 物件會以大括號括住。
- 每個 JSON 物件皆內含一組名稱-值配對,並以半形逗號分隔。
- 配對的名稱和值會以半形冒號分隔。
- 名稱會以引號括住。
- 值可以是數字、字串、布林值、陣列、物件 (JSON 物件) 或空值。
舉例來說,img_src
是一個網址字串。若將網址貼至網路瀏覽器中,就會看到火星表面的圖片。
您現可從「火星」網路服務取得 JSON 回應,這是個不錯的起點。但您真正需要的是 Kotlin 物件,而非大型 JSON 字串。此外還有一個名為 Moshi 的外部程式庫,這個 Android JSON 剖析器可將 JSON 字串轉換為 Kotlin 物件。Retrofit 具備可與 Moshi 搭配使用的轉換工具,是非常適合在這裡使用的優異程式庫。
在此工作中,您會使用 Moshi 程式庫搭配 Retrofit,將網路服務中的 JSON 回應剖析為呈現火星相片的實用 Kotlin 物件。應用程式會改為顯示傳回的火星相片數量,而非顯示原始 JSON。
新增 Moshi 程式庫依附元件
- 開啟 build.gradle (Module: app)。
- 在依附元件區段新增以下程式碼,以包含 Moshi 依附元件。此依附元件會新增使用 Kotlin 支援的 Moshi JSON 程式庫支援。
// Moshi
implementation 'com.squareup.moshi:moshi-kotlin:1.13.0'
- 在
dependencies
區塊中找出 Retrofit 純量轉換工具行,並將以下依附元件變更為使用converter-moshi
:
替換為
// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Retrofit with scalar Converter
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
使用
// Retrofit with Moshi Converter
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
- 按一下「Sync Now」(立即同步處理),使用新依附元件重新建構專案。
實作「火星相片」資料類別
從網路服務取得的 JSON 回應範例項目看起來會像這樣,如下所示:
[{
"id":"424906",
"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"
},
...]
在上述範例中,請注意每個「火星」相片項目皆具有以下的 JSON 鍵與值配對:
id
:屬性的 ID,以字串表示。由於其已納入" "
,因此屬於String
類型而非Integer
。img_src
:網址圖片,以字串表示。
Moshi 會剖析此 JSON 資料,然後將其轉換為 Kotlin 物件。如要這麼做,Moshi 必須具有 Kotlin 資料類別以儲存剖析結果,因此您會在此步驟中建立資料類別 MarsPhoto
。
- 在 network 套件上按一下滑鼠右鍵,然後依序選取「New」>「Kotlin File/Class」。
- 在彈出式視窗中選取「Class」,然後輸入
MarsPhoto
做為類別名稱。這麼做會在network
套件中建立名為MarsPhoto.kt
的新檔案。 - 在類別定義前方新增
data
關鍵字,以將MarsPhoto
設為資料類別。將 {} 括號變更為 () 括號。這樣會發生錯誤,因為資料類別必須定義至少一個屬性。
data class MarsPhoto(
)
- 將下列屬性新增至
MarsPhoto
類別定義。
data class MarsPhoto(
val id: String, val img_src: String
)
請注意,MarsPhoto
類別中的每個變數皆會對應至 JSON 物件中的鍵名。如要比對特定 JSON 回應中的類型,請為所有值使用 String
物件。
Moshi 剖析 JSON 時,會根據名稱比對鍵,並在資料物件中填入適當的值。
@Json 註解
有時,JSON 回應中的鍵名可能導致 Kotlin 屬性有所混淆,或與建議的程式設計樣式不符;舉例來說,在 JSON 檔案中,img_src
鍵會使用底線,而屬性的 Kotlin 慣例會使用大小寫字母 (「駝峰式大小寫」)。
如要在資料類別中使用與 JSON 回應中鍵名不同的變數名稱,請使用 @Json
註解。在此範例中,資料類別中的變數名稱為 imgSrcUrl
。您可使用 @Json(name = "img_src")
將變數對應至 JSON 屬性 img_src
。
- 將
img_src
鍵這行替換為以下顯示的行。依要求匯入com.squareup.moshi.Json
。
@Json(name = "img_src") val imgSrcUrl: String
更新 MarsApiService 和 OverviewViewModel
在此工作中,您會使用 Moshi 建構工具來建立 Moshi 物件,做法與 Retrofit 建構工具類似。
您會將 ScalarsConverterFactory
替換為 KotlinJsonAdapterFactory
,以讓 Retrofit 知道可以使用 Moshi 將 JSON 回應轉換為 Kotlin 物件。接著會更新網路 API 和 ViewModel
,以使用 Moshi 物件。
- 開啟
network/MarsApiService.kt
。注意ScalarsConverterFactory
的未解決參考錯誤。這是因為您在先前的步驟中已變更 Retrofit 依附元件。刪除ScalarConverterFactory
的匯入作業。您會在不久之後修正其他錯誤。
移除:
import retrofit2.converter.scalars.ScalarsConverterFactory
- 在檔案頂端 (在 Retrofit 建構工具之前),新增下列程式碼以建立 Moshi 物件,類似 Retrofit 物件。
private val moshi = Moshi.Builder()
依要求匯入 com.squareup.moshi.Moshi
和 com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
。
- 若要讓 Moshi 註解與 Kotlin 順利搭配運作,請在 Moshi 建構工具中新增
KotlinJsonAdapterFactory
,然後呼叫build()
。
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
- 在
retrofit
物件宣告中,將 Retrofit 建構工具變更為使用MoshiConverterFactory
而非ScalarConverterFactory
,並傳遞您剛建立的moshi
執行個體。
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
依要求匯入 retrofit2.converter.moshi.MoshiConverterFactory
。
- 您已將
MoshiConverterFactory
設定妥當,現在可以要求 Retrofit 從 JSON 陣列傳回MarsPhoto
物件清單,而非傳回 JSON 字串。更新MarsApiService
介面,讓 Retrofit 傳回MarsPhoto
物件清單,而非傳回String
。
interface MarsApiService {
@GET("photos")
suspend fun getPhotos(): List<MarsPhoto>
}
- 對
viewModel
進行類似的變更,開啟OverviewViewModel.kt
。向下捲動至getMarsPhotos()
方法。 - 在方法
getMarsPhotos()
中,listResult
為List<MarsPhoto>
而不再是String
。該清單大小為已接收和剖析的相片數量。如要輸出已擷取的相片數量,請按照下列方式更新_status.
value
。
_status.value = "Success: ${listResult.size} Mars photos retrieved"
在系統提示時匯入 com.example.android.marsphotos.network.MarsPhoto
。
- 確認裝置或模擬器已關閉飛航模式。編譯並執行應用程式。此時訊息應會顯示從網路服務傳回的屬性數量,而非大型 JSON 字串:
9. 解決方案程式碼
build.gradle(Module: MarsPhotos.app)
以下是要包含的新依附元件。
dependencies { ... // Moshi implementation 'com.squareup.moshi:moshi-kotlin:1.13.0' // Retrofit with Moshi Converter implementation 'com.squareup.retrofit2:converter-moshi:2.9.0' ... }
Manifests/AndroidManifest.xml
從以下程式碼片段新增網際網路權限 <uses-permission..>
程式碼。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.marsphotos">
<!-- In order for our app to access the Internet, we need to define this permission. -->
<uses-permission android:name="android.permission.INTERNET" />
<application
...
</application>
</manifest>
network/MarsPhoto.kt
package com.example.android.marsphotos.network
import com.squareup.moshi.Json
/**
* This data class defines a Mars photo which includes an ID, and the image URL.
* The property names of this data class are used by Moshi to match the names of values in JSON.
*/
data class MarsPhoto(
val id: String,
@Json(name = "img_src") val imgSrcUrl: String
)
network/MarsApiService.kt
package com.example.android.marsphotos.network
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
private const val BASE_URL =
"https://android-kotlin-fun-mars-server.appspot.com"
/**
* Build the Moshi object with Kotlin adapter factory that Retrofit will be using.
*/
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
/**
* The Retrofit object with the Moshi converter.
*/
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
/**
* A public interface that exposes the [getPhotos] method
*/
interface MarsApiService {
/**
* Returns a [List] of [MarsPhoto] and this method can be called from a Coroutine.
* The @GET annotation indicates that the "photos" endpoint will be requested with the GET
* HTTP method
*/
@GET("photos")
suspend fun getPhotos() : List<MarsPhoto>
}
/**
* A public Api object that exposes the lazy-initialized Retrofit service
*/
object MarsApi {
val retrofitService: MarsApiService by lazy { retrofit.create(MarsApiService::class.java) }
}
Overview/OverviewViewModel.kt
package com.example.android.marsphotos.overview
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.android.marsphotos.network.MarsApi
import kotlinx.coroutines.launch
/**
* The [ViewModel] that is attached to the [OverviewFragment].
*/
class OverviewViewModel : ViewModel() {
// The internal MutableLiveData that stores the status of the most recent request
private val _status = MutableLiveData<String>()
// The external immutable LiveData for the request status
val status: LiveData<String> = _status
/**
* Call getMarsPhotos() on init so we can display status immediately.
*/
init {
getMarsPhotos()
}
/**
* Gets Mars photos information from the Mars API Retrofit service and updates the
* [MarsPhoto] [List] [LiveData].
*/
private fun getMarsPhotos() {
viewModelScope.launch {
try {
val listResult = MarsApi.retrofitService.getPhotos()
_status.value = "Success: ${listResult.size} Mars photos retrieved"
} catch (e: Exception) {
_status.value = "Failure: ${e.message}"
}
}
}
}
10. 摘要
REST 網路服務
- 網路服務是透過網際網路提供的軟體功能,可讓應用程式傳送要求和傳回資料。
- 一般的網路服務使用 REST 架構。提供 REST 架構的網路服務稱為「符合 REST 樣式」的服務。符合 REST 樣式的網路服務,均使用標準網路元件和通訊協定建構而成。
- 透過 URI 以標準化方式向 REST 網路服務傳送要求。
- 如要使用網路服務,應用程式必須建立網路連線,並與服務通訊。接著,應用程式必須接收回應資料,並將其剖析為可供應用程式使用的格式。
- Retrofit 程式庫是一個用戶端程式庫,可讓應用程式向 REST 網路服務發出要求。
- 使用轉換工具向 Retrofit 告知該如何處理傳送至網路服務的資料,以及從網路服務傳回的資料。舉例來說,
ScalarsConverter
轉換工具會將網路服務資料視為String
或其他原始檔案。 - 如要讓應用程式連上網際網路,請在 Android 資訊清單中新增
"android.permission.INTERNET"
權限。
JSON 剖析
11. 瞭解詳情
Android 開發人員說明文件:
Kotlin 說明文件:
- 例外狀況:「try」(嘗試)、「catch」(偵測)、「finally」(最終)、「throw」(擲回)、「Nothing」(無)
- 協同程式程式碼研究室
- 協同程式官方說明文件
- 協同程式結構定義與調度工具
其他: