【Android】文件分块上传尝试
【Android】文件分块上传
在完成一个项目时,遇到了需要上传长视频的场景,尽管可以手动限制视频清晰度和视频的码率帧率,但仍然避免不了视频大小过大的问题,且由于服务器原因,网络不太稳定。这个时候想到了可以将文件分块。
为什么选择文件分片上传
- 提高上传成功率:在网络不稳定或上传大文件时,一般上传可能因网络中断而导致整个上传过程失败,需要重新开始。而分片上传是将文件分成多个小块分别上传,即使某个分片上传失败,只需重新上传该分片,而不是整个文件,大大提高了上传的成功率。
- 实现断点续传:分片上传可以记录每个分片的上传进度,当上传因某种原因中断后,再次启动上传时,可以从上次中断的位置继续上传未完成的分片,无需从头开始,节省了时间和带宽。
- 并发上传:可以将多个分片同时上传到服务器,利用多线程或并发请求技术,充分利用网络带宽,加快上传速度。特别是对于大文件,并发上传多个分片能够显著缩短上传时间。
文件MD5 摘要计算
public static String getFileMD5String(File file) {MessageDigest messageDigest = null;FileInputStream fileInputStream = null;try {// 1. 初始化 MD5 摘要器messageDigest = MessageDigest.getInstance("MD5");// 2. 打开文件输入流fileInputStream = new FileInputStream(file);byte[] buffer = new byte[8192]; // 8KB 缓冲区int length;// 3. 流式读取文件内容while ((length = fileInputStream.read(buffer)) != -1) {messageDigest.update(buffer, 0, length); // 更新哈希计算}// 4. 生成最终哈希值byte[] digest = messageDigest.digest();// 5. 转换为十六进制字符串StringBuilder sb = new StringBuilder();for (byte b : digest) {String hex = Integer.toHexString(0xFF & b); // 字节转十六进制if (hex.length() == 1) {sb.append('0'); // 补零对齐}sb.append(hex);}return sb.toString();} catch (...) {// 异常处理} finally {// 关闭流}return null;
}
1. 为什么使用 MessageDigest
?
MessageDigest
是 Java 标准库中专门用于生成哈希摘要的类。- 支持多种算法(MD5、SHA-1、SHA-256 等),通过
getInstance("MD5")
指定算法。
2. 为什么分块读取(8KB 缓冲区)?
- 内存效率:直接读取整个文件到内存会导致 OOM(尤其处理大文件时)。
- 性能平衡:
- 过小(如 1KB)→ 增加 I/O 次数,降低性能。
- 过大(如 1MB)→ 占用更多内存,边际收益递减。
3. 流式更新的必要性
while ((length = fileInputStream.read(buffer)) != -1) {messageDigest.update(buffer, 0, length);
}
- 逐块更新哈希状态,避免一次性处理整个文件。
- 即使文件大小超过内存限制,仍可正常计算。
并发上传
public void sendDetectVideo(File file, String token,String fileMD5String, LoadTasksCallBack callBack) {long fileSize = file.length();int totalChunks = (int) Math.ceil(fileSize * 1.0 / CHUNK_SIZE);CountDownLatch latch = new CountDownLatch(totalChunks);Map<Integer, Future<Boolean>> futures = new HashMap<>();for (int i = 0; i < totalChunks; i++) {final int chunkIndex = i;long start = i * CHUNK_SIZE;long end = Math.min((i + 1) * CHUNK_SIZE, fileSize);Callable<Boolean> task = () -> {try {Log.d("TAG", "sendDetectVideo: " + token);return uploadChunk(file, token, start, end, chunkIndex, totalChunks, fileMD5String, callBack);} finally {latch.countDown();}};Future<Boolean> future = executorService.submit(task);futures.put(chunkIndex, future);}try {latch.await();boolean allSuccess = true;for (Future<Boolean> future : futures.values()) {if (!future.get()) {allSuccess = false;break;}}if (allSuccess) {callBack.onSuccess("所有分块上传成功");} else {callBack.onFailed("部分分块上传失败");}} catch (InterruptedException | ExecutionException e) {e.printStackTrace();callBack.onFailed("上传过程中出现异常: " + e.getMessage());} finally {executorService.shutdown();}
}
初始化参数
long fileSize = file.length();
int totalChunks = (int) Math.ceil(fileSize * 1.0 / CHUNK_SIZE);
- 计算总分块数:根据文件大小和预设的
CHUNK_SIZE
(如5MB)计算需要分成多少块。
并发控制工具
CountDownLatch latch = new CountDownLatch(totalChunks);
Map<Integer, Future<Boolean>> futures = new HashMap<>();
- CountDownLatch:用于等待所有分块上传完成(初始值为总分块数)。
- Future集合:保存每个分块上传任务的执行结果。
遍历所有分块
for (int i = 0; i < totalChunks; i++) {final int chunkIndex = i;long start = i * CHUNK_SIZE;long end = Math.min((i + 1) * CHUNK_SIZE, fileSize);Callable<Boolean> task = () -> {try {return uploadChunk(...); // 上传分块} finally {latch.countDown(); // 无论成功与否,计数器减1}};Future<Boolean> future = executorService.submit(task);futures.put(chunkIndex, future);
}
- 分块范围计算:确定每个分块的起始(
start
)和结束(end
)位置。 - 任务定义:每个分块上传逻辑封装为
Callable
任务,上传完成后触发latch.countDown()
。 - 任务提交:将任务提交到线程池
executorService
,保存返回的Future
对象。
等待所有分块完成
try {latch.await(); // 阻塞直到所有分块完成// 检查所有任务结果boolean allSuccess = true;for (Future<Boolean> future : futures.values()) {if (!future.get()) { // 获取任务执行结果allSuccess = false;break;}}// 回调结果if (allSuccess) {callBack.onSuccess("所有分块上传成功");} else {callBack.onFailed("部分分块上传失败");}
} catch (...) {// 异常处理
} finally {executorService.shutdown(); // 关闭线程池
}
- 阻塞等待:
latch.await()
确保主线程等待所有分块上传完成。 - 结果检查:遍历所有
Future
,检查每个分块是否上传成功。 - 回调通知:根据结果调用
onSuccess
或onFailed
。 - 资源释放:关闭线程池。
Build请求体
private boolean uploadChunk(File file, String token, long start, long end, int chunkIndex,int totalChunks, String MD5, LoadTasksCallBack callBack) {RequestParams mToken = new RequestParams();mToken.put("Authorization", "Bearer " + token);MultipartBody.Builder requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM);Log.d(TAG, "uploadChunk: " + chunkIndex + " " + totalChunks + " " + MD5);MultipartBody multipartBody = requestBody.addFormDataPart("md5", MD5).addFormDataPart("chunkIndex", String.valueOf(chunkIndex)).addFormDataPart("totalChunks", String.valueOf(totalChunks)).addFormDataPart("file", "file", createChunkRequestBody(file, chunkIndex, start, end)).build();Request request = createRequest(URL.SEND_VIDEO_FILE_URL, multipartBody, mToken, start);return executeRequest(request, callBack);
}private Request createRequest(String url, MultipartBody multipartBody, RequestParams mToken, long start) {Headers.Builder mHeadersBuilder = new Headers.Builder();for (Map.Entry<String, String> entry : mToken.urlParams.entrySet()) {mHeadersBuilder.add(entry.getKey(), entry.getValue());}Request.Builder requestBuilder = new Request.Builder().url(url).headers(mHeadersBuilder.build()).post(multipartBody);return requestBuilder.build();
}private boolean executeRequest(Request request, LoadTasksCallBack callBack) {try (Response response = client.newCall(request).execute()) {if (response.isSuccessful()) {String string = response.body().string();System.out.println("FileonResponse: " + string);callBack.onSuccess(string);return true;} else if (response.code() == 416) {// 416表示请求的Range无效,可能需要重新上传该分块System.out.println("onResponse: 分块上传失败,重新上传该分块");callBack.onFailed("分块上传失败,重新上传该分块");return false;} else {System.out.println("onResponse: 上传失败,状态码: " + response.code());callBack.onFailed("上传失败,状态码: " + response.code());return false;}} catch (IOException e) {System.out.println("onFailure: " + "上传失败");e.printStackTrace();callBack.onFailed("上传失败: " + e.getMessage());return false;}
}
分块相关
为视频文件的指定分块生成一个RequestBody
对象,用于通过OkHttp将分块数据流式上传到服务器,避免一次性加载大文件到内存。
private RequestBody createChunkRequestBody(File videoFile,int chunkIndex, long start, long end) {return new RequestBody() {@Overridepublic MediaType contentType() {return MediaType.parse("video/mp4");}@Overridepublic void writeTo(BufferedSink sink) throws IOException {try (RandomAccessFile file = new RandomAccessFile(videoFile, "r");FileChannel channel = file.getChannel()) {ByteBuffer buffer = ByteBuffer.allocate(8192);long position = start;long remaining = end - start;while (remaining > 0) {int readSize = (int) Math.min(buffer.capacity(), remaining);buffer.limit(readSize);int bytesRead = channel.read(buffer, position);if (bytesRead == -1) break;sink.write(buffer.array(), 0, bytesRead);position += bytesRead;remaining -= bytesRead;buffer.clear();}}}};
}
方法签名
private RequestBody createChunkRequestBody(File videoFile, // 要上传的视频文件int chunkIndex, // 当前分块的索引(未直接使用)long start, // 分块起始字节位置long end // 分块结束字节位置
)
匿名内部类
返回一个自定义的RequestBody
对象,重写两个关键方法:
contentType()
- 指定内容类型
@Override
public MediaType contentType() {return MediaType.parse("video/mp4"); // 明确告知服务器上传的是MP4视频
}
writeTo(BufferedSink sink)
- 数据写入逻辑
@Override
public void writeTo(BufferedSink sink) throws IOException {try (RandomAccessFile file = new RandomAccessFile(videoFile, "r"); // 只读模式打开文件FileChannel channel = file.getChannel() // 获取NIO文件通道) {ByteBuffer buffer = ByteBuffer.allocate(8192); // 分配8KB缓冲区long position = start; // 当前读取位置long remaining = end - start; // 剩余需读取的字节数while (remaining > 0) {// 确定本次读取的字节数int readSize = (int) Math.min(buffer.capacity(), remaining);buffer.limit(readSize); // 设置缓冲区读取上限// 从文件指定位置读取数据到缓冲区int bytesRead = channel.read(buffer, position);if (bytesRead == -1) break; // 文件已读完// 将缓冲区数据写入网络流sink.write(buffer.array(), 0, bytesRead);// 更新位置和剩余字节数position += bytesRead;remaining -= bytesRead;buffer.clear(); // 重置缓冲区供下次使用}}
}
流程示意
+----------------+ +----------------+ +----------------+
| 视频文件 | | ByteBuffer | | OkHttp请求流 |
| (分块范围: | ----> | (8KB缓冲区) | ----> | (BufferedSink) |
| start - end) | +----------------+ +----------------+
+----------------+▲| 每次定位到| 新的position+-----------------+
相关文章:
【Android】文件分块上传尝试
【Android】文件分块上传 在完成一个项目时,遇到了需要上传长视频的场景,尽管可以手动限制视频清晰度和视频的码率帧率,但仍然避免不了视频大小过大的问题,且由于服务器原因,网络不太稳定。这个时候想到了可以将文件分…...

超详细Docker教程
前言:大家在在Linux上部署mysql及其他软件时,大家想一想自己最大的感受是什么? 我相信,除了个别天赋异禀的人以外,大多数人都会有相同的感受,那就是麻烦。核心体现在三点: 命令太多了ÿ…...

Java项目拷打(外卖+点评)
一、点评星球(黑马点评) 1、项目概述 1.1、项目简介 本项目是基于Spring Boot与Redis深度整合的前后端分离的点评平台。系统以Redis为核心技术支撑,重点解决高并发场景下的缓存穿透、击穿、雪崩等问题,涵盖商户展示、优惠券秒杀…...
hadoop中了解yarm
Hadoop中的YARN(Yet Another Resource Negotiator)是一种新的Hadoop资源管理器,是一个通用资源管理系统,可为上层应用提供统一的资源管理和调度。以下是其相关介绍: 核心思想 将JobTracker的资源管理和作业调度/监控功…...
Android usb网络共享详解
Android usb网络共享详解 文章目录 Android usb网络共享详解一、前言二、USB网络共享使用的前提1、Android设备支持adb 并且打开usb开关2、原生Settings能看到USB网络共享开关3、代码中检测USB网络共享是否支持 三、Settings 中USB网络共享代码的部分代码1、Settings\res\xml\t…...
【数据库知识】Mysql进阶-高可用MHA(Master High Availability)方案
mysql高可用MHA(Master High Availability)方案 集群部署模式下的高可用方案一、高可用架构原理1. 核心组件2. 故障切换流程 二、详细部署步骤 (3节点集群)1. 环境准备2. 节点配置(以 node1 为例)3. 初始化集群4. 部署MySQL Route…...
Web 架构之会话保持深度解析
文章目录 一、引言二、会话保持的基本概念2.1 什么是会话2.2 为什么需要会话保持 三、会话保持的常见实现方式3.1 基于客户端的会话保持3.1.1 Cookie 方式3.1.2 URL 重写方式 3.2 基于服务器端的会话保持3.2.1 负载均衡器会话保持3.2.2 会话共享 四、会话保持可能遇到的问题及解…...

微信小程序仿淘宝拍照/照片点位识图、点位裁剪生图、图片裁剪组件、图片点位框选、裁剪生成图片,canvasToImg
实现效果 效果: 1.微信小程序仿淘宝拍照/照片点位识图、根据点位裁剪生图、图片可裁剪、图片高度可控 2.识别点位自动生成标准构图方案,支持手动微调实现像素级精准裁剪 3.可以根据接口识别的点位信息实现拍照/相册图片特征点自动识别并裁剪 实现步骤 …...
attention_weights = torch.ones_like(prompt_embedding[:, :, 0]):切片操作获取第二维度,第三维度
attention_weights = torch.ones_like(prompt_embedding[:, :, 0]):切片操作获取第1 维度,第二维度 attention_weights = torch.ones_like(prompt_embedding[:, :, 0]) 这行代码的作用是创建一个与 prompt_embedding[:, :, 0] 形状相同且所有元素都为 1 的张量,它用于初始化…...
Rust入门之高级Trait
Rust入门之高级Trait - 本文源码 引言 前面学习了迭代器(Iterators),Iterator源码中就用到了关联类型的功能。关联类型就属于高级trait的内容,这次我们学习一下高级trait,了解关联类型等知识。关联类型看似和泛型相…...
从 Set、Map 到 WeakSet、WeakMap 的进阶之旅
在 ES5 时代,JavaScript 的数据结构主要依赖于两种类型:数组和对象。然而,随着应用规模的增长和复杂性上升,传统的数据结构越来越难以满足开发需求。比如,需要一个能自动去重的集合、一个支持任意类型键名的字典、一个…...
TTL (Time-To-Live) 解析
文章目录 TTL (Time-To-Live) 解析:网络与Java中的应用一、TTL的定义二、TTL在网络中的应用1. **路由和数据包的生命周期**2. **DNS中的TTL**3. **防止环路** 三、TTL在Java中的应用1. **缓存管理**2. **Java中的ThreadLocal**3. **网络通信中的TTL** 四、TTL的注意…...

Qt/C++开发监控GB28181系统/录像文件查询/录像回放/倍速播放/录像文件下载
一、前言 搞定了实时预览后,另一个功能就是录像回放,录像回放和视频点播功能完全一致,唯一的区别就是发送点播的sdp信息中携带了开始时间和结束时间,因为是录像文件,所以有这个时间,而实时视频预览这个对应…...

季报中的FPGA行业:U型反转,春江水暖
上周Lattice,AMD两大厂商相继发布2025 Q1季报,尽管恢复速度各异,但同时传递出FPGA行业整体回暖的复苏信号。 5月5日,Lattice交出了“勉强及格”的答卷,报告季度营收1亿2000万,与华尔街的预期基本相符。 对于这家聚焦在中小规模器件的领先厂商而言,按照其CEO的预期,长…...

嵌入式机器学习平台Edge Impulse图像分类 – 快速入门
陈拓 2025/05/08-2025/05/11 1. 简介 官方网址 https://edgeimpulse.com/ 适用于任何边缘设备的人工智能: Gateways - 网关 Sensors & Cameras - 传感器和摄像头 Docker Containers - Docker容器 MCUs, NPUs, CPUs, GPUs 构建数据集、训练模型并优化库以…...
web 自动化之 yaml 数据/日志/截图
文章目录 一、yaml 数据获取二、日志获取三、截图 一、yaml 数据获取 需要安装 PyYAML 库 import yaml import os from TestPOM.common import dir_config as Dir import jsonpathclass Data:def __init__(self,keyNone,file_name"test_datas.yaml"):file_path os…...
ARMV8 RK3399 u-boot TPL启动流程分析 --start.S
上电后运行的第一支文件:arch/arm/cpu/armv8/start.S CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK1 #include <asm/arch/boot0.h> 跳转到 arch/arm/include/asm/arch-rockchip/boot0.h CONFIG_SPL_BUILD1 b 1f ROCKCHIP_EARLYRETURN_TO_BROMno TINY_FRAMEWORKno …...

zst-2001 上午题-历年真题 计算机网络(16个内容)
网络设备 计算机网络 - 第1题 ac 计算机网络 - 第2题 d 计算机网络 - 第3题 集线器不能隔离广播域和冲突域,所以集线器就1个广播域和冲突域 交换机就是那么的炫,可以隔离冲突域,有4给冲突域,但不能隔离广播域…...

使用termius连接腾讯云服务器
使用termius连接腾讯云服务器 1.下载termius termius官网 安装配置教程 这里安装的window版本> 默认安装到C盘,不建议修改路径 可以选择谷歌登录,也可以不登录,软件是免费的,试用的是付费版本,不需要点 2.配置 这里…...
redis 命令大全整理
http://doc.redisfans.com/ 原网址 Redis 命令分类 Key(键) Key(键)命令 exists/del/keys/type/scanobject/move/dump/migratettl/pttl/persist/expireat/pexpireat/expire/pexpirerename/renamenxsort/randomkey/restoreexists 语法:exists key [key ...] 检查一个或多…...

实景三维建模软件应用场景(众趣科技实景三维建模)
实景三维建模软件应用场景概述 实景三维建模软件,作为数字化时代的重要工具,不仅能够真实、立体、时序化地反映和表达物理世界,还为国家的基础设施建设和数字化发展提供了有力的支撑。 在测绘与地理信息领域,实景三维建模软件是构…...
Mac M系列 安装 jadx-gui
安装 Homebrew在终端中执行以下命令(需管理员密码): 安装 Homebrew(官方源) /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"国内用户可用镜像源加速&…...
软考 系统架构设计师系列知识点之杂项集萃(56)
接前一篇文章:软考 系统架构设计师系列知识点之杂项集萃(55) 第91题 商业智能关注如何从业务数据中提取有用的信息,然后采用这些信息指导企业的业务开展。商业智能系统主要包括数据预处理、建立()、数据分…...
Ubuntu20.04 搭建Kubernetes 1.28版本集群
环境依赖 以下操作,无特殊说明,所有节点都需要执行 安装 ssh 服务安装 openssh-server复制代码 sudo apt-get install openssh-server修改配置文件复制代码 vim /etc/ssh/sshd_config找到配置项 复制代码 LoginGraceTime 120 PermitRootLogin prohibit-password StrictModes…...

【Linux】基础指令(Ⅱ)
目录 1. mv指令 2. cat指令 3.echo指令 补:输出重定向 4. more指令 5. less指令 6. head指令和tail指令 7.date指令 时间戳: 8. cal指令 9. alias指令 10.grep指令 1. mv指令 语法:mv [选项]... 源文件/目录 目标文件/目录 …...
RAG之大规模解析 PDF 文档全流程实战
PDF 文档在商业、学术和政府领域无处不在,蕴含着大量宝贵信息。然而,从 PDF 中提取结构化数据却面临着独特的挑战,尤其是在处理数千甚至数百万个文档时。本指南探讨了大规模解析 PDF 的策略和工具。 PDF解析挑战 PDF 的设计初衷是为了提供一致的视觉呈现,而非数据提取。这…...
vue-ganttastic甘特图label标签横向滚动固定方法
这个甘特图之前插件里,没有找到能固定label标签在屏幕上的办法,用css各种办法都没有实现,所以我我直接手写定位,用js监听滚动条滚动的距离,然后同步移动甘特图label标签,造成一种定位的错觉,以下…...
AcroForm JavaScript Promise 对象应用示例: 异步加载PDF文件
这段代码演示了在Adobe Acrobat DC Pro 的 JavaScript 环境中如何使用 Promise 对象处理异步操作。具体功能是: 定义了一个loadFile函数,模拟异步加载PDF文件的操作使用Promise对象封装异步操作,提供成功(resolve)和失败(reject)两种状态通过…...
MySQL 8.0 OCP 1Z0-908 题目解析(2)
题目005 Choose two. Which two actions can obtain information about deadlocks? □ A) Run the SHOW ENGINE INNODB MUTEX command from the mysql client. □ B) Enable the innodb_status_output_locks global parameter. □ C) Enable the innodb_print_all_deadlock…...

【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
问题场景: 提示:ipa是用于苹果设备安装的软件包资源 设备:iphone 13(未越狱) 安装包类型:ipa包 调试工具:hbuilderx 问题描述 提要:ios包无法安装 uniapp导出ios包无法安装 相信有小伙伴跟我一样&…...