uni-app 安卓10以上上传原图解决方案

Android 10及以上版本中,由于系统对文件访问的限制,使用chooseImage并勾选原图上传后,返回的是图片的外部存储路径,如:'file:///storage/emulated/0/DCIM/Camera/'。这种外部存储路径,无法直接转换成所需要的数据格式,如base64。

上传原图解决方案

为了适配Android 10及以上版本,需要将文件从外部存储路径拷贝到应用的私有目录(如_doc/),然后在应用内部进行操作。

1. 使用uni.chooseImageplus.gallery.pick选择图片
  • 这些API会返回一个临时路径,该路径是应用可以访问的。
2. 将文件拷贝到应用的私有目录
  • 使用plus.io.resolveLocalFileSystemURL解析应用的私有目录路径(如_doc/)。

  • 使用fileEntry.copyTo将文件从临时路径拷贝到目标路径。

代码示例:

javascript 复制代码
export default {
  data() {
    return {
      tempFilePath: '', // 临时文件路径
      targetFilePath: '' // 目标文件路径
    };
  },
  methods: {
    async chooseImage() {
     
      // 调用uni.chooseImage选择图片
      uni.chooseImage({
        count: 1,
        sizeType: ['original', 'compressed'],
        sourceType: ['album', 'camera'],
        success: (res) => {
          this.tempFilePath = res.tempFilePaths[0];
          this.saveImageToDoc();
        },
        fail: (err) => {
          console.error('选择图片失败:', err);
        }
      });
    },

    saveImageToDoc() {
      const fileName = this.tempFilePath.split('/').pop();
      this.targetFilePath = `_doc/${fileName}`;

      // 确保目标目录存在
      plus.io.resolveLocalFileSystemURL('_doc/', (root) => {
        console.log('目标目录已存在');
        // 检查目标文件是否存在
        plus.io.resolveLocalFileSystemURL(this.targetFilePath, (fileEntry) => {
          fileEntry.remove(() => {
            console.log('文件已删除,可以重新复制');
            this.copyFile(root);
          }, (error) => {
            console.error('删除文件失败:', error.message);
          });
        }, (error) => {
          console.log('目标文件不存在,可以直接复制');
          this.copyFile(root);
        });
      }, (error) => {
        console.error('目标目录不存在,创建目录');
        plus.io.resolveLocalFileSystemURL('/', (fs) => {
          fs.getDirectory('doc', { create: true }, () => {
            console.log('目录创建成功');
            this.copyFile(fs.root);
          }, (error) => {
            console.error('目录创建失败:', error.message);
          });
        }, (error) => {
          console.error('无法访问根目录:', error.message);
        });
      });
    },
    copyFile(root) {
      plus.io.resolveLocalFileSystemURL(this.tempFilePath, (entry) => {
        entry.copyTo(root, fileName, (newEntry) => {
          console.log('文件复制成功:', newEntry.fullPath);
            //这里就拿到了图片的私有路径,可进行转换操作
          uni.showModal({
            title: '成功',
            content: '图片已保存到应用的_doc目录',
            showCancel: false
          });
        }, (error) => {
          console.error('复制文件失败:', error.message);
        });
      }, (error) => {
        console.error('解析文件路径失败:', error.message);
      });
    }
  }
};

注意:私有目录多了很多无用的图片,故需在使用完成后,立刻清理。

以上,就是一个简单的实现demo。

但是,如果选择了多个原图上传,可能会报错。因为在循环中调用copyFile时,可能会遇到以下问题:

  1. 异步操作的顺序问题 :由于copyFile是异步操作,循环中的每次调用可能会同时进行,导致文件路径冲突或其他问题。

  2. 文件删除操作的时机问题 :你在copyFile中尝试在所有文件处理完成后删除原文件,但由于异步操作的不确定性,可能会导致删除操作提前执行,影响后续操作。

循环上传解决方案

为了解决这些问题,可以使用以下方法:

  1. 使用Promiseasync/await:确保每次文件操作完成后再进行下一次操作。

  2. 在所有文件处理完成后统一删除:避免在每次复制后立即删除文件,而是等到所有文件处理完成后统一删除。

代码实例:

