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

使用Retrofit发起HTTP请求时Composable视图崩溃求助

Kotlin Retrofit + Compose 请求数据崩溃问题

每次加载SampleView()组合函数时应用崩溃,以下是我向本地Node.js API发起请求的代码:

SampleView 代码

@RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
@Composable
fun SampleView() {
    val data: ArrayOfSampleData = loadData()

    Text(text = data.data.str)
}

@OptIn(DelicateCoroutinesApi::class)
@RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
fun loadData(): ArrayOfSampleData {
    var data: ArrayOfSampleData = (ArrayOfSampleData((SampleDataClass(id = 0, str = "", bools = arrayOf(0)))))

    GlobalScope.launch(Dispatchers.IO) {
        val response = try {
            RetrofitInstance.api.loadData()
        } catch (e: HttpException) {
            print(e.message)
            return@launch
        } catch (e: IOException) {
            print(e.message)
            return@launch
        }

        if (response.isSuccessful && response.body() != null) {
            withContext(Dispatchers.Main) {
                data = response.body()!!
            }
        }
    }

    return data
}

Retrofit 配置代码

object RetrofitInstance {

    private val client = OkHttpClient.Builder().apply {
        addInterceptor(Interceptor())
    }.build()

    val api : ApiInterface by lazy {
        Retrofit.Builder()
            .baseUrl(Util.Base)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiInterface::class.java)
    }
}

class Interceptor: Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
            .newBuilder()
            .addHeader("Content-Type", "application/json")
            .addHeader("apikey", "apikey")
            .build()

        return chain.proceed(request)
    }
}

interface ApiInterface {
@GET("/test")
    suspend fun loadData(): Response<ArrayOfSampleData>;
}

object Util {
    const val Base = "localhost:4300"
}

数据类代码

data class SampleDataClass(
    val id: Int,
    val str: String,
    val bools: Array<Int>
)

data class ArrayOfSampleData(
    val data: Array<SampleDataClass>
)

崩溃原因及修正方案

  1. 数组属性访问错误
    原代码中data.data.str直接访问数组的str属性,这是错误的——data.data是Array<SampleDataClass>类型,必须通过索引访问元素后再获取属性,比如data.data[0].str。这是直接导致崩溃的原因。

  2. 异步请求未结合Compose状态管理
    loadData()是同步函数,但内部用GlobalScope.launch发起异步请求,直接返回初始值,且修改data变量无法触发Compose重组。需改用mutableStateOf保存状态,结合LaunchedEffect在组件生命周期内发起请求。

  3. GlobalScope不当使用
    GlobalScope的生命周期不受Compose组件控制,易导致内存泄漏。应使用rememberCoroutineScope或LaunchedEffect提供的协程作用域。

  4. BaseUrl缺少协议
    Retrofit要求BaseUrl必须包含http://或https://,原代码中localhost:4300会导致请求失败。

  5. 类名冲突
    自定义的Interceptor类与OkHttp的Interceptor接口重名,易引发编译或运行时混淆,需重命名自定义拦截器。


修正后的完整代码

SampleView 修正版

@RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
@Composable
fun SampleView() {
    // 用mutableStateOf保存数据状态,触发Compose重组
    val dataState = remember { mutableStateOf<ArrayOfSampleData?>(null) }

    // 组件首次加载时发起请求
    LaunchedEffect(Unit) {
        val response = try {
            RetrofitInstance.api.loadData()
        } catch (e: HttpException) {
            e.printStackTrace()
            return@LaunchedEffect
        } catch (e: IOException) {
            e.printStackTrace()
            return@LaunchedEffect
        }

        if (response.isSuccessful && response.body() != null) {
            dataState.value = response.body()
        }
    }

    // 根据状态显示不同内容
    val data = dataState.value
    when {
        data == null -> Text(text = "加载中...")
        data.data.isEmpty() -> Text(text = "无数据")
        else -> Text(text = data.data[0].str)
    }
}

Retrofit 配置修正版

object RetrofitInstance {

    private val client = OkHttpClient.Builder().apply {
        addInterceptor(AuthInterceptor()) // 重命名拦截器避免冲突
    }.build()

    val api : ApiInterface by lazy {
        Retrofit.Builder()
            .baseUrl("http://${Util.Base}") // 添加http协议
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiInterface::class.java)
    }
}

// 重命名为AuthInterceptor,避免与OkHttp的Interceptor冲突
class AuthInterceptor : okhttp3.Interceptor {
    override fun intercept(chain: okhttp3.Interceptor.Chain): okhttp3.Response {
        val request = chain.request()
            .newBuilder()
            .addHeader("Content-Type", "application/json")
            .addHeader("apikey", "apikey")
            .build()

        return chain.proceed(request)
    }
}

interface ApiInterface {
    @GET("/test")
    suspend fun loadData(): Response<ArrayOfSampleData>
}

object Util {
    const val Base = "localhost:4300"
}

数据类修正版(改用List替代Array,提升Gson兼容性)

data class SampleDataClass(
    val id: Int,
    val str: String,
    val bools: List<Int>
)

data class ArrayOfSampleData(
    val data: List<SampleDataClass>
)

内容的提问来源于stack exchange,提问作者mfusco

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 03:53:19