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

【Android】文件分块上传尝试

【Android】文件分块上传

在完成一个项目时,遇到了需要上传长视频的场景,尽管可以手动限制视频清晰度和视频的码率帧率,但仍然避免不了视频大小过大的问题,且由于服务器原因,网络不太稳定。这个时候想到了可以将文件分块。

为什么选择文件分片上传

  1. 提高上传成功率:在网络不稳定或上传大文件时,一般上传可能因网络中断而导致整个上传过程失败,需要重新开始。而分片上传是将文件分成多个小块分别上传,即使某个分片上传失败,只需重新上传该分片,而不是整个文件,大大提高了上传的成功率。
  2. 实现断点续传:分片上传可以记录每个分片的上传进度,当上传因某种原因中断后,再次启动上传时,可以从上次中断的位置继续上传未完成的分片,无需从头开始,节省了时间和带宽。
  3. 并发上传:可以将多个分片同时上传到服务器,利用多线程或并发请求技术,充分利用网络带宽,加快上传速度。特别是对于大文件,并发上传多个分片能够显著缩短上传时间。

文件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,检查每个分块是否上传成功。
  • 回调通知:根据结果调用 onSuccessonFailed
  • 资源释放:关闭线程池。

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】文件分块上传 在完成一个项目时&#xff0c;遇到了需要上传长视频的场景&#xff0c;尽管可以手动限制视频清晰度和视频的码率帧率&#xff0c;但仍然避免不了视频大小过大的问题&#xff0c;且由于服务器原因&#xff0c;网络不太稳定。这个时候想到了可以将文件分…...

超详细Docker教程

前言&#xff1a;大家在在Linux上部署mysql及其他软件时&#xff0c;大家想一想自己最大的感受是什么&#xff1f; 我相信&#xff0c;除了个别天赋异禀的人以外&#xff0c;大多数人都会有相同的感受&#xff0c;那就是麻烦。核心体现在三点&#xff1a; 命令太多了&#xff…...

Java项目拷打(外卖+点评)

一、点评星球&#xff08;黑马点评&#xff09; 1、项目概述 1.1、项目简介 本项目是基于Spring Boot与Redis深度整合的前后端分离的点评平台。系统以Redis为核心技术支撑&#xff0c;重点解决高并发场景下的缓存穿透、击穿、雪崩等问题&#xff0c;涵盖商户展示、优惠券秒杀…...

hadoop中了解yarm

Hadoop中的YARN&#xff08;Yet Another Resource Negotiator&#xff09;是一种新的Hadoop资源管理器&#xff0c;是一个通用资源管理系统&#xff0c;可为上层应用提供统一的资源管理和调度。以下是其相关介绍&#xff1a; 核心思想 将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&#xff08;Master High Availability&#xff09;方案 集群部署模式下的高可用方案一、高可用架构原理1. 核心组件2. 故障切换流程 二、详细部署步骤 (3节点集群)1. 环境准备2. 节点配置&#xff08;以 node1 为例&#xff09;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

实现效果 效果&#xff1a; 1.微信小程序仿淘宝拍照/照片点位识图、根据点位裁剪生图、图片可裁剪、图片高度可控 2.识别点位自动生成标准构图方案&#xff0c;支持手动微调实现像素级精准裁剪 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 - 本文源码 引言 前面学习了迭代器&#xff08;Iterators&#xff09;&#xff0c;Iterator源码中就用到了关联类型的功能。关联类型就属于高级trait的内容&#xff0c;这次我们学习一下高级trait&#xff0c;了解关联类型等知识。关联类型看似和泛型相…...

从 Set、Map 到 WeakSet、WeakMap 的进阶之旅

在 ES5 时代&#xff0c;JavaScript 的数据结构主要依赖于两种类型&#xff1a;数组和对象。然而&#xff0c;随着应用规模的增长和复杂性上升&#xff0c;传统的数据结构越来越难以满足开发需求。比如&#xff0c;需要一个能自动去重的集合、一个支持任意类型键名的字典、一个…...

TTL (Time-To-Live) 解析

文章目录 TTL (Time-To-Live) 解析&#xff1a;网络与Java中的应用一、TTL的定义二、TTL在网络中的应用1. **路由和数据包的生命周期**2. **DNS中的TTL**3. **防止环路** 三、TTL在Java中的应用1. **缓存管理**2. **Java中的ThreadLocal**3. **网络通信中的TTL** 四、TTL的注意…...

