一、Postman檢查API接口是否支持
? ? ? ? 1.“HTTP Request” 來創建一個新的請求。——請求構建界面,這是你進行所有 API 調用的地方。
? ? ? ? 2.設置請求方法和 URL:
????????選擇請求方法: 在 URL 輸入框左側,有一個下拉菜單。點擊它,選擇你想要測試的 HTTP 請求方法(例如:GET, POST, PUT, DELETE 等)。
GET:獲取數據
????????區別: GET 請求通常用于從服務器獲取或讀取數據。它不應該對服務器上的數據產生任何修改。因此,GET 被認為是“安全”和“冪等”的。安全(Safe): 發送 GET 請求不會改變服務器的狀態或數據。冪等(Idempotent): 多次發送相同的 GET 請求,結果都將是相同的,不會產生副作用。
工作原理:
客戶端(瀏覽器、Postman 等)將請求參數附加在 URL 的查詢字符串中(
.../users?id=123
)。請求頭(Headers)和 URL 組成請求信息發送給服務器。
服務器根據 URL 和參數定位資源,然后返回資源的表示(通常是 JSON 或 XML 格式)給客戶端。
因為參數在 URL 中,所以 GET 請求的長度有限制,并且不適合傳輸敏感數據。
POST:創建數據
????????區別: POST 請求用于向服務器提交數據以創建新資源。它會改變服務器的狀態,因此不安全且非冪等。不安全(Unsafe): 發送 POST 請求可能會改變服務器上的數據。非冪等(Non-Idempotent): 多次發送相同的 POST 請求,可能會導致創建多個重復的資源。例如,多次提交一個訂單表單,可能會創建多份訂單。
工作原理:
客戶端將請求數據放在請求的**主體(Body)**中發送給服務器。請求體可以包含任何類型的數據,如表單數據、JSON、文件等,因此沒有長度限制。
服務器接收到請求體中的數據后,處理并創建一個新資源。
通常,服務器在創建成功后會返回一個 201 Created 的狀態碼,并在響應頭中包含新創建資源的 URL。
PUT:更新數據
????????區別: PUT 請求用于更新或替換服務器上已存在的資源。它與 POST 的主要區別在于冪等性。不安全(Unsafe): 會修改服務器上的數據。冪等(Idempotent): 多次發送相同的 PUT 請求,結果與發送一次請求是相同的。如果資源不存在,它通常會創建新資源。
工作原理:
客戶端通過 URL 明確指定要更新的資源(例如:
.../users/123
)。客戶端將完整的、更新后的資源表示放在請求主體中發送給服務器。如果請求中缺少某個字段,服務器通常會將其刪除或設為空。
服務器用請求主體中的數據完全替換 URL 指定的資源。
成功后,服務器通常返回 200 OK 或 204 No Content 狀態碼。
DELETE:刪除數據
????????區別: DELETE 請求用于刪除服務器上指定的資源。不安全(Unsafe): 會改變服務器上的數據。冪等(Idempotent): 多次發送相同的 DELETE 請求,結果與發送一次是相同的。第一次請求會刪除資源,后續請求會返回“資源未找到”或成功,但不會產生新的副作用。
工作原理:
客戶端通過 URL 明確指定要刪除的資源(例如:
.../users/123
)。服務器收到請求后,會刪除該資源。
成功后,服務器通常返回 200 OK 或 204 No Content 狀態碼。
????????輸入請求 URL: 在請求方法右側的輸入框中,輸入你要測試的 API 接口的完整 URL。例如:https://api.example.com/users
。
? ? ? ?3. API 接口通常需要一些參數來完成請求。Postman 提供了多種添加參數的方式,根據 API 的要求來選擇。
URL 參數(Params):
如果你的 API 需要在 URL 中傳遞參數(例如:
https://api.example.com/users?id=123
),你可以在 URL 輸入框下方找到 "Params" 選項卡。點擊這個選項卡,Postman 會自動將你在 URL 中輸入的參數拆分出來,或者你也可以在這里手動添加鍵值對(Key-Value pairs)。
請求頭(Headers):
許多 API 接口需要通過請求頭來傳遞信息,比如 認證令牌(Authorization Token) 或 內容類型(Content-Type)。
點擊 "Headers" 選項卡,添加你需要的鍵值對。例如:
Key: Authorization
Value: Bearer your_token_here
二、Android中APIService調用
1.Retrofit 接口定義,用于調用 Spotify 的 RESTful API。通過接口interface調用
2.Retrofit的注釋詳解
????????它們告訴 Retrofit 如何構建 HTTP 請求。例如下列
第一個示例
@GET("v1/me/top/{type}")
@GET:指的是HTTP請求方式為GET
其中的"v1/me/top/{type}"
:這是一個相對 URL 路徑。Retrofit 會將其與您在構建 Retrofit 實例時提供的基本 URL(例如 https://api.spotify.com/
)拼接起來,形成完整的請求地址。
object RetrofitClient {private const val BASE_URL = "https://api.spotify.com/"// 配置 OkHttpClientprivate val okHttpClient = OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {level = HttpLoggingInterceptor.Level.BODY // 打印所有請求和響應的詳細日志}).connectTimeout(30, TimeUnit.SECONDS) // 連接超時.readTimeout(30, TimeUnit.SECONDS) // 讀取超時.writeTimeout(30, TimeUnit.SECONDS) // 寫入超時.build()// 創建 Retrofit 實例val spotifyApiService: SpotifyApiService by lazy {Retrofit.Builder().baseUrl(BASE_URL).client(okHttpClient).addConverterFactory(GsonConverterFactory.create()).build().create(SpotifyApiService::class.java)}
}
?第二個示例
suspend fun getTopItems(@Header("Authorization") authorization: String, // Access Token@Path("type") type: String, // "artists" 或 "tracks"@Query("time_range") timeRange: String? = null, // long_term, medium_term, short_term@Query("limit") limit: Int? = null, // 數量@Query("offset") offset: Int? = null // 偏移量): TopItemsResponse<Item> // 使用泛型 <*> 因為 items 可以是 Artist 或 Track
????????@Header
:用于在 HTTP 請求頭中添加鍵值對。"Authorization"
:這是 HTTP 請求頭的鍵名,通常用于傳遞身份驗證令牌。authorization: String
:這個參數的值會通過 @Header
注解,被添加到請求頭中。您需要在這里傳入您的 Access Token
,例如 "Bearer BQC..."
。
????????@Path
:用于將方法參數的值插入到 URL 路徑中。在這里,它會將方法參數 type
的值(如 "artists"
)替換掉 URL 路徑中的 {type}
占位符。
????????@Query
:用于向 URL 中添加查詢參數(Query Parameters)。"time_range"
:這是查詢參數的鍵名。Retrofit 會將其與方法參數的值組合成 ?time_range=...
這樣的形式。
????????suspend fun ... : TopItemsResponse<Item>
:
????????TopItemsResponse
:這是一個自定義的數據類(或接口),用于映射 API 返回的 JSON 響應。Retrofit 會使用一個 JSON 轉換器(如 Moshi 或 Gson)將 JSON 數據自動解析成這個對象。
????????<Item>
:這是一個泛型(Generic)。在 getTopItems
方法中,使用了泛型 <Item>
,因為 items
列表中的對象可以是 Artist
,也可以是 Track
。這種設計使得代碼更加靈活,但需要您在調用時指定具體的類型,或者在運行時進行類型檢查。
????????在 getTopArtistItems
方法中,更具體地指定了 <Artist>
,這意味著這個方法專門用于獲取藝術家數據,返回的 items
列表將是 Artist
對象的列表。這是一種更類型安全的做法,通常更推薦。
三、Android中API的數據模型創建
@Serializable
的作用是告訴 Kotlinx.serialization 庫,這個數據類需要被序列化和反序列化。
序列化 (Serialization):將 Kotlin 對象(例如
TopItemsResponse
的實例)轉換成另一種格式,比如 JSON 字符串。反序列化 (Deserialization):將 JSON 字符串轉換回 Kotlin 對象。
????????在 Android 開發中,當您從網絡 API 獲取數據時,通常會得到一個 JSON 格式的字符串。為了在您的應用中方便地使用這些數據,您需要將其轉換為一個 Kotlin 對象,這就是反序列化。當您需要向服務器發送數據時(例如 POST 或 PUT 請求),您需要將 Kotlin 對象轉換為 JSON 字符串,這就是序列化。
@Serializable
data class TopItemsResponse<T>(val href: String,val limit: Int,val next: String?,val offset: Int,val previous: String?,val total: Int,val items: List<T> // 泛型 T 可以是 Artist 或 Track
)
@Serializable
解決了以下幾個核心問題:
自動化數據轉換:
在沒有
@Serializable
的情況下,您需要手動編寫代碼來解析 JSON 字符串,并為每個字段賦值。這非常繁瑣且容易出錯。有了
@Serializable
,您只需要定義好數據類,Kotlinx.serialization 庫會為您自動完成 JSON 和 Kotlin 對象之間的轉換。
與 Retrofit 的無縫集成:
當您使用 Retrofit 調用 API 時,您可以將
TopItemsResponse
作為返回類型。Retrofit 會檢測到您使用了 Kotlinx.serialization 的@Serializable
注解,并自動使用其 JSON 轉換器來解析 API 返回的 JSON 數據,并將其填充到TopItemsResponse
對象中。它使得您的網絡請求代碼非常簡潔,您不需要關心底層的數據解析細節。
類型安全和泛型支持:
在您的
TopItemsResponse<T>
例子中,items: List<T>
是一個泛型列表。Kotlinx.serialization 能夠正確處理這個泛型,它會根據您在調用時提供的具體類型(例如
Artist
或Track
)來自動解析items
列表中的每個對象。
之后的對象參數比如Image,Item都用data class數據類來生成模型
四、Android中MediaSession數據調用
1.MediaItemUtils
工具類
????????它主要用于將不同類型的數據模型(如 Album
、Track
、Artist
等)轉換為 androidx.media3.common.MediaItem
對象。這個工具類通常在媒體播放應用中用于構建和管理媒體庫。
????????MediaItem
: MediaItem
是一個媒體庫中條目的抽象表示。它可以代表一首歌曲、一個專輯、一個藝術家、一個播放列表或任何其他可播放或可瀏覽的內容。
它包含一個唯一的
mediaId
。它包含一個
MediaMetadata
對象來描述該媒體條目。它還可以包含一個
sourceUri
,指向實際的媒體文件或流。
????????MediaMetadata
: MediaMetadata
包含媒體條目的詳細信息,如標題、藝術家、專輯、封面圖片 URI (artworkUri
)、媒體類型 (mediaType
) 等。
MediaMetadata.Builder
用于創建和配置MediaMetadata
對象。
fun buildMediaItem(title: String,mediaId: String,isPlayable: Boolean,isBrowsable: Boolean,mediaType: @MediaMetadata.MediaType Int,subtitleConfigurations: List<MediaItem.SubtitleConfiguration> = mutableListOf(),album: String? = null,artist: String? = null,genre: String? = null,sourceUri: Uri? = null,imageUri: Uri? = null,extras: Bundle? = null,subTitle: String = "",): MediaItem {val metadata =MediaMetadata.Builder().setAlbumTitle(album).setTitle(title).setSubtitle(subTitle).setArtist(artist).setGenre(genre).setIsBrowsable(isBrowsable).setIsPlayable(isPlayable).setArtworkUri(imageUri).setMediaType(mediaType).setExtras(extras).build()return MediaItem.Builder().setMediaId(mediaId).setSubtitleConfigurations(subtitleConfigurations).setMediaMetadata(metadata).setUri(sourceUri).build()}
之后是擴展函數來構建MediaItem的模式
????????MediaItemUtils
中定義了多個擴展函數(如 Album.toMediaItem()
、Track.toMediaItem()
等)以及一個通用的 buildMediaItem()
函數。
buildMediaItem()
函數: 這是一個通用的輔助函數,它接受各種參數(標題、mediaId
、isPlayable
等),然后使用 MediaMetadata.Builder
和 MediaItem.Builder
來構建一個 MediaItem
。
MediaMetadata.Builder
的setTitle()
、setArtist()
、setArtworkUri()
等方法用于設置媒體的元數據。MediaItem.Builder
的setMediaId()
、setMediaMetadata()
、setUri()
等方法用于構建最終的MediaItem
。
????????擴展函數(Extension Functions): Kotlin 的擴展函數能力在這里得到了很好的應用。例如,Album.toMediaItem()
允許你在一個 Album
對象上直接調用 .toMediaItem()
方法,使其看起來像是 Album
類本身的一個成員函數。
這些擴展函數的作用是將特定數據模型(如
Album
、Track
)轉換為通用的MediaItem
格式,以便媒體播放器(如ExoPlayer
)能夠統一處理它們。
fun Album.toMediaItem(): MediaItem {return buildMediaItem(title = this.name,mediaId = this.id,isPlayable = true,isBrowsable = true,mediaType = MediaMetadata.MEDIA_TYPE_ALBUM,imageUri = getUri(images[1].url),sourceUri = this.uri.toUri(),extras = Bundle().apply {putString(MediaItemUtils.MEDIA_EXTTERNAL_URLS, this@toMediaItem.external_urls.spotify)}).setMediaItemType(MediaItemUtils.MEDIA_ITEM_TYPE_NORMAL)}
2.SpotifyDataRepository——Spotify數據倉庫
????????它封裝了對 Spotify Web API 的網絡請求,并將返回的數據轉換為 MediaItem
對象,以便在媒體播放器應用中使用。
Retrofit 和 Spotify API 服務
suspend fun fetchTopTracks(timeRange: String = "medium_term", limit: Int = 10, offset: Int = 0):List<MediaItem> {val result = RetrofitClient.spotifyApiService.getTopItems(authorization = "Bearer ${SPUtils.getString(SPUtils.ACCESS_TOKEN)}",type = "tracks",timeRange = timeRange,limit = limit,offset = offset)return result.items.map { it.album.toMediaItem() }}
這段代碼的核心是與 Retrofit 的集成,它是一個用于 Android 和 Java 的類型安全的 HTTP 客戶端。
RetrofitClient.spotifyApiService
: 這是一個 Retrofit 接口的實例,它定義了所有與 Spotify Web API 交互的請求方法。每個方法都使用注解(如@GET
)來指定請求類型和 URL。suspend fun
: 所有的網絡請求函數都使用了suspend
關鍵字,這表示它們是 suspend 函數。suspend
函數是 Kotlin Coroutines 的核心,它們可以暫停和恢復執行,使得異步操作(如網絡請求)可以在不阻塞主線程的情況下進行。
之后的網絡請求和數據轉換流程
每個 fetch
函數都遵循一個標準的模式:
發起網絡請求: 調用
RetrofitClient.spotifyApiService
接口中的相應方法,并傳入必要的參數(如authorization
token、id
、limit
等)。等待響應: 由于這些函數是
suspend
的,它們會暫停執行直到 Retrofit 收到來自 Spotify API 的響應。處理結果:
val result = RetrofitClient.spotifyApiService...
: Retrofit 會自動將 JSON 響應解析成預定義的 Kotlin 數據類對象(例如,result.items
或result.tracks.items
)。.map { ... }
: 這是一個 Kotlin 集合的轉換操作。它遍歷 Retrofit 返回的數據列表(例如result.items
),并對每個元素應用一個轉換函數。.toMediaItem()
: 這是上一個代碼片段中定義的擴展函數。它負責將 Spotify 的數據模型(如Album
或Track
)轉換為MediaItem
對象。這個步驟至關重要,因為它將網絡獲取的原始數據轉換成媒體播放器能夠理解和使用的格式。
返回
MediaItem
列表: 函數最終返回一個List<MediaItem>
,這個列表可以直接提供給Media3
媒體會話或播放列表。
3.onGetChildren——
媒體庫服務(Media Library Service)處理客戶端(如 Android Auto、Google Assistant 或其他媒體瀏覽器)請求其子項列表的入口點。
1. MediaLibraryService
和 onGetChildren
MediaLibraryService
: 這是 Media3 庫中的一個服務,用于向客戶端公開一個可瀏覽的媒體庫樹。客戶端可以通過它來獲取媒體內容,而不僅僅是播放。onGetChildren()
: 這個方法是MediaLibraryService
的核心。當客戶端(例如,汽車信息娛樂系統)想要瀏覽某個媒體項的子項時,它會調用此方法,并傳入父項的parentId
。你的任務是在這個方法中,根據
parentId
決定要返回哪些子媒體項。
@OptIn(UnstableApi::class)override fun onGetChildren(session: MediaLibraryService.MediaLibrarySession,browser: MediaSession.ControllerInfo,parentId: String,page: Int,pageSize: Int,params: MediaLibraryService.LibraryParams?,): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {logd(TAG) { "jidouauto cloud music onGetChildren beforeinit parentId: $parentId" }return scope.future {when(parentId){SPOTIFY_MUSIC_ROOT_ID -> {LibraryResult.ofItemList(dataRepository.getSpotifyMainTabs(), params)}MEDIA_TABS_RECOMMEND ->{val tracks = dataRepository.fetchTopTracks("short_term",10,0)val artists = dataRepository.fetchTopArtistTracks("short_term",10,0)val resultList= mutableListOf<MediaItem>()resultList.addAll(tracks)resultList.addAll(artists)LibraryResult.ofItemList(resultList,params)}MEDIA_TABS_MINE ->{val result = dataRepository.fetchPlaylistTracks(20,0)LibraryResult.ofItemList(result,params)}MEDIA_ALBUM_PLAY_LIST->{val extras = params?.extrasval playlistId = extras?.getString("playlistId") ?: "-1"val result = dataRepository.fetchAlbum(playlistId)LibraryResult.ofItemList(result,params)}MEDIA_ARTIST_PLAY_LIST->{val extras = params?.extrasval playlistId=extras?.getString("playlistId")?:"-1"val result =dataRepository.fetchArtistTopTracks(playlistId,"ES")LibraryResult.ofItemList(result,params)}MEDIA_PLAY_LIST->{val extras = params?.extrasval playlistId=extras?.getString("playlistId")?:"-1"val result =dataRepository.fetchDetailTracks(playlistId,"ES")LibraryResult.ofItemList(listOf(result) ,params)}else ->{LibraryResult.ofError(SessionError.ERROR_BAD_VALUE)}}}}
2. 構建媒體庫層級結構
這段代碼使用一個 when
表達式根據 parentId
來構建一個多層級的媒體庫:
根目錄 (
SPOTIFY_MUSIC_ROOT_ID
): 當parentId
是根 ID 時,它會返回一個包含主要選項卡(如“推薦”、“我的音樂”)的列表。這些選項卡本身是可瀏覽的文件夾。dataRepository.getSpotifyMainTabs()
返回一個List<MediaItem>
,其isBrowsable
屬性被設置為true
。
子目錄(
MEDIA_TABS_RECOMMEND
,MEDIA_TABS_MINE
等): 當parentId
是某個選項卡的 ID 時,它會調用dataRepository
中的相應方法來獲取具體的內容列表。MEDIA_TABS_RECOMMEND
: 返回熱門歌曲和熱門藝術家的列表。MEDIA_TABS_MINE
: 返回用戶的播放列表。
動態內容(
MEDIA_ALBUM_PLAY_LIST
,MEDIA_ARTIST_PLAY_LIST
等): 這部分展示了如何處理更深層次的、依賴于特定 ID 的媒體內容。params?.extras
:params
參數中的extras
Bundle
可以用來傳遞額外的信息。例如,當客戶端點擊一個專輯項時,它可以在extras
中傳遞專輯的 ID。extras?.getString("playlistId")
: 這段代碼從extras
中提取出playlistId
,然后用它來調用dataRepository
的相應方法,從而獲取該專輯或藝術家的具體曲目列表。LibraryResult.ofItemList(...)
: 這是Media3
庫用來封裝和返回結果的類。它將獲取到的MediaItem
列表包裝在LibraryResult
中。
五、總結
????????整個工作流程是一個自底向上的過程,首先從 Postman 檢查開始,確保 API 接口的功能、參數和請求方式(GET, POST, PUT, DELETE)是正確的。
????????在 Android 端,這個流程由 Retrofit 庫實現。你通過定義一個 Retrofit 接口來抽象 API,并使用 @GET
、@Header
、@Path
和 @Query
等注解來構建請求。同時,你使用 Kotlinx.serialization 和 @Serializable
注解來創建數據模型,讓 Retrofit 自動將 JSON 響應解析為 Kotlin 對象,大大簡化了數據處理。
????????接著,一個名為 SpotifyDataRepository
的數據倉庫封裝了所有這些網絡請求,并利用 Kotlin 擴展函數 (toMediaItem()
) 將獲取到的原始數據模型(如 Album
, Track
)統一轉換為 Media3 庫所需的 MediaItem
對象,這些對象包含了媒體的元數據(MediaMetadata
)。
????????最后,在 MediaLibraryService
的 onGetChildren
方法中,你根據客戶端請求的 parentId
來判斷用戶正在瀏覽的媒體庫層級,然后調用 SpotifyDataRepository
中相應的方法獲取數據,并將轉換好的 MediaItem
列表封裝到 LibraryResult
中返回給客戶端,從而構建一個多層級的、可瀏覽的媒體庫,供 Android Auto 等設備使用。