uniapp小程序处理Blob二进制流数据实战指南‌

一、引言

在实际开发中,我们经常会遇到以下场景:

  1. 从后台下载文件(如图片、PDF、音视频)并在小程序中展示或保存到本地
  2. 在小程序端生成二进制数据(如 Canvas 绘制后得到的图片),并上传到服务器
  3. 将用户选择的本地文件(如拍摄的照片)作为二进制流发送给后端
  4. 动态将二进制流转换为 Base64 字符串进行展示或传输

在浏览器环境中,我们习惯使用 Blob 对象来封装二进制数据,利用 fetchXHRresponseType = 'blob',或直接通过 new Blob([...]) 创建。但在各端小程序(如微信小程序、支付宝小程序、百度小程序)以及 uniapp 封装的环境下,并不完全支持标准的 Blob API,而是通过 ArrayBufferBase64FileSystemManager 等方式来实现二进制流的操作。因此,理解“在 uniapp 小程序中模拟 Blob 行为”的思路与实践技巧,是解决上述场景的关键。

本篇指南将系统地讲解:

  1. Blob 与 ArrayBuffer 基础:理解二进制流的概念,以及在 uniapp 小程序中如何获取和表示它。
  2. 场景实战

    • 从后端接口获取二进制流并预览/保存;
    • 将 Canvas 或本地文件转为二进制并上传;
    • 下载文件并保存到相册。
  3. 核心 API 详解uni.requestuni.downloadFileuni.getFileSystemManagerjs-base64 等常用工具函数。
  4. 代码示例:每个场景都提供完整的 Vue 页面代码,便于直接复制到项目中使用。
  5. ASCII 图解:通过流程图帮助理解请求-流转-保存-展示的全过程。
  6. 注意事项与常见问题:包括兼容性、性能、内存占用等细节。

希望通过本文的示例与解析,你能迅速掌握在 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:一种表示通用、固定长度的二进制数据缓冲区(底层内存缓冲),可以通过 DataViewUint8Array 等视图对其内容进行读写。在小程序环境中,网络请求、下载接口一般返回 ArrayBuffer,开发者需要手动将其转换为文件或 Base64。

为什么在小程序中无法直接使用标准的 Blob

各类小程序(如微信/支付宝小程序)内部 并不支持 浏览器原生的 Blob 对象与 URL.createObjectURL;它们通过自己的二进制方案(如 ArrayBufferFileSystemManager)来处理文件和数据。uniapp 又要兼容不同平台,故采取以下思路:

  1. 请求时指定 responseType: 'arraybuffer',拿到二进制数据缓冲区;
  2. 利用 uni.getFileSystemManager().writeFile()ArrayBuffer 写入到本地临时文件(如 .jpg.pdf.mp4),然后使用小程序的原生预览接口(如 uni.previewImageuni.openDocument)进行展示;
  3. ArrayBuffer 转换为 Base64 字符串,用于 <image>:src="'data:image/png;base64,' + base64Data" 或作为 API 上传请求体。
  4. 从 Canvas 导出图像时,在 H5 端可使用 canvas.toBlob(),但在小程序端则需要先调用 uni.canvasToTempFilePath() 将画布导出为临时文件,再通过 getFileSystemManager().readFile({ encoding: 'base64' }) 得到 Base64。

2.2 uniapp 小程序环境下的常用“Blob”替代方案

场景浏览器 Blob 方案uniapp 小程序 环境替代
网络请求下载二进制数据fetch(url).then(r => r.blob()) 或 XHRuni.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 → Blobcanvas.toBlob(callback)uni.canvasToTempFilePath()FileSystemManager.readFile({ encoding:'base64' })
二进制流 → Base64FileReader.readAsDataURL(blob)base64-jsuni.arrayBufferToBase64(arraybuffer)
提示:uniapp 提供了 uni.arrayBufferToBase64(arraybuffer)uni.base64ToArrayBuffer(base64) 两个内置工具函数,方便在小程序端进行 ArrayBuffer 与 Base64 的互转。

三、场景一:从后端接口获取二进制流并预览/保存

最常见的需求是 从后端下载一个文件(如头像、PDF、Excel、视频等),并在小程序端预览或直接保存到相册/本地。

