Kotlin Coroutines如何用于Android XML异步上传

来源:站长联盟作者:下班再修头衔:程序员
导读:本期聚焦于小伙伴创作的《Kotlin Coroutines如何用于Android XML异步上传》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《Kotlin Coroutines如何用于Android XML异步上传》有用,将其分享出去将是对创作者最好的鼓励。

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

Kotlin Coroutines如何用于Android XML异步上传

环境准备与依赖配置

首先需要在项目的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.1192.168.0.1可正常使用。
  • 实际项目中需要添加文件权限申请逻辑,针对Android 6.0以上版本需要动态申请存储权限。

Kotlin_CoroutinesAndroidXML异步上传协程修改时间:2026-07-07 02:30:16

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。