Qt/C++开发监控GB28181系统/录像文件查询/录像回放/倍速播放/录像文件下载

一、前言 搞定了实时预览后&#xff0c;另一个功能就是录像回放&#xff0c;录像回放和视频点播功能完全一致&#xff0c;唯一的区别就是发送点播的sdp信息中携带了开始时间和结束时间&#xff0c;因为是录像文件&#xff0c;所以有这个时间&#xff0c;而实时视频预览这个对应…...

季报中的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/ 适用于任何边缘设备的人工智能&#xff1a; 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

上电后运行的第一支文件&#xff1a;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题 集线器不能隔离广播域和冲突域&#xff0c;所以集线器就1个广播域和冲突域 交换机就是那么的炫&#xff0c;可以隔离冲突域&#xff0c;有4给冲突域&#xff0c;但不能隔离广播域&#xf…...

使用termius连接腾讯云服务器

使用termius连接腾讯云服务器 1.下载termius termius官网 安装配置教程 这里安装的window版本> 默认安装到C盘&#xff0c;不建议修改路径 可以选择谷歌登录&#xff0c;也可以不登录&#xff0c;软件是免费的&#xff0c;试用的是付费版本&#xff0c;不需要点 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 ...] 检查一个或多…...

实景三维建模软件应用场景(众趣科技实景三维建模)

实景三维建模软件应用场景概述 实景三维建模软件&#xff0c;作为数字化时代的重要工具&#xff0c;不仅能够真实、立体、时序化地反映和表达物理世界&#xff0c;还为国家的基础设施建设和数字化发展提供了有力的支撑。 在测绘与地理信息领域&#xff0c;实景三维建模软件是构…...

Mac M系列 安装 jadx-gui

安装 Homebrew在终端中执行以下命令&#xff08;需管理员密码&#xff09;&#xff1a; 安装 Homebrew&#xff08;官方源&#xff09; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"国内用户可用镜像源加速&…...

软考 系统架构设计师系列知识点之杂项集萃(56)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之杂项集萃&#xff08;55&#xff09; 第91题 商业智能关注如何从业务数据中提取有用的信息&#xff0c;然后采用这些信息指导企业的业务开展。商业智能系统主要包括数据预处理、建立&#xff08;&#xff09;、数据分…...

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指令 补&#xff1a;输出重定向 4. more指令 5. less指令 6. head指令和tail指令 7.date指令 时间戳&#xff1a; 8. cal指令 9. alias指令 10.grep指令 1. mv指令 语法&#xff1a;mv [选项]... 源文件/目录 目标文件/目录 …...

RAG之大规模解析 PDF 文档全流程实战

PDF 文档在商业、学术和政府领域无处不在,蕴含着大量宝贵信息。然而,从 PDF 中提取结构化数据却面临着独特的挑战,尤其是在处理数千甚至数百万个文档时。本指南探讨了大规模解析 PDF 的策略和工具。 PDF解析挑战 PDF 的设计初衷是为了提供一致的视觉呈现,而非数据提取。这…...

vue-ganttastic甘特图label标签横向滚动固定方法

这个甘特图之前插件里&#xff0c;没有找到能固定label标签在屏幕上的办法&#xff0c;用css各种办法都没有实现&#xff0c;所以我我直接手写定位&#xff0c;用js监听滚动条滚动的距离&#xff0c;然后同步移动甘特图label标签&#xff0c;造成一种定位的错觉&#xff0c;以下…...

AcroForm JavaScript Promise 对象应用示例: 异步加载PDF文件

这段代码演示了在Adobe Acrobat DC Pro 的 JavaScript 环境中如何使用 Promise 对象处理异步操作。具体功能是&#xff1a; 定义了一个loadFile函数&#xff0c;模拟异步加载PDF文件的操作使用Promise对象封装异步操作&#xff0c;提供成功(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文件

问题场景&#xff1a; 提示&#xff1a;ipa是用于苹果设备安装的软件包资源 设备&#xff1a;iphone 13(未越狱) 安装包类型&#xff1a;ipa包 调试工具&#xff1a;hbuilderx 问题描述 提要&#xff1a;ios包无法安装 uniapp导出ios包无法安装 相信有小伙伴跟我一样&…...