javascript 复制代码
async handleChooseImage(sourceType) {
  if (sourceType === 'camera') {
    this.handleStartGyro();
  }
  try {
    if (sourceType === 'album') {
      // 从相册中选择图片
      console.log("从相册中选择多张图片:");
      await new Promise((resolve, reject) => {
        plus.gallery.pick(async (e) => {
          if (e.files.length === 0) {
            console.log("取消选择图片");
            resolve();
            return;
          }
          uni.showToast({
            title: "上传中",
            icon: "loading"
          });
          for (const [index, data] of e.files.entries()) {
            await this.saveImageToDoc(data, index, e.files.length);
          }
          uni.hideLoading();
          uni.showToast({
            title: "上传完成",
            icon: "success"
          });
          resolve();
        }, (e) => {
          console.log("取消选择图片");
          resolve();
        }, {
          filter: "image",
          multiple: true
        });
      });
    } else {
      // 从相机中选择图片
      const res = await uni.chooseImage({
        count: 9,
        sizeType: ["original"],
        sourceType: [sourceType]
      });
      const imagePaths = res.tempFilePaths;
      let gyroData = '';
      if (sourceType === 'camera') {
        gyroData = this.gyroValueRaw.join(',');
      }
      this.gyroModule && this.gyroModule.stopCustomSensor();
      if (this.gyroUpdateTimer) clearInterval(this.gyroUpdateTimer);
      uni.showToast({
        title: "上传中",
        icon: "loading"
      });
      for (const [index, path] of imagePaths.entries()) {
        await this.handleUploadNew(path, index, imagePaths.length, gyroData);
      }
      uni.hideLoading();
      uni.showToast({
        title: "上传完成",
        icon: "success"
      });
      uni.removeStorageSync("workData");
      setTimeout(() => {
        uni.redirectTo({
          url: "/pages/work/work"
        });
      }, 1000);
    }
  } catch (error) {
    console.error("选择照片失败:", error);
    uni.showToast({
      title: "选择照片失败",
      icon: "none"
    });
  }
},

async saveImageToDoc(tempFilePath, index, total) {
  const fileName = tempFilePath.split('/').pop();
  this.targetFilePath = `_doc/${fileName}`;

  // 确保目标目录存在
  const root = await this.ensureDirectoryExists('_doc/');

  // 检查目标文件是否存在
  let fileEntry;
  try {
    fileEntry = await this.resolveFileEntry(this.targetFilePath);
    await fileEntry.remove();
    console.log('文件已删除,可以重新复制');
  } catch (error) {
    console.log('目标文件不存在,可以直接复制');
  }

  // 复制文件
  const newEntry = await this.copyFile(root, tempFilePath, fileName);
  console.log('文件复制成功:', newEntry.fullPath);

  // 上传文件
  await this.handleUploadNew(newEntry.fullPath, index, total);
  if (index === total - 1) {
    uni.hideLoading();
    uni.showToast({
      title: "所有图片已上传",
      icon: "success"
    });
  }
},

ensureDirectoryExists(dirPath) {
  return new Promise((resolve, reject) => {
    plus.io.resolveLocalFileSystemURL(dirPath, (root) => {
      resolve(root);
    }, (error) => {
      console.error('目标目录不存在,创建目录');
      plus.io.resolveLocalFileSystemURL('/', (fs) => {
        fs.getDirectory(dirPath, { create: true }, (root) => {
          resolve(root);
        }, (error) => {
          console.error('目录创建失败:', error.message);
          reject(error);
        });
      }, (error) => {
        console.error('无法访问根目录:', error.message);
        reject(error);
      });
    });
  });
},

resolveFileEntry(filePath) {
  return new Promise((resolve, reject) => {
    plus.io.resolveLocalFileSystemURL(filePath, (fileEntry) => {
      resolve(fileEntry);
    }, (error) => {
      reject(error);
    });
  });
},

copyFile(root, tempFilePath, fileName) {
  return new Promise((resolve, reject) => {
    plus.io.resolveLocalFileSystemURL(tempFilePath, (entry) => {
      entry.copyTo(root, fileName, (newEntry) => {
        resolve(newEntry);
      }, (error) => {
        console.error('复制文件失败:', error);
        reject(error);
      });
    }, (error) => {
      console.error('解析文件路径失败:', error);
      reject(error);
    });
  });
},

优化点说明

  1. 使用async/await

    • plus.gallery.pick的回调改为async函数,并在循环中使用await来同步处理每个文件。

    • 确保每次文件处理完成后才进行下一次操作。

  2. 统一处理逻辑

    • saveImageToDoc方法改为异步方法,确保文件复制和上传操作是同步进行的。
  3. 错误处理

    • 使用try-catch捕获异步操作中的错误,并提供详细的错误提示。
  4. 用户体验

    • 在操作过程中显示加载提示。

    • 在操作完成后提供明确的反馈信息。

通过这些优化,代码将更加健壮、易读,并且可以避免并发问题。

相关推荐
Monly213 小时前
Uniapp:创建项目
uni-app
清晨細雨3 小时前
uniapp微信小程序:WIFI设备配网之TCP/UDP开发AP配网
前端·物联网·小程序·uni-app
敲代码的彭于晏4 小时前
2025 年必看!uni-app 结合 VSCode 实现高效跨平台开发入门
vue.js·uni-app
盛夏绽放5 小时前
uni-app 状态管理深度解析:Vuex 与全局方案实战指南
前端·javascript·uni-app
七七小报5 小时前
uniapp-商城-31-shop页面中的 我的订单
uni-app
小凯!在努力13 小时前
无线uniapp调试设备
uni-app
samuel91814 小时前
uniapp通过uni.addInterceptor实现路由拦截
前端·javascript·uni-app
凌云峰14 小时前
Uniapp调用native.js使用经典蓝牙串口通讯方法及问题解决
uni-app
Benson叔16 小时前
uniapp APP端 DOM生成图片保存到相册
uni-app