上传文件worker

class UploadWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    companion object {
        private const val PRESIGNED_URL = "PRESIGNED_URL"
        private const val FILE_PATH = "FILE_PATH"
        private const val ATTEMPT_COUNT = "ATTEMPT_COUNT"
        private const val MAX_ATTEMPT = 3
        fun addUploadTask(
            context: Context,
            preUrl: String,
            filePath: String,
            attemptCount: Int = 1
        ) {
            LogUtils.d("UploadWorker addUploadTask $filePath")
            // 构建输入参数
            val inputData = Data.Builder()
                .putString(PRESIGNED_URL, preUrl)
                .putString(FILE_PATH, filePath)
                .putInt(ATTEMPT_COUNT, attemptCount)
                .build()
            // 创建 WorkRequest
            val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()
            val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
                .setInputData(inputData)
                .setConstraints(constraints)
                .build()
            // 提交任务
            WorkManager.getInstance(context).enqueue(uploadWorkRequest)
        }
    }

    override suspend fun doWork(): Result {
        // 从输入参数中获取预签名 URL 和文件路径
        val presignedUrl = inputData.getString(PRESIGNED_URL) ?: return Result.failure()
        val filePath = inputData.getString(FILE_PATH) ?: return Result.failure()
        val attempt = inputData.getInt(ATTEMPT_COUNT, 1) // 默认从第一次开始
        LogUtils.d("UploadWorker addUploadTask doWork attempt=$attempt")
        return try {
            val file = File(filePath)
            val client = OkHttpClient()
            val request = Request.Builder()
                .url(presignedUrl)
                .put(file.asRequestBody("text/plain".toMediaType()))
                .build()

            val response = client.newCall(request).execute()
            LogUtils.d("UploadWorker addUploadTask doWork response=$response")
            if (response.isSuccessful) {
                file.delete()
                Result.success()
            } else {
                deleteOrRetry(attempt,filePath,presignedUrl)
                Result.failure()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            deleteOrRetry(attempt,filePath,presignedUrl)
            Result.failure()
        }
    }

    private fun deleteOrRetry(attemptCount: Int,filePath: String,preUrl: String,){
        if (attemptCount < MAX_ATTEMPT) {
            addUploadTask(applicationContext, preUrl, filePath, attemptCount + 1)
        } else {
            deleteFile(filePath)
        }
    }

    private fun deleteFile(filePath:String){
        try {
            val file = File(filePath)
            if (file.exists()) file.delete()
        }catch (e:Exception){
            e.printStackTrace()
        }
    }
}

如果文件很大,大到一个什么范围需要分段上传呢?

  • 内存压力:大文件在上传时如果全部加载进内存,容易造成 OOM(Out of Memory)。

  • 上传稳定性:网络中断时,如果是整个文件上传,一旦失败就要重头再来,耗时耗流量。

  • 可控性更强:可以做断点续传、上传进度监控、更好地用户体验。

  • <10MB:直接上传通常没问题

  • 10MB - 50MB:需要视设备内存情况和网络稳定性决定

  • >50MB:推荐强烈使用分段上传

class StreamingFileRequestBody(
    private val file: File,
    private val contentType: MediaType,
    private val bufferSize: Int = DEFAULT_BUFFER_SIZE // 可自定义缓存大小
) : RequestBody() {

    override fun contentType(): MediaType = contentType

    override fun contentLength(): Long = file.length()

    override fun writeTo(sink: BufferedSink) {
        file.inputStream().use { input ->
            val buffer = ByteArray(bufferSize)
            var bytesRead: Int
            while (input.read(buffer).also { bytesRead = it } >= 0) {
                sink.write(buffer, 0, bytesRead)
            }
        }
    }
}
val request = Request.Builder()
    .url(presignedUrl)
    .put(
        StreamingFileRequestBody(
            file = file,
            contentType = "application/octet-stream".toMediaType(),
            bufferSize = 8192 // 8KB buffer,默认就很合理
        )
    )
    .build()

val response = OkHttpClient().newCall(request).execute()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值