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

向Google Sheet写入数据:协程与NetworkOnMainThreadException问题

问题解答

1. NetworkOnMainThreadException 异常原因

Android系统明确规定主线程(UI线程)不能执行网络请求等耗时操作——这类操作会阻塞UI渲染,导致应用卡顿甚至触发ANR(应用无响应)。你的代码中service.spreadsheets().values().update(...).execute()是同步阻塞的网络请求方法,直接在按钮点击的主线程中调用,触发了系统的线程安全检查,因此抛出该异常。

2. 当前实现是否正确?

不正确。submitExpensesToGoogleSheet方法是同步执行网络请求,且被主线程直接调用,完全违反了Android的线程规则,必然导致崩溃。

3. 能否不使用协程完成该功能?

可以,但手动线程管理容易引发内存泄漏、线程复用混乱等问题,仅作为备选方案:

方案一:手动开启子线程

将网络请求逻辑放到独立线程中执行,如需更新UI再切回主线程:

import android.os.Handler
import android.os.Looper

class SheetsAPI(private val application: Application, credential: GoogleCredential) {
    // 原有成员变量不变
    private val jsonFactory = JacksonFactory.getDefaultInstance()
    private val httpTransport = NetHttpTransport()
    private val service: Sheets = Sheets.Builder(httpTransport, jsonFactory, credential)
        .setApplicationName(application.getString(R.string.app_name))
        .build()

    fun submitExpensesToGoogleSheet(expenses: List<ExpenseData>){
        Thread {
            try {
                val spreadsheetId = "_____spreadsheetid____"
                val range = "Sheet1!A1:B2"
                val valueRange = ValueRange().setValues(
                    listOf(
                        listOf("Expense", "Amount"),
                        *expenses.map { listOf(it.title, it.amount) }.toTypedArray()
                    )
                )

                service.spreadsheets().values().update(spreadsheetId, range, valueRange)
                    .setValueInputOption("RAW")
                    .execute()

                println("Data sent to Sheet successfully")

                // 切回主线程更新UI(如弹出成功提示)
                Handler(Looper.getMainLooper()).post {
                    // 执行UI更新操作
                }
            } catch (e: Exception) {
                e.printStackTrace()
                // 切回主线程处理异常提示
                Handler(Looper.getMainLooper()).post {
                    // 异常处理UI逻辑
                }
            }
        }.start()
    }
}

方案二:使用Google API异步方法

部分Google API客户端提供了异步执行方法(如executeAsync()),通过回调处理结果:

class SheetsAPI(private val application: Application, credential: GoogleCredential) {
    // 原有成员变量不变...

    fun submitExpensesToGoogleSheet(expenses: List<ExpenseData>){
        val spreadsheetId = "_____spreadsheetid____"
        val range = "Sheet1!A1:B2"
        val valueRange = ValueRange().setValues(
            listOf(
                listOf("Expense", "Amount"),
                *expenses.map { listOf(it.title, it.amount) }.toTypedArray()
            )
        )

        service.spreadsheets().values().update(spreadsheetId, range, valueRange)
            .setValueInputOption("RAW")
            .executeAsync(object : ResultCallback<UpdateValuesResponse> {
                override fun onResult(result: UpdateValuesResponse?, e: GoogleJsonResponseException?) {
                    if (e == null && result != null) {
                        println("Data sent to Sheet successfully")
                        // 切回主线程更新UI
                        Handler(Looper.getMainLooper()).post {
                            // UI更新逻辑
                        }
                    } else {
                        e?.printStackTrace()
                        // 切回主线程处理异常
                        Handler(Looper.getMainLooper()).post {
                            // 异常处理UI逻辑
                        }
                    }
                }
            })
    }
}

4. 使用协程的正确集成方式

协程是Android官方推荐的异步处理方案,代码简洁且易于维护,具体步骤如下:

步骤1:修改SheetsAPI方法为挂起函数

将网络请求逻辑切换到IO调度器执行:

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class SheetsAPI(private val application: Application, credential: GoogleCredential) {
    // 原有成员变量不变...

    // 定义为挂起函数,仅能在协程或其他挂起函数中调用
    suspend fun submitExpensesToGoogleSheet(expenses: List<ExpenseData>){
        withContext(Dispatchers.IO) {
            val spreadsheetId = "_____spreadsheetid____"
            val range = "Sheet1!A1:B2"
            val valueRange = ValueRange().setValues(
                listOf(
                    listOf("Expense", "Amount"),
                    *expenses.map { listOf(it.title, it.amount) }.toTypedArray()
                )
            )

            service.spreadsheets().values().update(spreadsheetId, range, valueRange)
                .setValueInputOption("RAW")
                .execute()

            println("Data sent to Sheet successfully")
        }
        // 网络请求完成后自动回到调用协程的上下文(若从主线程启动,可直接更新UI)
    }
}

步骤2:在Compose按钮点击中启动协程

使用rememberCoroutineScope获取协程作用域,在点击事件中启动协程调用方法:

import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import kotlinx.coroutines.launch

// 示例Compose函数
fun ExpenseSubmitScreen(expenseList: List<ExpenseData>) {
    val coroutineScope = rememberCoroutineScope()
    val context = LocalContext.current
    // 初始化SheetsAPI实例(需传入你的credential)
    val sheetsApi = SheetsAPI(context.applicationContext as Application, /* credential */)

    Column {
        Button(onClick = {
            coroutineScope.launch {
                try {
                    sheetsApi.submitExpensesToGoogleSheet(expenseList)
                    // 直接更新UI,比如显示成功提示
                } catch (e: Exception) {
                    e.printStackTrace()
                    // 处理异常,比如显示错误提示
                }
            }
        }) {
            Text("提交到Google Sheet")
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 02:25:55