uniapp小程序处理Blob二进制流数据实战指南
一、引言
在实际开发中,我们经常会遇到以下场景:
- 从后台下载文件(如图片、PDF、音视频)并在小程序中展示或保存到本地;
- 在小程序端生成二进制数据(如 Canvas 绘制后得到的图片),并上传到服务器;
- 将用户选择的本地文件(如拍摄的照片)作为二进制流发送给后端;
- 动态将二进制流转换为 Base64 字符串进行展示或传输。
在浏览器环境中,我们习惯使用 Blob 对象来封装二进制数据,利用 fetch
、XHR
的 responseType = 'blob'
,或直接通过 new Blob([...])
创建。但在各端小程序(如微信小程序、支付宝小程序、百度小程序)以及 uniapp 封装的环境下,并不完全支持标准的 Blob
API,而是通过 ArrayBuffer、Base64、FileSystemManager 等方式来实现二进制流的操作。因此,理解“在 uniapp 小程序中模拟 Blob 行为”的思路与实践技巧,是解决上述场景的关键。
本篇指南将系统地讲解:
- Blob 与 ArrayBuffer 基础:理解二进制流的概念,以及在 uniapp 小程序中如何获取和表示它。
场景实战:
- 从后端接口获取二进制流并预览/保存;
- 将 Canvas 或本地文件转为二进制并上传;
- 下载文件并保存到相册。
- 核心 API 详解:
uni.request
、uni.downloadFile
、uni.getFileSystemManager
、js-base64
等常用工具函数。 - 代码示例:每个场景都提供完整的
Vue
页面代码,便于直接复制到项目中使用。 - ASCII 图解:通过流程图帮助理解请求-流转-保存-展示的全过程。
- 注意事项与常见问题:包括兼容性、性能、内存占用等细节。
希望通过本文的示例与解析,你能迅速掌握在 uniapp 小程序中对二进制流(Blob)数据的获取、转换、保存与上传技巧,提升开发效率和代码质量。
二、Blob 与 ArrayBuffer 基础
2.1 什么是二进制流、Blob 与 ArrayBuffer
- 二进制流(Binary Stream):指以“字节”为单位的原始数据流,常用于文件、图片、音视频的下载与上传。
- Blob(Browser File Object):在浏览器中,
Blob
表示不可变、原始二进制数据,可以从网络请求、Canvas、File API 等多种来源创建。Blob 可通过URL.createObjectURL
生成临时 URL,用于<img>
、<a>
下载等。 - ArrayBuffer:一种表示通用、固定长度的二进制数据缓冲区(底层内存缓冲),可以通过
DataView
、Uint8Array
等视图对其内容进行读写。在小程序环境中,网络请求、下载接口一般返回ArrayBuffer
,开发者需要手动将其转换为文件或 Base64。
为什么在小程序中无法直接使用标准的 Blob
各类小程序(如微信/支付宝小程序)内部 并不支持 浏览器原生的 Blob
对象与 URL.createObjectURL
;它们通过自己的二进制方案(如 ArrayBuffer
、FileSystemManager
)来处理文件和数据。uniapp 又要兼容不同平台,故采取以下思路:
- 请求时指定
responseType: 'arraybuffer'
,拿到二进制数据缓冲区; - 利用
uni.getFileSystemManager().writeFile()
将ArrayBuffer
写入到本地临时文件(如.jpg
、.pdf
、.mp4
),然后使用小程序的原生预览接口(如uni.previewImage
、uni.openDocument
)进行展示; - 将
ArrayBuffer
转换为 Base64 字符串,用于<image>
中:src="'data:image/png;base64,' + base64Data"
或作为 API 上传请求体。 - 从 Canvas 导出图像时,在 H5 端可使用
canvas.toBlob()
,但在小程序端则需要先调用uni.canvasToTempFilePath()
将画布导出为临时文件,再通过getFileSystemManager().readFile({ encoding: 'base64' })
得到 Base64。
2.2 uniapp 小程序环境下的常用“Blob”替代方案
场景 | 浏览器 Blob 方案 | uniapp 小程序 环境替代 |
---|---|---|
网络请求下载二进制数据 | fetch(url).then(r => r.blob()) 或 XHR | uni.request({ url, responseType:'arraybuffer' }) |
Blob → 显示图片/下载链接 | URL.createObjectURL(blob) → <img> 或 <a> | FileSystemManager.writeFile(arraybuffer) → uni.previewImage({ urls: [filePath] }) |
Blob → FormData 上传 | formData.append('file', blob, name) | 将 ArrayBuffer 转 Base64,或写临时文件再 uni.uploadFile |
Canvas → Blob | canvas.toBlob(callback) | uni.canvasToTempFilePath() → FileSystemManager.readFile({ encoding:'base64' }) |
二进制流 → Base64 | FileReader.readAsDataURL(blob) | base64-js 或 uni.arrayBufferToBase64(arraybuffer) |
提示:uniapp 提供了uni.arrayBufferToBase64(arraybuffer)
、uni.base64ToArrayBuffer(base64)
两个内置工具函数,方便在小程序端进行 ArrayBuffer 与 Base64 的互转。
三、场景一:从后端接口获取二进制流并预览/保存
最常见的需求是 从后端下载一个文件(如头像、PDF、Excel、视频等),并在小程序端预览或直接保存到相册/本地。
3.1 思路与流程
发起请求
uni.request({ url: 'https://api.example.com/download/pdf', method: 'GET', responseType: 'arraybuffer', success: (res) => { /* res.data 即为 ArrayBuffer */ } });
写入临时文件
const fs = uni.getFileSystemManager(); const filePath = `${wx.env.USER_DATA_PATH}/temp.pdf`; fs.writeFile({ filePath, data: res.data, encoding: 'binary', success: () => { /* 写入成功 */ } });
预览或保存
预览 PDF:
uni.openDocument({ filePath, fileType: 'pdf' });
保存图片到相册(以图片为例):
uni.saveImageToPhotosAlbum({ filePath, success: () => { uni.showToast({ title: '保存成功' }); } });
ASCII 图解:下载文件并写入本地流程
┌────────────┐ 1. GET 请求 ┌───────────────┐ │ uni.request │──────────────▶│ 后端文件 API │ └────────────┘ 2. 返回 ArrayBuffer │ │ 3. res.data │ ArrayBuffer │ ┌────────────────────────────────────┴────────────────────────────┐ │ 4. fs.writeFile(filePath, arraybuffer, encoding:'binary') │ └─────────────────────────────────────────────────────────────────┘ │ 5. 临时文件 filePath(如 temp.pdf 或 temp.jpg) │ │ 6. uni.openDocument / uni.previewImage / uni.saveImageToPhotosAlbum │ ┌─────────────────────────────────────────────────────────────────┘
3.2 代码示例:下载 PDF 并预览
以下示例演示如何在 uniapp 小程序中,下载后台返回的 PDF(通过 ArrayBuffer
),写入本地后调用 uni.openDocument
预览。
<template>
<view class="container">
<button @click="downloadAndPreviewPDF">下载并预览 PDF</button>
</view>
</template>
<script>
export default {
methods: {
downloadAndPreviewPDF() {
uni.showLoading({ title: '正在下载...' });
uni.request({
url: 'https://api.example.com/files/sample.pdf', // 后端接口,返回 ArrayBuffer
method: 'GET',
responseType: 'arraybuffer',
success: (res) => {
if (res.statusCode !== 200) {
uni.hideLoading();
uni.showToast({ title: '下载失败:' + res.statusCode, icon: 'none' });
return;
}
// 1. 获取 ArrayBuffer
const arrayBuffer = res.data;
// 2. 构造本地临时文件路径
// 注意:微信小程序使用 wx.env.USER_DATA_PATH;其他小程序平台也类似
const filePath = `${wx.env.USER_DATA_PATH}/downloaded.pdf`;
// 3. 写入文件
const fs = uni.getFileSystemManager();
fs.writeFile({
filePath,
data: arrayBuffer,
encoding: 'binary',
success: () => {
uni.hideLoading();
// 4. 预览 PDF
uni.openDocument({
filePath,
fileType: 'pdf',
success: () => {
console.log('打开 PDF 成功');
},
fail: (err) => {
console.error('打开 PDF 失败:', err);
uni.showToast({ title: '打开 PDF 失败', icon: 'none' });
}
});
},
fail: (err) => {
uni.hideLoading();
console.error('写入文件失败:', err);
uni.showToast({ title: '写入文件失败', icon: 'none' });
}
});
},
fail: (err) => {
uni.hideLoading();
console.error('下载请求失败:', err);
uni.showToast({ title: '下载失败', icon: 'none' });
}
});
}
}
};
</script>
<style>
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
button {
padding: 10px 20px;
font-size: 16px;
}
</style>
3.3 代码示例:下载图片并保存到相册
若需要将后台返回的图片(二进制流)写入文件并保存到用户相册,可参考下方示例(以微信小程序为例):
<template>
<view class="container">
<button @click="downloadAndSaveImage">下载并保存图片</button>
</view>
</template>
<script>
export default {
methods: {
downloadAndSaveImage() {
uni.showLoading({ title: '下载中...' });
uni.request({
url: 'https://api.example.com/files/sample.jpg',
method: 'GET',
responseType: 'arraybuffer',
success: (res) => {
uni.hideLoading();
if (res.statusCode !== 200) {
uni.showToast({ title: '下载失败:' + res.statusCode, icon: 'none' });
return;
}
const arrayBuffer = res.data;
const filePath = `${wx.env.USER_DATA_PATH}/downloaded.jpg`;
const fs = uni.getFileSystemManager();
fs.writeFile({
filePath,
data: arrayBuffer,
encoding: 'binary',
success: () => {
// 保存到相册
uni.saveImageToPhotosAlbum({
filePath,
success: () => {
uni.showToast({ title: '保存成功', icon: 'success' });
},
fail: (err) => {
console.error('保存相册失败:', err);
uni.showToast({ title: '保存失败', icon: 'none' });
}
});
},
fail: (err) => {
console.error('写入图片失败:', err);
uni.showToast({ title: '写入失败', icon: 'none' });
}
});
},
fail: (err) => {
uni.hideLoading();
console.error('下载图片请求失败:', err);
uni.showToast({ title: '下载失败', icon: 'none' });
}
});
}
}
};
</script>
<style>
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
button {
padding: 10px 20px;
font-size: 16px;
}
</style>
四、场景二:Canvas / 本地文件 转二进制并上传
在 H5 中,我们习惯使用 canvas.toBlob()
或 new Blob([...])
将 Canvas 内容生成 Blob,但在小程序中,必须通过 uni.canvasToTempFilePath()
将画布导出为临时文件,再通过 FileSystemManager.readFile
读取为 Base64 或 ArrayBuffer,再发送给后端。下面以“Canvas 绘制后上传图片”为例,详细演示流程。
4.1 思路与流程
在页面上渲染 Canvas
<canvas id="myCanvas" canvas-id="myCanvas" style="width:300px;height:300px;"></canvas>
绘制示例图形
const ctx = uni.createCanvasContext('myCanvas', this); ctx.setFillStyle('#FF0000'); ctx.fillRect(50, 50, 200, 200); ctx.draw();
导出 Canvas 为临时文件
uni.canvasToTempFilePath({ canvasId: 'myCanvas', success: (res) => { const tempFilePath = res.tempFilePath; // 接下来可上传 tempFilePath,或将其转为 Base64 } }, this);
将临时文件读取为 Base64
const fs = uni.getFileSystemManager(); fs.readFile({ filePath: tempFilePath, encoding: 'base64', success: (fileRes) => { const base64Data = fileRes.data; // 纯 Base64,不含 data:image/png;base64, 前缀 // 1) 可直接在 <image> 中展示:src="data:image/png;base64,{{base64Data}}" // 2) 或封装为 ArrayBuffer 上传到后端 } });
上传到后端(示例以 Base64 作为请求体)
uni.request({ url: 'https://api.example.com/upload/image', method: 'POST', header: { 'Content-Type': 'application/json' }, data: { filename: 'canvas.png', data: base64Data }, success: (uploadRes) => { /* 上传成功 */ } });
ASCII 图解:Canvas ➔ 临时文件 ➔ Base64 ➔ 上传
┌─────────────────────────────┐ │ 1. 页面渲染 Canvas 图像 │ └─────────────────────────────┘ ↓ ┌─────────────────────────────┐ │ 2. uni.canvasToTempFilePath │ │ └───────────────┘ │ │ res.tempFilePath → 'wxfile://'│ └─────────────────────────────┘ ↓ ┌─────────────────────────────┐ │ 3. fs.readFile(filePath, │ │ encoding:'base64') │ │ ⇒ base64Data (纯 base64) │ └─────────────────────────────┘ ↓ ┌─────────────────────────────┐ │ 4. uni.request 上传 base64 │ │ { filename, data: base64 } │ └─────────────────────────────┘
4.2 代码示例:Canvas 绘制并上传
<template>
<view class="container">
<canvas
id="myCanvas"
canvas-id="myCanvas"
style="width:300px; height:300px; border:1px solid #000;"
></canvas>
<button @click="drawAndUpload">绘制并上传</button>
<image v-if="uploadedUrl" :src="uploadedUrl" mode="widthFix" style="width:200px; margin-top:20px;" />
</view>
</template>
<script>
export default {
data() {
return {
uploadedUrl: '' // 上传后返回的文件地址
};
},
methods: {
drawAndUpload() {
// 1. 绘制 Canvas
const ctx = uni.createCanvasContext('myCanvas', this);
ctx.setFillStyle('#3498db');
ctx.fillRect(0, 0, 300, 300);
ctx.setFontSize(24);
ctx.setFillStyle('#ffffff');
ctx.fillText('uniapp 二进制流', 40, 150);
ctx.draw(false, () => {
// 2. Canvas 绘制完成后导出临时文件
uni.canvasToTempFilePath({
canvasId: 'myCanvas',
success: (res) => {
const tempFilePath = res.tempFilePath;
// 3. 读取为 Base64
const fs = uni.getFileSystemManager();
fs.readFile({
filePath: tempFilePath,
encoding: 'base64',
success: (fileRes) => {
const base64Data = fileRes.data;
// 4. 上传到后端
uni.showLoading({ title: '上传中...' });
uni.request({
url: 'https://api.example.com/upload/image',
method: 'POST',
header: { 'Content-Type': 'application/json' },
data: {
filename: 'canvas.png',
data: base64Data
},
success: (uploadRes) => {
uni.hideLoading();
if (uploadRes.statusCode === 200 && uploadRes.data && uploadRes.data.url) {
this.uploadedUrl = uploadRes.data.url;
uni.showToast({ title: '上传成功', icon: 'success' });
} else {
uni.showToast({ title: '上传失败', icon: 'none' });
}
},
fail: (e) => {
uni.hideLoading();
console.error('上传请求失败:', e);
uni.showToast({ title: '上传请求失败', icon: 'none' });
}
});
},
fail: (err) => {
console.error('读取 Base64 失败:', err);
uni.showToast({ title: '读取 Base64 失败', icon: 'none' });
}
});
},
fail: (err) => {
console.error('导出 Canvas 临时文件失败:', err);
uni.showToast({ title: '导出临时文件失败', icon: 'none' });
}
}, this);
});
}
}
};
</script>
<style>
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 50px;
}
canvas {
margin-bottom: 20px;
}
button {
padding: 10px 20px;
font-size: 16px;
}
image {
border: 1px solid #ddd;
}
</style>
4.3 代码示例:选取本地文件并上传(ArrayBuffer 方式)
如果想让用户主动选取本地文件(如相册里的图片、视频等),并将其作为二进制流上传,可以先拿到用户选中的 tempFilePath
,再读取为 ArrayBuffer
,最后通过 uni.request
发送二进制流。示例代码如下(以图片为例):
<template>
<view class="container">
<button @click="chooseAndUploadFile">选择并上传文件</button>
<image v-if="uploadedUrl" :src="uploadedUrl" mode="widthFix" style="width:200px; margin-top:20px;" />
</view>
</template>
<script>
export default {
data() {
return {
uploadedUrl: ''
};
},
methods: {
chooseAndUploadFile() {
uni.chooseImage({
count: 1,
success: (chooseRes) => {
const tempFilePath = chooseRes.tempFilePaths[0];
// 读取文件为 ArrayBuffer
const fs = uni.getFileSystemManager();
fs.readFile({
filePath: tempFilePath,
// 不传 encoding,返回 ArrayBuffer;传 encoding:'base64' 则返回 Base64
success: (fileRes) => {
const arrayBuffer = fileRes.data;
// 直接上传 ArrayBuffer
uni.showLoading({ title: '上传中...' });
uni.request({
url: 'https://api.example.com/upload/binary',
method: 'POST',
header: {
'Content-Type': 'application/octet-stream',
'X-Filename': 'user_avatar.png'
},
// 需将 ArrayBuffer 放到 data
data: arrayBuffer,
// 在小程序端需要加上下面两行,确保 data 能被正确识别为二进制流
// 但在 uniapp 里默认会自动处理 ArrayBuffer
success: (uploadRes) => {
uni.hideLoading();
if (uploadRes.statusCode === 200 && uploadRes.data && uploadRes.data.url) {
this.uploadedUrl = uploadRes.data.url;
uni.showToast({ title: '上传成功', icon: 'success' });
} else {
uni.showToast({ title: '上传失败', icon: 'none' });
}
},
fail: (err) => {
uni.hideLoading();
console.error('上传失败:', err);
uni.showToast({ title: '上传失败', icon: 'none' });
}
});
},
fail: (err) => {
console.error('读取文件失败:', err);
uni.showToast({ title: '读取文件失败', icon: 'none' });
}
});
},
fail: (err) => {
console.error('选择文件失败:', err);
}
});
}
}
};
</script>
<style>
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 50px;
}
button {
padding: 10px 20px;
font-size: 16px;
}
image {
border: 1px solid #ddd;
}
</style>
说明:
fs.readFile({ filePath, success(res) })
默认返回ArrayBuffer
,如果传encoding: 'base64'
则返回 Base64 字符串;uni.request
支持直接把ArrayBuffer
放到data
中,且会设置相应的请求头为二进制流。
五、场景三:下载文件保存到相册/本地
除了从接口获取二进制流,uniapp 还提供了更高层次的 uni.downloadFile
API,方便地将远程文件下载到本地缓存,并返回临时路径。配合 uni.saveImageToPhotosAlbum
、uni.saveVideoToPhotosAlbum
等接口,可快速实现“下载→本地缓存→保存到相册”的功能。
5.1 uni.downloadFile
简介
用法
uni.downloadFile({ url: 'https://example.com/path/to/file.mp4', // 远程文件地址 header: { /* 可选的 header */ }, success: (res) => { if (res.statusCode === 200) { const tempFilePath = res.tempFilePath; // 可直接 preview、保存、播放 } }, fail: (err) => { /* 下载失败 */ } });
- 返回的
res.tempFilePath
:是一个本地临时文件路径,例如wxfile://tmp_a1b2c3.jpg
。在 H5 端会被自动转换为可访问的 URL,在小程序端可以直接用于预览或保存。
5.2 代码示例:下载并保存视频到相册
<template>
<view class="container">
<button @click="downloadAndSaveVideo">下载并保存视频</button>
</view>
</template>
<script>
export default {
methods: {
downloadAndSaveVideo() {
uni.showLoading({ title: '下载中...' });
uni.downloadFile({
url: 'https://example.com/videos/sample.mp4',
success: (res) => {
uni.hideLoading();
if (res.statusCode === 200) {
const tempFilePath = res.tempFilePath;
// 保存到相册
uni.saveVideoToPhotosAlbum({
filePath: tempFilePath,
success: () => {
uni.showToast({ title: '保存成功', icon: 'success' });
},
fail: (err) => {
console.error('保存到相册失败:', err);
uni.showToast({ title: '保存失败', icon: 'none' });
}
});
} else {
uni.showToast({ title: '下载失败:' + res.statusCode, icon: 'none' });
}
},
fail: (err) => {
uni.hideLoading();
console.error('下载请求失败:', err);
uni.showToast({ title: '下载失败', icon: 'none' });
}
});
}
}
};
</script>
<style>
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
button {
padding: 10px 20px;
font-size: 16px;
}
</style>
提示:
- 对于大文件(如视频、APK),建议在下载前提醒用户网络流量,并且在下载过程中显示进度:
const downloadTask = uni.downloadFile({ url, success, fail });
downloadTask.onProgressUpdate((res) => {
console.log(`下载进度:${res.progress}%`);
// 可通过 uni.showLoading({ title: `下载 ${res.progress}%` }) 更新进度
});
- 若要把文件保存到小程序的“用户本地相册”(iOS/Android 相册),需要在
manifest.json
或小程序代码里申请相应权限(如相机/存储权限)。
六、核心 API 与工具函数详解
下面对本文所用到的主要 API 和辅助工具函数进行一一说明,便于在项目中查阅与复用。
6.1 uni.request
(下载二进制流)
uni.request({
url: '', // 请求地址
method: 'GET', // 或 'POST'
responseType: 'arraybuffer', // 指定返回类型为 ArrayBuffer
header: { // 可选自定义请求头
'Authorization': 'Bearer xxxxx'
},
data: {}, // 仅在 POST 时携带请求体
success: (res) => {
// res.data: ArrayBuffer
// res.statusCode: HTTP 状态码
},
fail: (err) => { /* 请求失败 */ }
});
- 注意:仅当
responseType: 'arraybuffer'
时,res.data
才是ArrayBuffer
。默认是字符串。 在 H5 端,可通过以下方式模拟 Blob:
uni.request({ url, responseType: 'arraybuffer', success: (res) => { const arrayBuffer = res.data; // 将 ArrayBuffer 转成 Blob const blob = new Blob([arrayBuffer], { type: 'application/pdf' }); const url = URL.createObjectURL(blob); // 在 H5 页面中就可以 <iframe :src="url" /> 预览了 } });
6.2 uni.downloadFile
(下载并拿到临时文件路径)
const downloadTask = uni.downloadFile({
url: '', // 文件下载地址
filePath: '', // (可选)指定下载后保存的路径;若不指定,则使用 tempFilePath
success: (res) => {
if (res.statusCode === 200) {
const tempFilePath = res.tempFilePath;
// tempFilePath 可直接用于 <image>、<video>、uni.openDocument 等
}
},
fail: (err) => { /* 下载失败 */ }
});
// 监听下载进度
downloadTask.onProgressUpdate((res) => {
console.log(`下载进度:${res.progress}%`);
});
res.tempFilePath
:下载成功后文件的本地临时路径,可直接用于<image :src="tempFilePath" />
或存储/预览。- 对于 H5 端,
uni.downloadFile
实际上会触发浏览器的下载,不一定能拿到tempFilePath
;可使用原生fetch
或XMLHttpRequest(responseType:'blob')
。
6.3 uni.getFileSystemManager
(文件读写管理器)
const fs = uni.getFileSystemManager();
// 写文件:ArrayBuffer 写入本地临时路径
fs.writeFile({
filePath: localPath, // 如 `${wx.env.USER_DATA_PATH}/xxx.pdf`
data: arrayBuffer,
encoding: 'binary', // 读写 ArrayBuffer 要用 'binary'
success: () => { /* 写入成功 */ },
fail: (err) => { /* 写入失败 */ }
});
// 读文件:可读为 Base64 或 ArrayBuffer
fs.readFile({
filePath: localPath,
encoding: 'base64', // 或不传 encoding, 默认返回 ArrayBuffer
success: (res) => {
const base64Data = res.data; // 若 encoding:'base64'
// const arrayBuffer = res.data; // 若不传 encoding
},
fail: (err) => { /* 读取失败 */ }
});
- 写入时:
data
可以是ArrayBuffer
、string
(若encoding
为utf8
或ascii
);若要写入二进制流,必须使用encoding: 'binary'
,否则数据会被当成字符串乱码写入。 读取时:
- 若指定
encoding: 'base64'
,则res.data
为 Base64 字符串; - 若不指定
encoding
,则res.data
为ArrayBuffer
。
- 若指定
6.4 Base64 ↔ ArrayBuffer 互转
在 uniapp 小程序中,可使用内置的 ArrayBuffer/Base64 转换函数,也可以借助第三方库如 js-base64
、base64-js
。
6.4.1 uniapp 内置函数
// ArrayBuffer → Base64
const base64Data = uni.arrayBufferToBase64(arrayBuffer);
// Base64 → ArrayBuffer
const arrayBuffer = uni.base64ToArrayBuffer(base64Data);
示例:
const aBuf = new Uint8Array([0x41,0x42,0x43]).buffer; // ArrayBuffer “ABC” const b64 = uni.arrayBufferToBase64(aBuf); // b64 = "QUJD" const aBuf2 = uni.base64ToArrayBuffer(b64); // aBuf2 等同于 aBuf
6.4.2 使用 js-base64
(H5 和小程序均可用)
npm install js-base64
import { Base64 } from 'js-base64';
// ArrayBuffer → Base64
function arrayBufferToBase64(arrayBuffer) {
let binary = '';
const bytes = new Uint8Array(arrayBuffer);
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return Base64.encode(binary);
}
// Base64 → ArrayBuffer
function base64ToArrayBuffer(base64) {
const binary = Base64.atob(base64);
const len = binary.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
}
提示:在小程序端,若引入js-base64
,会增加包体体积,推荐优先使用内置uni.arrayBufferToBase64
。
七、综合示例:多功能二进制流处理页面
下面将上述几个场景整合到一个 uniapp 页面 中,演示从下载、预览、保存到上传,以及 Canvas 生成并上传的全流程。你可以复制以下代码到自己的项目中,直接使用或做二次改造。
<template>
<view class="page-container">
<text class="header">uniapp 小程序二进制流实战示例</text>
<!-- 场景一:下载并预览 PDF -->
<button @click="downloadAndPreviewPDF">下载并预览 PDF</button>
<!-- 场景一:下载并保存图片 -->
<button @click="downloadAndSaveImage">下载并保存图片</button>
<!-- 场景二:Canvas 绘制并上传 -->
<canvas id="myCanvas" canvas-id="myCanvas" style="width:200px;height:200px;border:1px solid #000;margin-top:20px;"></canvas>
<button @click="drawAndUploadCanvas">绘制并上传 Canvas</button>
<image v-if="canvasUploadedUrl" :src="canvasUploadedUrl" mode="widthFix" style="width:100px;margin-top:10px;" />
<!-- 场景二:选本地图片并上传 -->
<button @click="chooseAndUploadImage">选择本地图片并上传</button>
<image v-if="fileUploadedUrl" :src="fileUploadedUrl" mode="widthFix" style="width:100px;margin-top:10px;" />
<!-- 场景三:下载并保存视频 -->
<button @click="downloadAndSaveVideo" style="margin-top:20px;">下载并保存视频</button>
</view>
</template>
<script>
export default {
data() {
return {
canvasUploadedUrl: '',
fileUploadedUrl: ''
};
},
methods: {
// 场景一:下载并预览 PDF
downloadAndPreviewPDF() {
uni.showLoading({ title: '下载 PDF...' });
uni.request({
url: 'https://api.example.com/files/sample.pdf',
method: 'GET',
responseType: 'arraybuffer',
success: (res) => {
if (res.statusCode !== 200) {
uni.hideLoading();
uni.showToast({ title: '下载失败', icon: 'none' });
return;
}
const arrayBuffer = res.data;
const filePath = `${wx.env.USER_DATA_PATH}/sample.pdf`;
const fs = uni.getFileSystemManager();
fs.writeFile({
filePath,
data: arrayBuffer,
encoding: 'binary',
success: () => {
uni.hideLoading();
uni.openDocument({
filePath,
fileType: 'pdf'
});
},
fail: (err) => {
uni.hideLoading();
console.error('写入 PDF 失败:', err);
uni.showToast({ title: '写入失败', icon: 'none' });
}
});
},
fail: (err) => {
uni.hideLoading();
console.error('PDF 下载失败:', err);
uni.showToast({ title: '下载失败', icon: 'none' });
}
});
},
// 场景一:下载并保存图片到相册
downloadAndSaveImage() {
uni.showLoading({ title: '下载图片...' });
uni.request({
url: 'https://api.example.com/files/sample.jpg',
method: 'GET',
responseType: 'arraybuffer',
success: (res) => {
uni.hideLoading();
if (res.statusCode !== 200) {
uni.showToast({ title: '下载失败', icon: 'none' });
return;
}
const arrayBuffer = res.data;
const filePath = `${wx.env.USER_DATA_PATH}/sample.jpg`;
const fs = uni.getFileSystemManager();
fs.writeFile({
filePath,
data: arrayBuffer,
encoding: 'binary',
success: () => {
uni.saveImageToPhotosAlbum({
filePath,
success: () => {
uni.showToast({ title: '保存成功', icon: 'success' });
},
fail: (err) => {
console.error('保存相册失败:', err);
uni.showToast({ title: '保存失败', icon: 'none' });
}
});
},
fail: (err) => {
console.error('写入图片失败:', err);
uni.showToast({ title: '写入失败', icon: 'none' });
}
});
},
fail: (err) => {
uni.hideLoading();
console.error('图片下载失败:', err);
uni.showToast({ title: '下载失败', icon: 'none' });
}
});
},
// 场景二:Canvas 绘制并上传
drawAndUploadCanvas() {
const ctx = uni.createCanvasContext('myCanvas', this);
ctx.setFillStyle('#2ecc71');
ctx.fillRect(0, 0, 200, 200);
ctx.setFontSize(18);
ctx.setFillStyle('#ffffff');
ctx.fillText('uniapp Blob 测试', 20, 100);
ctx.draw(false, () => {
uni.canvasToTempFilePath({
canvasId: 'myCanvas',
success: (res) => {
const tempFilePath = res.tempFilePath;
const fs = uni.getFileSystemManager();
fs.readFile({
filePath: tempFilePath,
encoding: 'base64',
success: (fileRes) => {
const base64Data = fileRes.data;
uni.showLoading({ title: '上传中...' });
uni.request({
url: 'https://api.example.com/upload/image',
method: 'POST',
header: { 'Content-Type': 'application/json' },
data: {
filename: 'canvas.png',
data: base64Data
},
success: (uploadRes) => {
uni.hideLoading();
if (uploadRes.statusCode === 200 && uploadRes.data.url) {
this.canvasUploadedUrl = uploadRes.data.url;
uni.showToast({ title: '上传成功', icon: 'success' });
} else {
uni.showToast({ title: '上传失败', icon: 'none' });
}
},
fail: (err) => {
uni.hideLoading();
console.error('上传失败:', err);
uni.showToast({ title: '上传失败', icon: 'none' });
}
});
},
fail: (err) => {
console.error('读取 Canvas Base64 失败:', err);
uni.showToast({ title: '读取失败', icon: 'none' });
}
});
},
fail: (err) => {
console.error('导出 Canvas 临时文件失败:', err);
uni.showToast({ title: '导出失败', icon: 'none' });
}
}, this);
});
},
// 场景二:选本地图片并上传(二进制流)
chooseAndUploadImage() {
uni.chooseImage({
count: 1,
success: (chooseRes) => {
const tempFilePath = chooseRes.tempFilePaths[0];
const fs = uni.getFileSystemManager();
fs.readFile({
filePath: tempFilePath,
success: (fileRes) => {
const arrayBuffer = fileRes.data; // ArrayBuffer
uni.showLoading({ title: '上传中...' });
uni.request({
url: 'https://api.example.com/upload/binary',
method: 'POST',
header: {
'Content-Type': 'application/octet-stream',
'X-Filename': 'upload_image.png'
},
data: arrayBuffer,
success: (uploadRes) => {
uni.hideLoading();
if (uploadRes.statusCode === 200 && uploadRes.data.url) {
this.fileUploadedUrl = uploadRes.data.url;
uni.showToast({ title: '上传成功', icon: 'success' });
} else {
uni.showToast({ title: '上传失败', icon: 'none' });
}
},
fail: (err) => {
uni.hideLoading();
console.error('上传失败:', err);
uni.showToast({ title: '上传失败', icon: 'none' });
}
});
},
fail: (err) => {
console.error('读取文件失败:', err);
uni.showToast({ title: '读取失败', icon: 'none' });
}
});
},
fail: (err) => {
console.error('选择图片失败:', err);
}
});
},
// 场景三:下载并保存视频到相册
downloadAndSaveVideo() {
uni.showLoading({ title: '下载视频...' });
const downloadTask = uni.downloadFile({
url: 'https://api.example.com/videos/sample.mp4',
success: (res) => {
uni.hideLoading();
if (res.statusCode === 200) {
const tempFilePath = res.tempFilePath;
uni.saveVideoToPhotosAlbum({
filePath: tempFilePath,
success: () => {
uni.showToast({ title: '保存成功', icon: 'success' });
},
fail: (err) => {
console.error('保存视频失败:', err);
uni.showToast({ title: '保存失败', icon: 'none' });
}
});
} else {
uni.showToast({ title: '下载失败:' + res.statusCode, icon: 'none' });
}
},
fail: (err) => {
uni.hideLoading();
console.error('下载失败:', err);
uni.showToast({ title: '下载失败', icon: 'none' });
}
});
// 监听进度
downloadTask.onProgressUpdate((progressRes) => {
console.log(`下载进度:${progressRes.progress}%`);
});
}
}
};
</script>
<style>
.page-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 50px;
}
.header {
font-size: 20px;
margin-bottom: 20px;
}
button {
margin-top: 15px;
padding: 10px 20px;
font-size: 16px;
}
canvas {
margin-top: 20px;
}
image {
margin-top: 10px;
border: 1px solid #ddd;
}
</style>
八、注意事项与常见问题
内存占用与大文件处理
- 二进制流(尤其是视频、PDF)文件较大时,将整个
ArrayBuffer
加载到内存可能导致内存溢出。 - 在下载大文件时,建议采用
uni.downloadFile
而非uni.request
,因为后者会先将整个数据加载到内存后再返回;uni.downloadFile
底层会边下载边写入临时文件,内存占用更低。 - 写入本地后,再通过
openDocument
、saveVideoToPhotosAlbum
等原生接口读取文件,避免将大文件一次性加载到内存中处理。
- 二进制流(尤其是视频、PDF)文件较大时,将整个
不同平台的临时路径差异
- 微信小程序:使用
wx.env.USER_DATA_PATH
作为读写的沙盒根目录。 - 支付宝/百度小程序:可以使用相对路径(如
'' + Date.now() + '.jpg'
),或者先调用uni.getStorageInfoSync()
获取缓存根路径。 - uniapp H5 端:
uni.getFileSystemManager()
不可用,需要使用浏览器的Blob
、URL.createObjectURL
等方式处理文件。
- 微信小程序:使用
文件权限与用户授权
- 在调用
uni.saveImageToPhotosAlbum
、uni.saveVideoToPhotosAlbum
时,需先申请用户授权 相册/存储权限;如果用户拒绝,fail
回调会被触发,需提示用户手动开启权限。 示例:
uni.authorize({ scope: 'scope.writePhotosAlbum', success: () => { /* 有权限,直接保存 */ }, fail: () => { uni.showModal({ title: '提示', content: '需要您授权保存到相册,否则无法保存。', success: (res) => { if (res.confirm) { uni.openSetting(); } } }); } });
- 在调用
Base64 字符串过大影响性能
- 当将大文件转为 Base64,再作为字符串放到内存中时,可能导致性能问题或
setData
卡顿。尽量避免对巨型文件(如大于 5MB 的视频/PDF)使用 Base64,优先使用uni.downloadFile
/uni.uploadFile
。 - 如果必须使用 Base64,可考虑分片上传或后端提供分段上传接口。
- 当将大文件转为 Base64,再作为字符串放到内存中时,可能导致性能问题或
不同小程序平台的兼容性
- 微信小程序 支持
wx.getFileSystemManager
、wx.openDocument
、wx.saveImageToPhotosAlbum
等 API; - 支付宝小程序 类似支持
my.getFileSystemManager()
、my.openDocument
(部分版本需自行封装)和my.saveImage
等; - 百度小程序 支持
swan.getFileSystemManager()
、swan.openDocument
、swan.saveImageToPhotosAlbum
; - 需要在代码中做平台检测,或使用 uniapp 统一封装的 API。
- 微信小程序 支持
Canvas 大小与导出分辨率
uni.canvasToTempFilePath
支持传width
、height
、destWidth
、destHeight
等参数,控制导出图片的分辨率与质量。例如destWidth: 600, destHeight: 600
可导出更高清的 PNG。
九、结语
通过本篇《uniapp 小程序处理 Blob 二进制流数据实战指南》,你已经掌握了:
- 二进制流的概念:了解浏览器端的 Blob 与 ArrayBuffer,在小程序中如何绕过 Blob 限制,使用 ArrayBuffer 结合文件读写进行二进制处理。
- 核心 API:
uni.request(responseType:'arraybuffer')
、uni.downloadFile
、uni.getFileSystemManager().writeFile/readFile
、uni.openDocument
、uni.saveImageToPhotosAlbum
、uni.canvasToTempFilePath
、uni.request
上传二进制流等关键函数用法。 典型场景实战:
- 下载 PDF/图片/视频 → 写入本地 → 预览/保存相册;
- Canvas 绘制 → 导出临时文件 → 读取 Base64 → 上传;
- 选本地文件 → 读取 ArrayBuffer → 上传二进制流;
- UE:下载文件带进度 → 写入后保存。
- 性能与兼容:掌握在大文件、高并发场景下避免一次性加载过大数据,以及各小程序平台的兼容性处理方法。
- 完整示例:提供了一个多功能页面,可直接复制到你的 uniapp 项目中快速使用或二次改造。
评论已关闭