You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Retrofit带ID的GET请求失效排查及图书封面展示实现

问题排查与解决方案:Google Books API BookShelf应用

一、getBookDetails()未执行的原因分析

你的代码里getBookDetails()没被调用,核心原因有两个:

  1. 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()调用)都不会执行,且不会触发日志。
  2. 不必要的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:36:01