在Android 10及以上版本中,由于系统对文件访问的限制,使用chooseImage并勾选原图上传后,返回的是图片的外部存储路径,如:'file:///storage/emulated/0/DCIM/Camera/'。这种外部存储路径,无法直接转换成所需要的数据格式,如base64。
上传原图解决方案
为了适配Android 10及以上版本,需要将文件从外部存储路径拷贝到应用的私有目录(如_doc/
),然后在应用内部进行操作。
1. 使用uni.chooseImage
或plus.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
时,可能会遇到以下问题:
-
异步操作的顺序问题 :由于
copyFile
是异步操作,循环中的每次调用可能会同时进行,导致文件路径冲突或其他问题。 -
文件删除操作的时机问题 :你在
copyFile
中尝试在所有文件处理完成后删除原文件,但由于异步操作的不确定性,可能会导致删除操作提前执行,影响后续操作。
循环上传解决方案
为了解决这些问题,可以使用以下方法:
-
使用
Promise
和async/await
:确保每次文件操作完成后再进行下一次操作。 -
在所有文件处理完成后统一删除:避免在每次复制后立即删除文件,而是等到所有文件处理完成后统一删除。
代码实例:
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);
});
});
},
优化点说明
-
使用
async/await
:-
将
plus.gallery.pick
的回调改为async
函数,并在循环中使用await
来同步处理每个文件。 -
确保每次文件处理完成后才进行下一次操作。
-
-
统一处理逻辑:
- 将
saveImageToDoc
方法改为异步方法,确保文件复制和上传操作是同步进行的。
- 将
-
错误处理:
- 使用
try-catch
捕获异步操作中的错误,并提供详细的错误提示。
- 使用
-
用户体验:
-
在操作过程中显示加载提示。
-
在操作完成后提供明确的反馈信息。
-
通过这些优化,代码将更加健壮、易读,并且可以避免并发问题。