向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
相关产品推荐
相关产品推荐

