在Android XML布局开发中,实现文件或数据异步上传时,传统的Thread+Handler或者回调方式容易出现代码嵌套、线程管理复杂的问题,Kotlin Coroutines通过挂起和恢复机制,能以同步的写法实现异步逻辑,大幅降低代码复杂度。

环境准备与依赖配置
首先需要在项目的build.gradle文件中添加Kotlin协程相关依赖,确保项目支持协程功能。如果是新版本Android Studio创建的项目,通常已经默认集成基础协程依赖,可额外添加网络请求相关的库用于上传逻辑。
dependencies {
// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
// Android协程扩展,包含主线程调度器
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
// 网络请求库,用于上传操作
implementation "com.squareup.okhttp3:okhttp:4.11.0"
}
XML布局设计
在activity_main.xml中设计简单的上传界面,包含选择文件按钮、上传按钮和进度显示文本,布局采用线性垂直排列,适配大多数设备屏幕。
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center">
<Button
android:id="@+id/btn_select_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="选择上传文件"/>
<Button
android:id="@+id/btn_start_upload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始上传"
android:layout_marginTop="16dp"/>
<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="未开始上传"
android:layout_marginTop="32dp"
android:textSize="16sp"/>
</LinearLayout>
协程作用域定义
在Activity中定义协程作用域,推荐使用viewModelScope如果结合ViewModel开发,或者自定义协程作用域并在生命周期结束时取消,避免内存泄漏。以下示例采用自定义作用域的方式,在Activity销毁时取消所有协程任务。
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.*
import java.io.File
import java.io.IOException
class MainActivity : AppCompatActivity() {
// 自定义协程作用域,IO线程用于网络操作,主线程用于更新UI
private val uploadScope = CoroutineScope(Dispatchers.IO + Job())
private var uploadFile: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initView()
}
private fun initView() {
// 选择文件逻辑,此处简化为模拟文件路径
findViewById<Button>(R.id.btn_select_file).setOnClickListener {
// 模拟选择文件,实际项目中可调用系统文件选择器
uploadFile = File(getExternalFilesDir(null), "test_upload.xml")
// 创建模拟文件用于测试
if (!uploadFile!!.exists()) {
uploadFile!!.writeText("<data><item>测试上传内容</item></data>")
}
findViewById<TextView>(R.id.tv_progress).text = "已选择文件:${uploadFile!!.name}"
}
// 上传按钮点击事件
findViewById<Button>(R.id.btn_start_upload).setOnClickListener {
uploadFile?.let { file ->
startUpload(file)
} ?: run {
findViewById<TextView>(R.id.tv_progress).text = "请先选择上传文件"
}
}
}
override fun onDestroy() {
super.onDestroy()
// 取消协程作用域中的所有任务,避免内存泄漏
uploadScope.cancel()
}
}
异步上传逻辑实现
使用协程的launch启动上传任务,在IO线程执行网络请求,通过withContext切换到主线程更新UI,避免手动切换线程的繁琐操作。
private fun startUpload(file: File) {
uploadScope.launch {
// 切换到主线程更新上传开始状态
withContext(Dispatchers.Main) {
findViewById<TextView>(R.id.tv_progress).text = "上传中..."
}
try {
// 执行上传操作,在IO线程中执行
val result = uploadFileToServer(file)
// 上传成功,切换到主线程更新结果
withContext(Dispatchers.Main) {
findViewById<TextView>(R.id.tv_progress).text = "上传成功:$result"
}
} catch (e: Exception) {
// 上传失败,切换到主线程更新错误信息
withContext(Dispatchers.Main) {
findViewById<TextView>(R.id.tv_progress).text = "上传失败:${e.message}"
}
}
}
}
// 实际的上传方法,使用OkHttp发送POST请求
private suspend fun uploadFileToServer(file: File): String = withContext(Dispatchers.IO) {
val client = OkHttpClient()
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(
"file",
file.name,
RequestBody.create(MediaType.parse("text/xml"), file)
)
.build()
val request = Request.Builder()
.url("http://192.168.0.1/upload") // 替换为实际上传接口地址
.post(requestBody)
.build()
val response = client.newCall(request).execute()
if (response.isSuccessful) {
response.body()?.string() ?: "上传成功但无返回内容"
} else {
throw IOException("服务器返回错误码:${response.code()}")
}
}
上传进度更新实现
如果需要显示上传进度,可以自定义RequestBody监听上传进度,再通过协程切换到主线程更新进度文本。
// 自定义带进度监听的RequestBody
class ProgressRequestBody(
private val requestBody: RequestBody,
private val onProgress: (Long, Long) -> Unit
) : RequestBody() {
override fun contentType(): MediaType? {
return requestBody.contentType()
}
override fun contentLength(): Long {
return requestBody.contentLength()
}
override fun writeTo(sink: okio.BufferedSink) {
val countingSink = okio.BufferedSink(sink.outputStream())
val bufferedSink = okio.buffer(countingSink)
requestBody.writeTo(bufferedSink)
bufferedSink.flush()
}
private inner class CountingSink(private val outputStream: java.io.OutputStream) : okio.Sink {
private var bytesWritten = 0L
override fun write(source: okio.Buffer, byteCount: Long) {
outputStream.write(source.readByteArray(byteCount.toInt()))
bytesWritten += byteCount
onProgress(bytesWritten, contentLength())
}
override fun flush() {
outputStream.flush()
}
override fun close() {
outputStream.close()
}
override fun timeout(): okio.Timeout {
return okio.Timeout.NONE
}
}
}
修改上传方法,使用自定义的ProgressRequestBody监听进度,并通过协程通道或者回调传递进度信息到主线程。
private suspend fun uploadFileWithProgress(file: File, onProgress: (Int) -> Unit): String = withContext(Dispatchers.IO) {
val client = OkHttpClient()
val originalBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(
"file",
file.name,
RequestBody.create(MediaType.parse("text/xml"), file)
)
.build()
val progressBody = ProgressRequestBody(originalBody) { bytesWritten, totalBytes ->
val progress = ((bytesWritten * 100) / totalBytes).toInt()
// 通过协程切换到主线程更新进度
CoroutineScope(Dispatchers.Main).launch {
onProgress(progress)
}
}
val request = Request.Builder()
.url("http://192.168.0.1/upload")
.post(progressBody)
.build()
val response = client.newCall(request).execute()
if (response.isSuccessful) {
response.body()?.string() ?: "上传成功"
} else {
throw IOException("上传失败:${response.code()}")
}
}
注意事项
- 协程作用域需要在对应的生命周期组件中取消,避免Activity或Fragment销毁后协程仍在执行导致内存泄漏。
- 网络请求等耗时操作必须放在IO线程或其他后台调度器中,不能放在主线程执行,否则会触发ANR。
- 上传接口的地址如果是
ippipp.com需要替换成ipipp.com,本地测试地址127.0.0.1和192.168.0.1可正常使用。 - 实际项目中需要添加文件权限申请逻辑,针对Android 6.0以上版本需要动态申请存储权限。
Kotlin_CoroutinesAndroidXML异步上传协程修改时间:2026-07-07 02:30:16