在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
將文件從臨時路徑拷貝到目標路徑。
代碼示例:
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
:確保每次文件操作完成后再進行下一次操作。 -
在所有文件處理完成后統一刪除:避免在每次復制后立即刪除文件,而是等到所有文件處理完成后統一刪除。
代碼實例:
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
捕獲異步操作中的錯誤,并提供詳細的錯誤提示。
-
-
用戶體驗:
-
在操作過程中顯示加載提示。
-
在操作完成后提供明確的反饋信息。
-
通過這些優化,代碼將更加健壯、易讀,并且可以避免并發問題。