当前位置: 首页 > wzjs >正文

网站运营 解决方案上海免费网站建站模板

网站运营 解决方案,上海免费网站建站模板,武进建设局网站为何老是打不开,链接下载引言:为什么需要文件上传进度监听? 在移动应用开发中,文件上传是常见需求。特别是当用户上传大文件(如视频、高清图片)时,缺乏进度反馈会导致糟糕的用户体验。本文将深入探讨如何通过封装OkHttp的RequestB…

引言:为什么需要文件上传进度监听?

在移动应用开发中,文件上传是常见需求。特别是当用户上传大文件(如视频、高清图片)时,缺乏进度反馈会导致糟糕的用户体验。本文将深入探讨如何通过封装OkHttp的RequestBody实现高效、灵活的文件上传进度监听。

技术架构图

客户端
ProgressRequestBody
CountingSink
OkHttp原始Sink
网络传输
ProgressListener
UI更新

关键技术组件

  1. ProgressListener:进度回调接口
  2. CountingSink:字节计数拦截器
  3. ProgressRequestBody:RequestBody封装器

完整代码实现

1. 进度监听接口

interface ProgressListener {fun onProgress(bytesWritten: Long, contentLength: Long, identifier: String? = null,done: Boolean = false)
}

2. 字节计数Sink实现