3.1 思路与流程

  1. 发起请求

    uni.request({
      url: 'https://api.example.com/download/pdf',
      method: 'GET',
      responseType: 'arraybuffer',
      success: (res) => { /* res.data 即为 ArrayBuffer */ }
    });
  2. 写入临时文件

    const fs = uni.getFileSystemManager();
    const filePath = `${wx.env.USER_DATA_PATH}/temp.pdf`;
    fs.writeFile({
      filePath,
      data: res.data,
      encoding: 'binary',
      success: () => { /* 写入成功 */ }
    });
  3. 预览或保存

    • 预览 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 思路与流程

  1. 在页面上渲染 Canvas

    <canvas id="myCanvas" canvas-id="myCanvas" style="width:300px;height:300px;"></canvas>
  2. 绘制示例图形

    const ctx = uni.createCanvasContext('myCanvas', this);
    ctx.setFillStyle('#FF0000');
    ctx.fillRect(50, 50, 200, 200);
    ctx.draw();
  3. 导出 Canvas 为临时文件

    uni.canvasToTempFilePath({
      canvasId: 'myCanvas',
      success: (res) => {
        const tempFilePath = res.tempFilePath;
        // 接下来可上传 tempFilePath,或将其转为 Base64
      }
    }, this);
  4. 将临时文件读取为 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 上传到后端
      }
    });
  5. 上传到后端(示例以 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.saveImageToPhotosAlbumuni.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;可使用原生 fetchXMLHttpRequest(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 可以是 ArrayBufferstring(若 encodingutf8ascii);若要写入二进制流,必须使用 encoding: 'binary',否则数据会被当成字符串乱码写入。
  • 读取时

    • 若指定 encoding: 'base64',则 res.data 为 Base64 字符串;
    • 若不指定 encoding,则 res.dataArrayBuffer

6.4 Base64 ↔ ArrayBuffer 互转

在 uniapp 小程序中,可使用内置的 ArrayBuffer/Base64 转换函数,也可以借助第三方库如 js-base64base64-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>

八、注意事项与常见问题

  1. 内存占用与大文件处理

    • 二进制流(尤其是视频、PDF)文件较大时,将整个 ArrayBuffer 加载到内存可能导致内存溢出。
    • 在下载大文件时,建议采用 uni.downloadFile 而非 uni.request,因为后者会先将整个数据加载到内存后再返回;uni.downloadFile 底层会边下载边写入临时文件,内存占用更低。
    • 写入本地后,再通过 openDocumentsaveVideoToPhotosAlbum 等原生接口读取文件,避免将大文件一次性加载到内存中处理。
  2. 不同平台的临时路径差异

    • 微信小程序:使用 wx.env.USER_DATA_PATH 作为读写的沙盒根目录。
    • 支付宝/百度小程序:可以使用相对路径(如 '' + Date.now() + '.jpg'),或者先调用 uni.getStorageInfoSync() 获取缓存根路径。
    • uniapp H5 端uni.getFileSystemManager() 不可用,需要使用浏览器的 BlobURL.createObjectURL 等方式处理文件。
  3. 文件权限与用户授权

    • 在调用 uni.saveImageToPhotosAlbumuni.saveVideoToPhotosAlbum 时,需先申请用户授权 相册/存储权限;如果用户拒绝,fail 回调会被触发,需提示用户手动开启权限。
    • 示例:

      uni.authorize({
        scope: 'scope.writePhotosAlbum',
        success: () => { /* 有权限,直接保存 */ },
        fail: () => {
          uni.showModal({
            title: '提示',
            content: '需要您授权保存到相册,否则无法保存。',
            success: (res) => {
              if (res.confirm) {
                uni.openSetting();
              }
            }
          });
        }
      });
  4. Base64 字符串过大影响性能

    • 当将大文件转为 Base64,再作为字符串放到内存中时,可能导致性能问题或 setData 卡顿。尽量避免对巨型文件(如大于 5MB 的视频/PDF)使用 Base64,优先使用 uni.downloadFile / uni.uploadFile
    • 如果必须使用 Base64,可考虑分片上传或后端提供分段上传接口。
  5. 不同小程序平台的兼容性

    • 微信小程序 支持 wx.getFileSystemManagerwx.openDocumentwx.saveImageToPhotosAlbum 等 API;
    • 支付宝小程序 类似支持 my.getFileSystemManager()my.openDocument(部分版本需自行封装)和 my.saveImage 等;
    • 百度小程序 支持 swan.getFileSystemManager()swan.openDocumentswan.saveImageToPhotosAlbum
    • 需要在代码中做平台检测,或使用 uniapp 统一封装的 API。
  6. Canvas 大小与导出分辨率

    • uni.canvasToTempFilePath 支持传 widthheightdestWidthdestHeight 等参数,控制导出图片的分辨率与质量。例如 destWidth: 600, destHeight: 600 可导出更高清的 PNG。

九、结语

通过本篇《uniapp 小程序处理 Blob 二进制流数据实战指南》,你已经掌握了:

  1. 二进制流的概念:了解浏览器端的 Blob 与 ArrayBuffer,在小程序中如何绕过 Blob 限制,使用 ArrayBuffer 结合文件读写进行二进制处理。
  2. 核心 APIuni.request(responseType:'arraybuffer')uni.downloadFileuni.getFileSystemManager().writeFile/readFileuni.openDocumentuni.saveImageToPhotosAlbumuni.canvasToTempFilePathuni.request 上传二进制流等关键函数用法。
  3. 典型场景实战

    • 下载 PDF/图片/视频 → 写入本地 → 预览/保存相册;
    • Canvas 绘制 → 导出临时文件 → 读取 Base64 → 上传;
    • 选本地文件 → 读取 ArrayBuffer → 上传二进制流;
    • UE:下载文件带进度 → 写入后保存。
  4. 性能与兼容:掌握在大文件、高并发场景下避免一次性加载过大数据,以及各小程序平台的兼容性处理方法。
  5. 完整示例:提供了一个多功能页面,可直接复制到你的 uniapp 项目中快速使用或二次改造。

评论已关闭

推荐阅读

DDPG 模型解析,附Pytorch完整代码
2024年11月24日
DQN 模型解析,附Pytorch完整代码
2024年11月24日
AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日