Retrofit带ID的GET请求失效排查及图书封面展示实现
问题排查与解决方案:Google Books API BookShelf应用
一、getBookDetails()未执行的原因分析
你的代码里getBookDetails()没被调用,核心原因有两个:
API响应处理不严谨
- 未先判断
listResult.isSuccessful,如果搜索请求返回非2xx状态码,listResult.body()会是null,导致listResult.body()?.items?.get(0)?.id为null,_books.value被设为null,后续_books.value?.let { getBookDetails(it) }因为null不会执行。 - 即使搜索请求成功,
items可能是空列表,调用get(0)会抛出IndexOutOfBoundsException,但你的try块只捕获IOException,这个异常会直接终止协程,后续代码(包括getBookDetails()调用)都不会执行,且不会触发日志。
- 未先判断
不必要的LiveData取值操作
你刚把id赋值给_books.value,立刻又从_books.value取值调用getBookDetails(),虽然LiveData的value是同步赋值,但完全可以直接用获取到的id变量,避免额外的空值风险。
二、修复getBookDetails()调用的代码调整
修改ViewModel中的getBooks()函数,完善响应判断、异常捕获逻辑,直接使用获取到的id调用详情接口:
class BookShelfViewModel : ViewModel() { private val _books = MutableLiveData("") val books: LiveData<String> = _books private val _imageUrl = MutableLiveData("") val imageUrl: LiveData<String> = _imageUrl init { getBooks() } private fun getBooks() { viewModelScope.launch { try { val listResult = BooksApi.retrofitService.getBooks("jazz+history") // 先判断请求是否成功 if (listResult.isSuccessful) { val firstBook = listResult.body()?.items?.firstOrNull() if (firstBook != null) { val bookId = firstBook.id _books.value = bookId // 直接用获取到的bookId调用详情接口,无需通过LiveData取值 getBookDetails(bookId) } else { Log.d("App error", "搜索结果为空") } } else { Log.d("App error", "搜索请求失败,状态码:${listResult.code()}") } } catch (e: IOException) { Log.d("App error","网络连接失败") } catch (e: Exception) { // 捕获所有异常,避免协程崩溃 Log.d("App error", "意外错误:${e.message}") } } } // 原getBookDetails()函数保留,无需修改 private fun getBookDetails(bookId: String) { viewModelScope.launch { try { val listResult = BooksApi.retrofitService.getBookById(bookId) Log.d("BookDetails","${listResult.body()}") if (listResult.isSuccessful) { Log.d("ImageUrl","${listResult.body()?.volumeInfo?.imageLinks?.thumbnail}") _imageUrl.value = listResult.body()?.volumeInfo?.imageLinks?.thumbnail } else { Log.d("BookDetails","详情请求失败,状态码:${listResult.code()}") } } catch (e: IOException) { Log.d("App error","详情接口网络连接失败") } } } }
三、图书封面图片展示实现
1. 集成图片加载库
推荐使用Coil(轻量、Kotlin友好),在模块级build.gradle.kts(或build.gradle)中添加依赖:
// Coil 图片加载库 implementation("io.coil-kt:coil:2.5.0")
2. 在Fragment中加载图片
修改CheckingFragment的onViewCreated()方法,完善图片加载逻辑,处理空链接和加载失败的情况:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.books.observe(viewLifecycleOwner) { newBooks -> binding.responseText.text = newBooks } viewModel.imageUrl.observe(viewLifecycleOwner) { imageUrl -> if (!imageUrl.isNullOrEmpty()) { // 使用Coil加载图片 binding.responseImage.load(imageUrl) { // 可选:设置加载占位图 placeholder(R.drawable.placeholder) // 可选:设置加载失败占位图 error(R.drawable.image_load_error) } } else { // 无图片链接时显示默认图 binding.responseImage.setImageResource(R.drawable.no_cover) } } }
3. 批量获取封面链接(按需求补充)
如果需要获取搜索结果中所有图书的封面链接,修改ViewModel使用LiveData<List<String>>存储链接列表:
class BookShelfViewModel : ViewModel() { private val _books = MutableLiveData("") val books: LiveData<String> = _books // 改用List存储多个封面链接 private val _imageUrls = MutableLiveData<List<String>>(emptyList()) val imageUrls: LiveData<List<String>> = _imageUrls init { getBooks() } private fun getBooks() { viewModelScope.launch { try { val listResult = BooksApi.retrofitService.getBooks("jazz+history") if (listResult.isSuccessful) { val bookIds = listResult.body()?.items?.mapNotNull { it.id } ?: emptyList() _books.value = bookIds.joinToString(", ") val imageLinks = mutableListOf<String>() // 遍历所有bookId,批量获取封面链接 for (bookId in bookIds) { val detailResult = BooksApi.retrofitService.getBookById(bookId) if (detailResult.isSuccessful) { detailResult.body()?.volumeInfo?.imageLinks?.thumbnail?.let { imageLinks.add(it) } } } _imageUrls.value = imageLinks } else { Log.d("App error", "搜索请求失败,状态码:${listResult.code()}") } } catch (e: IOException) { Log.d("App error","网络连接失败") } catch (e: Exception) { Log.d("App error", "意外错误:${e.message}") } } } }
之后可以在Fragment中用RecyclerView展示这些封面图片,每个Item使用Coil加载对应链接。
内容的提问来源于stack exchange,提问作者Shoaib Khan
相关产品推荐
相关产品推荐