import okio.ForwardingSink
import okio.Sink
import java.io.IOExceptionclass CountingSink(delegate: Sink,private val listener: ProgressListener,private val contentLength: Long,private val identifier: String? = null
) : ForwardingSink(delegate) {private var bytesWritten = 0Lprivate var lastReportedPercent = -1@Throws(IOException::class)override fun write(source: Buffer, byteCount: Long) {super.write(source, byteCount)bytesWritten += byteCount// 优化:每1%进度回调一次,避免频繁更新UIval currentPercent = (100 * bytesWritten / contentLength).toInt()if (currentPercent != lastReportedPercent) {listener.onProgress(bytesWritten, contentLength, identifier)lastReportedPercent = currentPercent}}@Throws(IOException::class)override fun close() {super.close()// 最终完成回调listener.onProgress(bytesWritten, contentLength, identifier, true)}
}

3. 可监听进度的RequestBody封装

import okhttp3.MediaType
import okhttp3.RequestBody
import okio.BufferedSink
import okio.Okioclass ProgressRequestBody(private val delegate: RequestBody,private val listener: ProgressListener,private val identifier: String? = null
) : RequestBody() {override fun contentType(): MediaType? = delegate.contentType()override fun contentLength(): Long = delegate.contentLength()@Throws(IOException::class)override fun writeTo(sink: BufferedSink) {val countingSink = CountingSink(sink, listener, contentLength(),identifier)val bufferedSink = Okio.buffer(countingSink)delegate.writeTo(bufferedSink)// 确保所有数据写入bufferedSink.flush()}
}

实战应用:多文件上传示例

Retrofit服务接口定义

interface UploadService {@Multipart@POST("upload")suspend fun uploadFiles(@Part files: List<MultipartBody.Part>): Response<UploadResult>
}

多文件上传管理器

import android.os.Handler
import android.os.Looper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.MultipartBody
import retrofit2.Response
import java.io.Fileclass UploadManager(private val service: UploadService,private val uiHandler: Handler = Handler(Looper.getMainLooper())
) {// 进度回调映射:文件路径 -> 进度百分比private val progressMap = mutableMapOf<String, Int>()// 进度监听器实现private val progressListener = object : ProgressListener {override fun onProgress(bytesWritten: Long, contentLength: Long, identifier: String?, done: Boolean) {identifier?.let { filePath ->val progress = (100 * bytesWritten / contentLength).toInt()progressMap[filePath] = progress// 更新UI(切换到主线程)uiHandler.post {// 这里简化处理,实际应通过LiveData或回调通知println("文件 $filePath 上传进度: $progress%")}}}}suspend fun uploadFiles(files: List<File>): UploadResult {return withContext(Dispatchers.IO) {// 准备上传部件val parts = files.map { file ->val requestBody = file.asRequestBody("application/octet-stream".toMediaType())val progressBody = ProgressRequestBody(requestBody,progressListener,file.absolutePath // 使用文件路径作为标识符)MultipartBody.Part.createFormData("files", file.name, progressBody)}// 执行上传val response = service.uploadFiles(parts)if (response.isSuccessful) {response.body() ?: throw UploadException("上传成功但返回空数据")} else {throw UploadException("上传失败: ${response.code()}")}}}
}class UploadException(message: String) : Exception(message)

Android中使用示例

// 在ViewModel中
class UploadViewModel : ViewModel() {private val uploadManager = UploadManager(RetrofitClient.uploadService)// 使用LiveData跟踪进度和结果val uploadProgress = MutableLiveData<Map<String, Int>>()val uploadResult = MutableLiveData<Result<UploadResult>>()fun uploadFiles(files: List<File>) {viewModelScope.launch {uploadResult.value = Result.loading()try {val result = uploadManager.uploadFiles(files)uploadResult.value = Result.success(result)} catch (e: Exception) {uploadResult.value = Result.error(e)}}}// 更新进度(实际应用中UploadManager应回调此方法)fun updateProgress(progressMap: Map<String, Int>) {uploadProgress.value = progressMap}
}// 在Activity/Fragment中观察
viewModel.uploadProgress.observe(this) { progressMap ->progressMap.forEach { (filePath, progress) ->// 更新对应文件的进度条fileProgressBars[filePath]?.progress = progress}
}viewModel.uploadResult.observe(this) { result ->when (result.status) {Status.LOADING -> showLoading()Status.SUCCESS -> showSuccess(result.data)Status.ERROR -> showError(result.message)}
}

性能优化策略

1. 回调频率控制

// 在CountingSink中添加优化逻辑
private var lastReportedTime = 0L
private val REPORT_INTERVAL = 200L // 200毫秒override fun write(source: Buffer, byteCount: Long) {super.write(source, byteCount)bytesWritten += byteCountval currentTime = System.currentTimeMillis()if (currentTime - lastReportedTime > REPORT_INTERVAL || bytesWritten == contentLength) {listener.onProgress(bytesWritten, contentLength, identifier)lastReportedTime = currentTime}
}

2. 弱引用防止内存泄漏

class WeakProgressListener(private val delegate: ProgressListener
) : ProgressListener {private val weakRef = WeakReference(delegate)override fun onProgress(bytesWritten: Long, contentLength: Long, identifier: String?, done: Boolean) {weakRef.get()?.onProgress(bytesWritten, contentLength, identifier, done)}
}// 使用方式
val progressBody = ProgressRequestBody(requestBody,WeakProgressListener(progressListener),file.absolutePath
)

3. 大文件分块上传

class ChunkedUploader(private val file: File,private val chunkSize: Long = 1024 * 1024 // 1MB
) {suspend fun uploadWithProgress(listener: ProgressListener) {val totalSize = file.length()var uploaded = 0Lvar chunkIndex = 0file.inputStream().use { input ->val buffer = ByteArray(chunkSize.toInt())var bytesRead: Intwhile (input.read(buffer).also { bytesRead = it } != -1) {// 上传当前分块uploadChunk(chunkIndex, buffer, bytesRead)// 更新进度uploaded += bytesReadlistener.onProgress(uploaded, totalSize, file.absolutePath)chunkIndex++}}// 最终完成回调listener.onProgress(totalSize, totalSize, file.absolutePath, true)}private suspend fun uploadChunk(index: Int, data: ByteArray, size: Int) {// 实现分块上传逻辑}
}

与其他技术对比

技术方案优点缺点适用场景
RequestBody封装底层实现、高效灵活、与OkHttp无缝集成需要自定义封装、对新手有一定难度需要精细控制上传过程的场景
Interceptor拦截器统一处理所有请求、位置集中难以区分不同文件进度、实现复杂需要全局监控的场景
系统级进度监听简单易用、无需额外代码功能有限、无法自定义回调频率简单小文件上传
分块上传+自定义协议支持断点续传、精确控制实现复杂、需要服务端配合超大文件上传、不稳定网络环境

关键点总结

  1. 核心原理:通过自定义Sink拦截写入操作实现字节计数
  2. 进度计算进度百分比 = (已上传字节数 / 文件总大小) * 100
  3. 线程安全:进度回调在IO线程,更新UI需切主线程
  4. 性能优化
    • 控制回调频率(时间阈值或进度阈值)
    • 使用弱引用防止内存泄漏
    • 大文件采用分块上传策略
  5. 多文件支持:为每个文件分配唯一标识符
  6. 容错处理:处理除零异常和取消上传逻辑

高级应用场景

1. 断点续传实现

class ResumableProgressRequestBody(delegate: RequestBody,listener: ProgressListener,identifier: String?,private val startPosition: Long // 从上次中断处继续
) : ProgressRequestBody(delegate, listener, identifier) {override fun writeTo(sink: BufferedSink) {val countingSink = CountingSink(sink, listener, contentLength(),identifier).apply {bytesWritten = startPosition // 设置起始位置}val bufferedSink = Okio.buffer(countingSink)delegate.writeTo(bufferedSink)bufferedSink.flush()}
}

2. 上传速度计算

// 在ProgressListener中添加速度回调
interface AdvancedProgressListener : ProgressListener {fun onSpeedCalculated(bytesPerSecond: Double, identifier: String?)
}// 在CountingSink中实现速度计算
private var lastBytesWritten = 0L
private var lastTimeMillis = System.currentTimeMillis()override fun write(source: Buffer, byteCount: Long) {super.write(source, byteCount)val currentTime = System.currentTimeMillis()val elapsed = currentTime - lastTimeMillisif (elapsed > 1000) { // 每秒计算一次val deltaBytes = bytesWritten - lastBytesWrittenval speed = deltaBytes / (elapsed / 1000.0)(listener as? AdvancedProgressListener)?.onSpeedCalculated(speed, identifier)lastBytesWritten = bytesWrittenlastTimeMillis = currentTime}
}

最佳实践建议

  1. 进度显示策略

    • 小文件(<5MB):显示百分比
    • 中等文件(5-50MB):显示百分比+剩余时间
    • 大文件(>50MB):显示百分比+速度+剩余时间
  2. 异常处理增强

    override fun writeTo(sink: BufferedSink) {try {// ... 正常写入逻辑} catch (e: IOException) {listener.onError(e, identifier)throw e} finally {// 清理资源}
    }
    
  3. 取消上传支持

    class CancellableProgressRequestBody(delegate: RequestBody,listener: ProgressListener,identifier: String?,private val isCancelled: () -> Boolean // 取消状态检查
    ) : ProgressRequestBody(delegate, listener, identifier) {@Throws(IOException::class)override fun writeTo(sink: BufferedSink) {val countingSink = object : CountingSink(sink, listener, contentLength(), identifier) {override fun write(source: Buffer, byteCount: Long) {if (isCancelled()) throw IOException("Upload cancelled")super.write(source, byteCount)}}// ... 其余逻辑}
    }
    

总结

本文详细介绍了通过封装OkHttp的RequestBody实现文件上传进度监听的技术方案。核心要点包括:

  1. 通过自定义CountingSink拦截和计算已上传字节数
  2. 使用ProgressRequestBody包装原始RequestBody
  3. 实现多文件上传的进度区分
  4. 性能优化和内存管理技巧
  5. 高级应用如断点续传和速度计算

这种方案具有高度灵活性和可扩展性,能够满足从简单到复杂的各种文件上传需求。

http://www.dtcms.com/wzjs/822538.html

相关文章:

  • 北京招聘高级网站开发工程师嘉兴网络项目建站公司
  • 网络销售平台上市公司有哪些谷歌广告优化师
  • 只做健康产品的网站可不可以建网站做微商
  • 网站建设的系统设计wordpress章节分页
  • 没有备案号的网站简述建设一个网站的具体步骤
  • 东莞制作公司网站的公司舆情监控系统
  • 个人做外贸网站违法吗房地产信息管理系统软件
  • 启用中文域名大网站wordpress文章管理
  • 网站建设公司初心网站建设深圳给源码
  • flash 企业网站 源码网页制作三合一案例教程
  • 兰州网站建设实验总结全国较好的网站建设公司
  • 交互式网站开发技术有哪些网站设计与开发怎么做
  • 北京市房山建设培训学校网站宣传部网站建设计划书
  • 专题网站开发工具给我免费播放的电影在线观看
  • 恩施网站建设公司山东省建设资格注册中心网站
  • 一般请人做网站和app多少钱平台网站建设网站
  • 网站后台管理模板免费下载网站建设是前端么
  • 服装箱包网站建设网站建设太金手指六六二五
  • 佛山科技网站建设高端网站设计公司有
  • 潘多拉固件建设网站微信里的小程序不见了
  • 投资交易网站开发群辉wordpress端口
  • 凡客建站网临沂个人做网站
  • 网页设计模板素材网站大全做网站都需要什么步骤
  • 淄博做网站的公司都有哪些电子商务搭建平台
  • 推销什么企业做网站和app6企业网站的建设目的包含什么
  • 个人网站做支付接口小程序招商加盟平台
  • 常州市城市建设局网站南京设计公司前十名
  • 济南城之运维网络科技seo网站推广软件
  • 东莞网络营销型网站一站式做网站价格
  • 免费的黄金网站有哪些企业网站seo运营