Android無(wú)需讀寫(xiě)權(quán)限通過(guò)臨時(shí)授權(quán)讀寫(xiě)用戶文件詳解
正文
在進(jìn)行需求開(kāi)發(fā)的時(shí)候,我們總是避不開(kāi)和用戶的數(shù)據(jù)打交道,那提到獲取用戶的數(shù)據(jù)一定會(huì)想到的東西就是申請(qǐng)權(quán)限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
在我剛學(xué)習(xí)安卓的時(shí)候,我是以為APP一定要聲明了讀寫(xiě)用戶空間權(quán)限并且在用戶授權(quán)之后才能獲取到用戶的文件,即使是做個(gè)簡(jiǎn)簡(jiǎn)單單的更換頭像的功能,或者是升級(jí)APP時(shí)下載新的APK。對(duì)于后者,我們其實(shí)可以將升級(jí)的APK包放到我們應(yīng)用的私有目錄下(無(wú)需權(quán)限),對(duì)于前者,有什么比較輕量,適合快速開(kāi)發(fā)需求的方法來(lái)滿足呢。
這里插三段小說(shuō)明,如果只想知道方法的可以直接跳過(guò)
- 首先我們要明白,為什么谷歌要用讀寫(xiě)權(quán)限來(lái)限制APP對(duì)用戶文件的操作權(quán)。答案其實(shí)很明顯,因?yàn)樾枰乐笰PP惡意侵犯用戶隱私,或者是在用戶的目錄里存放大量的垃圾文件,在用戶目錄里存放的文件是不會(huì)隨著APP的卸載而被刪除的,所以如果所有APP都在用戶的目錄里存放文件(像是相冊(cè)文件夾/下載文件夾),那用戶的體驗(yàn)別提有多糟糕了。
- 其次就是聲明權(quán)限其實(shí)是有挺多弊端的,如果不是非必須的權(quán)限,其實(shí)谷歌是希望我們能不要就不要的。做過(guò)谷歌應(yīng)用市場(chǎng)開(kāi)發(fā)的就知道,你聲明的每個(gè)權(quán)限都會(huì)在谷歌應(yīng)用的詳情頁(yè)標(biāo)注,這不僅僅是讓用戶一進(jìn)來(lái)就覺(jué)得:"這個(gè)APP又要窺探我隱私",而且是讓你在填應(yīng)用的數(shù)據(jù)安全表單時(shí)更加地麻煩,因?yàn)槟懵暶髁俗x寫(xiě)權(quán)限,那你就要說(shuō)明你的APP會(huì)獲取用戶的什么數(shù)據(jù),如何保存,用戶是否可以刪除以及是否知情等等。還有就是你聲明的權(quán)限越多,你的應(yīng)用審核時(shí)間就會(huì)越長(zhǎng),這個(gè)我相信沒(méi)有人覺(jué)得無(wú)所謂吧
- 第三就是,Android11及以上的版本其實(shí)已經(jīng)大削了WRITE_EXTERNAL_STORAGE這個(gè)權(quán)限,谷歌不再允許APP悄悄地在用戶的外置存儲(chǔ)目錄里偷偷拉屎了,你在用戶目錄里創(chuàng)建什么目錄存取什么數(shù)據(jù)都要在用戶知情并且同意的情況下才能進(jìn)行,而本文要介紹的方式是能兼容到Android13的,所以趕緊學(xué)起來(lái)吧^-^
模擬獲取用戶的圖片的邏輯
我們需要拿到代表用戶臨時(shí)授權(quán)給APP的Uri
通過(guò)
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就是用戶臨時(shí)授權(quán)的文件/文件夾的的標(biāo)識(shí)
}
}
或者
val launch = registerForActivityResult(ActivityResultContracts.GetContent()){uri->
//這里獲取到的uri就是用戶臨時(shí)授權(quán)的文件/文件夾的的標(biāo)識(shí)
}
//這里傳的參數(shù)是你要獲取的文件類型的mimeType
launch.launch("*/*")
啟動(dòng)系統(tǒng)的內(nèi)容選擇器讓用戶選擇要分享給我們APP的文件,以獲得文件的Uri
通過(guò)contentResolver打開(kāi)文件的文件描述符FileDescriptor
val pfd : ParcelFileDescriptor? = context.contentResolver.openFileDescriptor(uri, "r")
第一個(gè)參數(shù)是我們剛剛得到的文件的uri,第二個(gè)文件是表示我們對(duì)文件的操作模式,我現(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.
通過(guò)FileDescriptor可以打開(kāi)一個(gè)文件IO流(FIS或者FOS),就可以讀寫(xiě)文件啦
FileInputStream(pfd.fileDescriptor).use {
//這里可以先將用戶的圖片復(fù)制到私有目錄中,再讓用戶做進(jìn)一步的編輯操作
}
FileOutputStream(pfd.fileDescriptor).use {
}
但是注意,打開(kāi)的fileDescriptor是Closeable對(duì)象,所以用完之后需要手動(dòng)close(),這里我用的是ktolin的擴(kuò)展函數(shù),會(huì)在use代碼塊里的代碼運(yùn)行完之后自動(dòng)關(guān)閉流
另一種讀取文件的方法,還是使用contentResolver直接打開(kāi)io流
context.contentResolver.openInputStream(uri)?.use {
}
context.contentResolver.openOutputStream(uri)?.use {
}
模擬將文件寫(xiě)入用戶目錄的操作
其實(shí)思路是一模一樣的,只是你啟動(dòng)文件系統(tǒng)的意圖(intent)不一樣,以及對(duì)文件的操作不一樣
我們需要拿到代表用戶臨時(shí)授權(quán)給APP的Uri
//這里傳入你要?jiǎng)?chuàng)建的文件類型的mimeType,如果是"*/*"那就代表文件夾
val launcher = registerForActivityResult(ActivityResultContracts.CreateDocument("*/*")){uri->
//這里獲取到的uri是已經(jīng)創(chuàng)建好的文件的uri
}
//這里傳入要?jiǎng)?chuàng)建的文件名
launcher.launch("cache.png")
啟動(dòng)之后是這個(gè)界面

通過(guò)contentResolver打開(kāi)文件的文件描述符FileDescriptor
val pfd : ParcelFileDescriptor? = context.contentResolver.openFileDescriptor(uri, "rw")
第一個(gè)參數(shù)是我們剛剛得到的文件的uri,第二個(gè)文件是表示我們對(duì)文件的操作模式,我現(xiàn)在示范的是保存一張圖片所以要用讀寫(xiě)模式("rw")
通過(guò)FileDescriptor可以打開(kāi)一個(gè)文件IO流(FIS或者FOS),就可以寫(xiě)文件啦
FileOutputStream(pfd.fileDescriptor).use {
//這里將處理好的圖片利用fos寫(xiě)到用戶剛才用uri指定的地方
}
另一種讀取文件的方法,還是使用contentResolver直接打開(kāi)io流
context.contentResolver.openOutputStream(uri)?.use {
}
模擬獲取用戶文件夾控制權(quán)的操作
最后再模擬一下獲取用戶文件夾控制權(quán)的操作,通過(guò)這個(gè)方法你可以拿到其他應(yīng)用在外置存儲(chǔ)里的目錄(例如一些聊天軟件的聊天記錄其實(shí)就是存放在這個(gè)目錄的)
(先截了張圖,過(guò)兩天填坑)

通過(guò)Uri獲取文件信息
最后再介紹一 通過(guò)Uri獲取文件信息(文件名/文件大小/文件Mime類型)的方法
//第二個(gè)參數(shù)相當(dāng)于是sql里的select,列表里是要過(guò)濾的列名,如果傳null那說(shuō)明取所有的列,這樣性能會(huì)比較差
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)這三個(gè)有用的信息 注意,獲取到的cursor是Closeable對(duì)象,所以用完之后需要手動(dòng)close()
以上就是Android無(wú)需讀寫(xiě)權(quán)限通過(guò)臨時(shí)授權(quán)讀寫(xiě)用戶文件詳解的詳細(xì)內(nèi)容,更多關(guān)于Android臨時(shí)授權(quán)讀寫(xiě)用戶文件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android APP之WebView校驗(yàn)SSL證書(shū)的方法
這篇文章主要介紹了Android APP之WebView校驗(yàn)SSL證書(shū)的方法,需要的朋友可以參考下2017-09-09
android教你打造獨(dú)一無(wú)二的上拉下拉刷新加載框架
本篇文章主要介紹了android教你打造獨(dú)一無(wú)二的下拉刷新加載框架,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-03-03
Android?Flutter實(shí)現(xiàn)有趣的頁(yè)面滾動(dòng)效果
Flutter提供了?CustomScrollView?來(lái)粘合多個(gè)滑動(dòng)組件,并且可以實(shí)現(xiàn)更有趣的滑動(dòng)效果,本文就來(lái)為大家詳細(xì)講講實(shí)現(xiàn)的方法,需要的可以參考一下2022-06-06
Android開(kāi)發(fā)之Gradle?進(jìn)階Tasks深入了解
這篇文章主要為大家介紹了Android開(kāi)發(fā)之Gradle?進(jìn)階Tasks深入了解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
Android Flutter自適應(yīng)瀑布流案例詳解
這篇文章主要介紹了Android Flutter自適應(yīng)瀑布流案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
Android 使用jQuery實(shí)現(xiàn)item點(diǎn)擊顯示或隱藏的特效的示例
本篇文章主要介紹了Android 使用jQuery實(shí)現(xiàn)item點(diǎn)擊顯示或隱藏的特效的示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
Android技巧一之啟動(dòng)屏+新功能左右導(dǎo)航邏輯
這篇文章主要介紹了Android技巧一之啟動(dòng)屏+新功能左右導(dǎo)航邏輯的相關(guān)資料,需要的朋友可以參考下2016-01-01
InputFilter實(shí)現(xiàn)EditText文本輸入過(guò)濾器實(shí)例代碼解析
EditText是Android的文本輸入框控件。這篇文章給大家介紹 InputFilter實(shí)現(xiàn)EditText文本輸入過(guò)濾器實(shí)例代碼解析,需要的朋友一起看看吧2016-11-11

