uniapp_01_實現打開文件管理
關于uniapp實現app端文件管理
- 前言
- 安卓是如何實現的
- uniapp中幾種實現方式
- 文檔里需要用到的主要api介紹
- 實現
注: 可以先閱讀 [h5+ io操作](https://www.html5plus.org/doc/zh_cn/io.html) 在看本文章, 當時沒注意到io所以全調用的原生的包導致只能在安卓使用
前言
最近,用uniapp寫一個app,其中有個功能,需要訪問SDcard中的目錄和文件,在查閱uniapp官方文檔后,發現文檔給出的api不支持app端,只能用html5+native.js去獲取。于是,在踩了無數坑之后,就有了這篇記錄。
安卓是如何實現的
安卓針不同的版本實現的方法有所不同, Android 6 (API 23) 之前應用的權限在安裝時全部授予,運行時應用不再需要詢問用戶。在 Android 6.0 或更高版本對權限進行了分類,對某些涉及到用戶隱私的權限可在運行時根據用戶的需要動態授予
Android 10
- 需要在 AndroidManifest.xml 中添加 android.permission.WRITE_EXTERNAL_STORAGE 和 android.permission.READ_EXTERNAL_STORAGE 進行權限申請。
- application 中設置 android:requestLegacyExternalStorage="true"
- 然后需要動態的請求權限
Android 11
- 需要在 AndroidManifest.xml 中添加 android.permission.MANAGE_EXTERNAL_STORAGE
- 需要動態獲取權限
- 參考 android 11 獲取全部文件權限
- 參考 Android 10、11 存儲完全適配
- 參考 Android Q中文件沙盒模式讀寫文件 08-16
- 參考 Android開發之 permission動態權限獲取
- 參考 Android 獲取某個文件夾下的所有文件
- 參考 Java--getAbsolutePath()獲取絕對路徑和相對路徑getPath()getName()listFiles()
uniapp中幾種實現方式
- 使用 web-view , 指向一個 html 文件, 在html文件里面用input實現
- 用 html5+和native.js
- 參考 HTML5+規范5+Specification
文檔里需要用到的主要api介紹
- requestPermissions 獲取授權 !!!!此api十分重要,如果不用這個api獲取權限的話,就只能讀取沙盒或公共媒體文件夾里的文件, requestPermissions 需要傳入三個參數 第一個是權限數組,第二個成功回調方法,第三個是失敗回調
- runtimeMainActivity 我原本以為之只是獲取一個Activity實例后來才發現它相當于 Android中的 Context
- importClass 這個api是導入包的api 你可以到導入java包寫原生
- newObject 有了這個api感覺可以將 importClass 丟一邊去了,這個api是將導入和實例化合并了,第一次參數填要導入的包,第二個填實例化的需要傳遞的參數
- invoke 這個api是調用方法!!!十分重要
- IO IO模塊管理本地文件系統,用于對文件系統的目錄瀏覽、文件的讀取、文件的寫入等操作
實現
注:下面代碼如果有更好的實現方式或那里寫錯了,歡迎各位大佬在評論區指正
-
打開文件管理器
// 獲取應用主Activity實例對象 const MAIN = plus.android.runtimeMainActivity(); const INTENT = plus.android.importClass('android.content.Intent'); // 導入 Intent 類 const INTENT_OBJ = new INTENT(INTENT.ACTION_GET_CONTENT); INTENT_OBJ.addCategory(INTENT.CATEGORY_OPENABLE); // 創建分類 INTENT_OBJ.setType("*/*"); // 設置類型, 任意類型 image/* video/* .... // intent.putExtra(Intent.EXTRA_MIME_TYPES, 'image/*'); 設置多個類型 // intent.setDataAndType(mUri,"image/*"); MAIN.onActivityResult = (requestCode, resultCode, data) => { // ... 選擇的文件data MAIN.startActivityForResult(INTENT_OBJ, 1); -
獲取外部存儲權限
const permissionsList = [ "android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.READ_EXTERNAL_STORAGE" ]; plus.android.requestPermissions(permissionsList,(e)=>{ // ... 注: e里面包括了永久拒絕,拒絕,同意授權這些信息 }) -
判斷安卓版本
let Build = plus.android.importClass('android.os.Build'); let isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; -
判斷格式是否是uri類型的
let DocumentsContract = plus.android.importClass('android.provider.DocumentsContract'); // 判斷是否是這個格式的url content://com.android.providers.media.documents/document/image%3A82482 let isSystemUri = DocumentsContract.isDocumentUri(MAIN, uri); -
打開文件文件管理器選擇文件返回絕對路徑
/** * TODO 系統自帶的文件管理器 中 最近/圖片/視頻。。。等直接獲取 codid會是類型+文件id 沒有之前的路徑 需要檢測添加 * https://blog.csdn.net/qq_43278826/article/details/101672670 * https://www.runoob.com/w3cnote/android-tutorial-intent-base.html * https://juejin.cn/post/7012108220982362149 * @method openFileManager 打開系統文件管理器 選擇文件放回文件路徑 * @description uniapp 沒有提供打開安卓文件管理器的api 必須使用 input 或 plus * */ openFileManager: function() { // platform 系統 browserVersion 系統版本 const {platform, browserVersion} = uni.getSystemInfoSync(); if(platform == 'android') { const Activity = plus.android.runtimeMainActivity(); // 獲取 Activity const Intent = plus.android.importClass("android.content.Intent"); // 導入 Intent let initen_new = new Intent(Intent.ACTION_GET_CONTENT, null); // 實例化 Intent 并允許 獲取的是所有本地文件 可設置文件格式用于限制 initen_new.setType("*/*"); // 設置類型 */* 無類型限制 initen_new.addCategory(Intent.CATEGORY_OPENABLE); // Activity.startActivityForResult(initen_new, 1); // 啟動Activity 打開系統的文件管理器 /** * onActivityResult 安卓中 用于從其他頁面返回時帶回數據 * */ Activity.onActivityResult = (requestCode, resultCode, data) => { // console.log("打開文件管理器后選擇的文件返回了", requestCode, resultCode, data); // 打開文件管理器后選擇的文件返回了 const Uri = data.getData(); plus.android.importClass(Uri); const DocumentsContract =plus.android.importClass("android.provider.DocumentsContract"); const Build = plus.android.importClass('android.os.Build'); let isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; // 判斷安卓版本是否大于 4.4 /** * uri=content://com.android.providers.media.documents/document/image%3A293502 4.4以后 * uri=file:///storage/emulated/0/temp_photo.jpg * uri=content://media/external/images/media/193968 * * uri=content://media/external/images/media/13 4.4以前 */ if(DocumentsContract.isDocumentUri(Activity, Uri)){ // 獲取文件類型和id const DocId = DocumentsContract.getDocumentId(Uri); const [Type, Id] = DocId.split(":"); // 解析出數字格式的id // console.log("文件類型和id",Type, Id); let authority = Uri.getAuthority(); // if(authority == "com.android.providers.media.documents") { const MediaStore = plus.android.importClass('android.provider.MediaStore'); let contentUri = null; let selection = "_id=?"; let selectionArgs = [Id]; // TODO 有些文件夾下文件無法獲取uri應當是此處問題 switch(Type) { case 'image': contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI ;break; case 'video': contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI ;break; case 'audio': contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI ;break; } this.getDataColumn(Activity, contentUri, selection, selectionArgs); } else if(authority == "com.android.providers.downloads.documents"){ const ContentUris = plus.android.importClass('android.content.ContentUris'); let contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), parseInt(DocId));// 此處需要將文件id轉換成long類型的 不然返回的是null this.getDataColumn(Activity, contentUri); } else if(authority == "com.android.externalstorage.documents"){ const Environment = plus.android.importClass('android.os.Environment'); if("primary" == Type) { console.log("類型為primary的文件地址:", `${Environment.getExternalStorageDirectory()}/${Id}`); } else { const System = plus.android.importClass('java.lang.System'); console.log("類型非primary的文件地址",`${System.getenv("SECONDARY_STORAGE")}/${Id}`); } } } else if("content" == Uri.getScheme()) { this.getDataColumn(Activity, Uri); } else if("file" == Uri.getScheme()) { console.log("文件路徑:", uri.getPath()); } } } console.log("當前手機的系統是",platform); }, /** * uri轉路徑轉換 * @method getDataColumn uri轉路徑轉換 * @param {Obejct} activity 安卓的實例 * @param {Obejct} uri 獲取到的文件地址 * @param {String} selection * @param {Array} selectionArgs 文件的id數組 * */ getDataColumn: function(activity, uri, selection = null, selectionArgs = null) { /** * 官方,提供了兩個, 用來將絕對路徑和平臺路徑互相轉換的api * plus.io.convertAbsoluteFileSystem(path) 將平臺絕對路徑轉換成本地URL路徑 * plus.io.convertLocalFileSystemURL(url) 本地URL路徑轉換成平臺絕對路徑 * 絕對路徑符合各平臺文件路徑格式,通常用于Native.JS調用系統原生文件操作API,也可以在前面添加“file://”后在html頁面中直接使用。 */ plus.android.importClass(activity.getContentResolver()); let cursor = activity.getContentResolver().query(uri, ['_data'], selection, selectionArgs, null); plus.android.importClass(cursor); if (cursor != null && cursor.moveToFirst()) { let column_index = cursor.getColumnIndexOrThrow('_data'); let result = cursor.getString(column_index) cursor.close(); uni.getFileInfo({ filePath: result, success: (res) => { console.log(res); } }) return result; } return null; } -
獲取用戶所有以安裝程序
/** * @method getAllApply 獲取用戶所有已安裝程序 * */ getAllApply: function() { const main = plus.android.runtimeMainActivity(); // 此處相當于 context let pManager = plus.android.invoke(main, 'getPackageManager'); let pInfo = plus.android.invoke(pManager, 'getInstalledPackages', 0); let total = plus.android.invoke(pInfo, 'size'); // 遍歷獲取包名和應用名稱 for (let i = 0; i < total; i++) { // 獲取包名 let packName = plus.android.getAttribute(plus.android.invoke(pInfo, 'get', i), 'packageName'); // 獲取包名對應的應用名 let obj = plus.android.invoke(pManager, 'getApplicationInfo', packName, 0); let appName = plus.android.invoke(pManager, 'getApplicationLabel', obj); console.log(packName, appName); } } -
獲取根目錄
/** * @method getRootSDCar 獲取手機外部存儲目錄 * @description 用于獲取手機的外部存儲目錄 * */ getRootSDCar: function() { // .... 在只用之前一定要仙獲取權限 不然只能訪問到沙盒里的內容 // 獲取root目錄路徑 const Environment = plus.android.importClass("android.os.Environment"); // getExternalStorageDirectory 獲取外部存儲目錄即 SDCard // getRootDirectory 獲取 Android 的根目錄 即系統主目錄 let data = Environment.getExternalStorageDirectory(); let rootPath = plus.android.invoke(data, "getAbsolutePath"); console.log("根目錄", rootPath); } -
獲取絕對路徑
/** * @method getAbsolutePath 獲取絕對路徑 * */ getAbsolutePath: function() { /* StorageEventListener中有onStorageStateChanged()方法,當sd卡狀態改變時, 此方法會調用,對各狀態的判斷一般會用到Environment類,此類中包含的有關sd卡狀態的常量有: MEDIA_BAD_REMOVAL: 表明SDCard 被卸載前己被移除 MEDIA_CHECKING: 表明對象正在磁盤檢查 MEDIA_MOUNTED: 表明sd對象是存在并具有讀/寫權限 MEDIA_MOUNTED_READ_ONLY: 表明對象權限為只讀 MEDIA_NOFS: 表明對象為空白或正在使用不受支持的文件系統 MEDIA_REMOVED: 如果不存在 SDCard 返回 MEDIA_SHARED: 如果 SDCard 未安裝 ,并通過 USB 大容量存儲共享 返回 MEDIA_UNMOUNTABLE: 返回 SDCard 不可被安裝 如果 SDCard 是存在但不可以被安裝 MEDIA_UNMOUNTED: 返回 SDCard 已卸掉如果 SDCard 是存在但是沒有被安裝 */ const main = plus.android.runtimeMainActivity(); // 此處相當于 context const Build = plus.android.importClass('android.os.Build'); const Environment = plus.android.importClass("android.os.Environment"); let state = Environment.getExternalStorageState(); // 返回sd卡狀態 let isState = plus.android.invoke(state,'equals', Environment.MEDIA_MOUNTED); let dir = null; if(isState) { if(Build.VERSION.SDK_INT >= 29) { /* DIRECTORY_MUSIC 音樂存放 DIRECTORY_PODCASTS 系統廣播 DIRECTORY_RINGTONES 系統鈴聲 DIRECTORY_ALARMS 系統提醒鈴聲 DIRECTORY_NOTIFICATIONS 系統通知鈴聲 DIRECTORY_PICTURES 圖片存放 DIRECTORY_MOVIES 電影存放 DIRECTORY_DOWNLOADS 下載 DIRECTORY_DCIM 相機拍攝照片和視頻 */ // dir = main.getExternalFilesDir(Environment.DIRECTORY_MUSIC) // 獲取音樂; // dir = main.getDataDir(); // > data/形式的路徑 // getDataDirectory dir = main.getExternalFilesDir(null); // 獲取當前app下面的flies文件夾 this.path = plus.android.invoke(dir,"getAbsolutePath"); // 獲取絕對路徑 console.log("獲取此應用下Flies文件夾路徑",plus.android.invoke(dir,"getAbsolutePath")); /* // 獲得父目錄 this.filePath = plus.android.invoke(dir, "getParentFile"); console.log("父目錄",plus.android.invoke(this.filePath, "getName")); // 擁有的文件 let child = plus.android.invoke(dir, "listFiles"); for(let i = 0 ;i<child?.length;i++){ this.childPath.push(plus.android.invoke(child[i], "getAbsolutePath")) console.log("是子文件",plus.android.invoke(child[i], "getAbsolutePath")); } */ } else { dir = Environment.getRootDirectory() } } }, -
通過絕對路徑獲取此路徑下所有子文件 HTML5+ 實現
plus.io.resolveLocalFileSystemURL(`/storage/emulated/0/`,(metadata)=>{ // ... 需要獲取權限 // metadata.isDirectory // 判斷是是否是文件夾 // metadata.isFile();//判斷是是否是文件 let directoryReader = metadata.createReader(); // 創建一個目錄對象 獲取下面的子文件 directoryReader.readEntries((entries)=>{ for (var i = 0; i < entries.length; i++) { console.log("文件信息:" + entries[i].name); } }, (err)=>{}) -
通過絕對路徑獲取此路徑下所有子文件 偏向與原生 實現
getAllTheChildFiles: function(path){ this.childPath = [] const main = plus.android.runtimeMainActivity(); // 此處相當于 context const Build = plus.android.importClass('android.os.Build'); // 判斷 sd卡狀態 const Environment = plus.android.importClass("android.os.Environment"); const state = Environment.getExternalStorageState(); const isState = plus.android.invoke(state,'equals', Environment.MEDIA_MOUNTED); if(isState) { // 調用java File包實現 // let File = plus.android.importClass("java.io.File"); // let File_new = new File(metadata.toLocalURL()); const File = plus.android.newObject("java.io.File", `${path}`); // 導入包并new這個類 const exists = !plus.android.invoke(File, "exists"); // 判斷路徑是否存在 if(exists) return; const listFiles = plus.android.invoke(File, "listFiles");// 獲取子文件列表 this.filePath = `${path}` for(let i = 0; i<listFiles.length;i++){ const name = `${plus.android.invoke(listFiles[i], "getName")}`; const isFile = plus.android.invoke(listFiles[i], "isDirectory"); } } }

浙公網安備 33010602011771號