您好,登錄后才能下訂單哦!
這篇文章主要講解了“Android無需讀寫權(quán)限通過臨時授權(quán)讀寫用戶文件怎么實(shí)現(xiàn)”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Android無需讀寫權(quán)限通過臨時授權(quán)讀寫用戶文件怎么實(shí)現(xiàn)”吧!
在進(jìn)行需求開發(fā)的時候,我們總是避不開和用戶的數(shù)據(jù)打交道,那提到獲取用戶的數(shù)據(jù)一定會想到的東西就是申請權(quán)限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
在我剛學(xué)習(xí)安卓的時候,我是以為APP一定要聲明了讀寫用戶空間權(quán)限并且在用戶授權(quán)之后才能獲取到用戶的文件,即使是做個簡簡單單的更換頭像的功能,或者是升級APP時下載新的APK。對于后者,我們其實(shí)可以將升級的APK包放到我們應(yīng)用的私有目錄下(無需權(quán)限),對于前者,有什么比較輕量,適合快速開發(fā)需求的方法來滿足呢。
首先我們要明白,為什么谷歌要用讀寫權(quán)限來限制APP對用戶文件的操作權(quán)。答案其實(shí)很明顯,因?yàn)樾枰乐笰PP惡意侵犯用戶隱私,或者是在用戶的目錄里存放大量的垃圾文件,在用戶目錄里存放的文件是不會隨著APP的卸載而被刪除的,所以如果所有APP都在用戶的目錄里存放文件(像是相冊文件夾/下載文件夾),那用戶的體驗(yàn)別提有多糟糕了。
其次就是聲明權(quán)限其實(shí)是有挺多弊端的,如果不是非必須的權(quán)限,其實(shí)谷歌是希望我們能不要就不要的。做過谷歌應(yīng)用市場開發(fā)的就知道,你聲明的每個權(quán)限都會在谷歌應(yīng)用的詳情頁標(biāo)注,這不僅僅是讓用戶一進(jìn)來就覺得:"這個APP又要窺探我隱私",而且是讓你在填應(yīng)用的數(shù)據(jù)安全表單時更加地麻煩,因?yàn)槟懵暶髁俗x寫權(quán)限,那你就要說明你的APP會獲取用戶的什么數(shù)據(jù),如何保存,用戶是否可以刪除以及是否知情等等。還有就是你聲明的權(quán)限越多,你的應(yīng)用審核時間就會越長,這個我相信沒有人覺得無所謂吧
第三就是,Android11及以上的版本其實(shí)已經(jīng)大削了WRITE_EXTERNAL_STORAGE這個權(quán)限,谷歌不再允許APP悄悄地在用戶的外置存儲目錄里偷偷拉屎了,你在用戶目錄里創(chuàng)建什么目錄存取什么數(shù)據(jù)都要在用戶知情并且同意的情況下才能進(jìn)行,而本文要介紹的方式是能兼容到Android13的,所以趕緊學(xué)起來吧^-^
我們需要拿到代表用戶臨時授權(quán)給APP的Uri
通過
val intent = Intent(Intent.ACTION_GET_CONTENT) .addCategory(Intent.CATEGORY_OPENABLE) //這里傳的參數(shù)是你要獲取的文件類型的mimeType .setType(mimeType) startActivityForResult(intent,1024) override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == 1024 && resultCode == RESULT_OK) { val uri = data?.data //這里獲取到的uri就是用戶臨時授權(quán)的文件/文件夾的的標(biāo)識 } }
或者
val launch = registerForActivityResult(ActivityResultContracts.GetContent()){uri-> //這里獲取到的uri就是用戶臨時授權(quán)的文件/文件夾的的標(biāo)識 } //這里傳的參數(shù)是你要獲取的文件類型的mimeType launch.launch("*/*")
啟動系統(tǒng)的內(nèi)容選擇器讓用戶選擇要分享給我們APP的文件,以獲得文件的Uri
通過contentResolver打開文件的文件描述符FileDescriptor
val pfd : ParcelFileDescriptor? = context.contentResolver.openFileDescriptor(uri, "r")
第一個參數(shù)是我們剛剛得到的文件的uri,第二個文件是表示我們對文件的操作模式,我現(xiàn)在示范的是讀取用戶圖片所以用只讀模式("r")就可以了,關(guān)于mode的具體注釋,這里我直接粘貼原文了
mode – The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" or "rwt". SeeParcelFileDescriptor.parseMode for more details.
通過FileDescriptor可以打開一個文件IO流(FIS或者FOS),就可以讀寫文件啦
FileInputStream(pfd.fileDescriptor).use { //這里可以先將用戶的圖片復(fù)制到私有目錄中,再讓用戶做進(jìn)一步的編輯操作 } FileOutputStream(pfd.fileDescriptor).use { }
但是注意,打開的fileDescriptor是Closeable對象,所以用完之后需要手動close(),這里我用的是ktolin的擴(kuò)展函數(shù),會在use代碼塊里的代碼運(yùn)行完之后自動關(guān)閉流
另一種讀取文件的方法,還是使用contentResolver直接打開io流
context.contentResolver.openInputStream(uri)?.use { } context.contentResolver.openOutputStream(uri)?.use { }
其實(shí)思路是一模一樣的,只是你啟動文件系統(tǒng)的意圖(intent)不一樣,以及對文件的操作不一樣
我們需要拿到代表用戶臨時授權(quán)給APP的Uri
//這里傳入你要創(chuàng)建的文件類型的mimeType,如果是"*/*"那就代表文件夾 val launcher = registerForActivityResult(ActivityResultContracts.CreateDocument("*/*")){uri-> //這里獲取到的uri是已經(jīng)創(chuàng)建好的文件的uri } //這里傳入要創(chuàng)建的文件名 launcher.launch("cache.png")
啟動之后是這個界面
通過contentResolver打開文件的文件描述符FileDescriptor
val pfd : ParcelFileDescriptor? = context.contentResolver.openFileDescriptor(uri, "rw")
第一個參數(shù)是我們剛剛得到的文件的uri,第二個文件是表示我們對文件的操作模式,我現(xiàn)在示范的是保存一張圖片所以要用讀寫模式("rw")
通過FileDescriptor可以打開一個文件IO流(FIS或者FOS),就可以寫文件啦
FileOutputStream(pfd.fileDescriptor).use { //這里將處理好的圖片利用fos寫到用戶剛才用uri指定的地方 }
另一種讀取文件的方法,還是使用contentResolver直接打開io流
context.contentResolver.openOutputStream(uri)?.use { }
最后再模擬一下獲取用戶文件夾控制權(quán)的操作,通過這個方法你可以拿到其他應(yīng)用在外置存儲里的目錄(例如一些聊天軟件的聊天記錄其實(shí)就是存放在這個目錄的)
最后再介紹一 通過Uri獲取文件信息(文件名/文件大小/文件Mime類型)的方法
//第二個參數(shù)相當(dāng)于是sql里的select,列表里是要過濾的列名,如果傳null那說明取所有的列,這樣性能會比較差 val cursor: Cursor? = context.contentResolver.query( this, arrayOf(MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.SIZE), null, null, null )?.use { cursor -> if (cursor.moveToFirst()) { val columnIndex1 = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) if (columnIndex1 > -1) { name = cursor.getString(columnIndex1) } val columnIndex2 = cursor.getColumnIndex(MediaStore.MediaColumns.SIZE) if (columnIndex2 > -1) { size = cursor.getLong(columnIndex2) } }
文件的話,用正常途徑也只能拿到文件名(MediaStore.MediaColumns.DISPLAY_NAME),文件大小(MediaStore.MediaColumns.SIZE),文件Mime類型(MediaStore.MediaColumns.MIME_TYPE)這三個有用的信息 注意,獲取到的cursor是Closeable對象,所以用完之后需要手動close()
感謝各位的閱讀,以上就是“Android無需讀寫權(quán)限通過臨時授權(quán)讀寫用戶文件怎么實(shí)現(xiàn)”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Android無需讀寫權(quán)限通過臨時授權(quán)讀寫用戶文件怎么實(shí)現(xiàn)這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。