Android 7.0 引進(jìn)了許多新特性和 API娃循,其中有一點(diǎn)被很多人都忽略了,或許是沒有注意到,或許是感覺使用起來比較麻煩,在這里我就基于自己使用的體驗和過程簡述一下這個叫做 “作用域目錄訪問(Scoped Directory Access)” 的新 API缀皱。
如圖所示,作用域目錄訪問和 Android 6.0 訪問內(nèi)部儲存空間一樣动猬,需要應(yīng)用程序主動向用戶請求讀寫權(quán)限啤斗。不同的是,作用域目錄訪問不再要求應(yīng)用聲明 android.permission.WRITE_EXTERNAL_STORAGE
權(quán)限赁咙,也限制了應(yīng)用程序訪問內(nèi)部儲存空間行為钮莲,只能在請求的作用域內(nèi)進(jìn)行讀寫操作(包括文件、子文件夾)彼水。
這個 API 看似意義不大崔拥,你獲得了訪問內(nèi)部儲存空間權(quán)限也同樣可以向那些特定訪問域中寫入文件,但對于用戶來說凤覆,能更加放心地讓應(yīng)用程序使用他/她的手機(jī)內(nèi)部儲存链瓦;對開發(fā)者/廠商而言,也嚴(yán)格要求自己不要濫用權(quán)限盯桦,未來高版本 Android 對存儲機(jī)制大改的時候也能很快地應(yīng)對(甚至無需做出任何改動)慈俯。
廢話不多說,下面是作用域目錄訪問特性的使用:
權(quán)限聲明
權(quán)限拥峦?不需要聲明吧……哦贴膘,還要考慮到對舊版本 Android 的兼容,還是需要在 AndroidManifest 中聲明內(nèi)部儲存權(quán)限事镣,同時傳統(tǒng)的訪問方式應(yīng)當(dāng)保留下來步鉴,對 Build.VERSION.SDK_INT
進(jìn)行判斷再采取不同的措施。
對于 Android 7.0璃哟,可以嘗試更加激進(jìn)的權(quán)限聲明:
<!-- maxSdkVersion 指定這個權(quán)限只在哪個版本以下使用 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="24"/>
如果你的應(yīng)用程序最低 SDK 版本就是 25氛琢,可以不加上這一行聲明。
申請權(quán)限
使用 StorageManager
類獲取你要訪問的卷的 StorageVolume
實例随闪。
要獲取所有可用卷的列表阳似,包括可移動介質(zhì)卷,請使用 StorageManager.getStorageVolumes()
铐伴。
一般撮奏,我們只使用內(nèi)部儲存空間俏讹,只需要 StorageManager.getPrimaryStorageVolume()
獲得主要卷即可。
然后畜吊,通過調(diào)用該實例的 StorageVolume.createAccessIntent()
方法創(chuàng)建一個 intent泽疆,使用此 intent 訪問外部存儲目錄。
createAccessIntent()
需要傳入一個參數(shù)指定你想訪問哪個作用域玲献,只能從以下常量中選擇:
- Environment.DIRECTORY_MUSIC
- Environment.DIRECTORY_PODCASTS
- Environment.DIRECTORY_RINGTONES
- Environment.DIRECTORY_ALARMS
- Environment.DIRECTORY_NOTIFICATIONS
- Environment.DIRECTORY_PICTURES
- Environment.DIRECTORY_MOVIES
- Environment.DIRECTORY_DOWNLOADS
- Environment.DIRECTORY_DCIM
- Environment.DIRECTORY_DOCUMENTS
各個常量指定的位置和作用這里就不一一解釋了殉疼,都是 Android 在內(nèi)部儲存或者 SD 卡中默認(rèn)生成的公用文件夾。
示例代碼:
StorageManager sm = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
Intent intent = sm.getPrimaryStorageVolume().createAccessIntent(Environment.DIRECTORY_PICTURES);
startActivityForResult(intent, REQUEST_SCOPED_PERMISSION);
執(zhí)行了 Intent 后應(yīng)用程序就會彈出請求對話框捌年,等待用戶確認(rèn)瓢娜。
接收請求結(jié)果和目錄 Uri
startActivityForResult()
后待用戶確認(rèn)后,應(yīng)用程序當(dāng)前的 Activity 就會接收到請求結(jié)果礼预,Request code 由自己決定(如前面的 REQUEST_SCOPED_PERMISSION
)眠砾。
如果用戶同意了權(quán)限,Result code 為 RESULT_OK
托酸,且返回的 Intent 通過 getData()
可以獲得一個目錄 Uri褒颈。
獲得目錄 Uri 后強(qiáng)烈建議馬上調(diào)用:
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
系統(tǒng)將保留此 URI,后續(xù)的訪問請求將返回 RESULT_OK
获高,且不會向用戶顯示確認(rèn)對話框哈肖。
訪問文件和文件夾
和傳統(tǒng)的訪問內(nèi)部儲存空間的方式不同,F(xiàn)ile 類不適用于作用域目錄訪問念秧,也不能簡單的拼接相對路徑獲得目標(biāo)文件或子文件夾淤井。
這時候需要 android.support.v4.*
包中的 DocumentFile 類,通過這段代碼:
DocumentFile.fromTreeUri(this, uri)
獲得申請到的作用域目錄 DocumentFile摊趾,使用方式和 File 對象相差不大币狠。
但需要注意的是,要獲取文件或文件夾(包括次級目錄)時砾层,應(yīng)當(dāng)使用 DocumentFile.findFile()
逐步獲得目標(biāo) DocumentFile漩绵。若運(yùn)行過程發(fā)現(xiàn)文件夾不存在,則需要用到 DocumentFile.createDirectory()
(請不要當(dāng)作 findDirectory 使用肛炮,它會創(chuàng)建全新的文件夾)止吐。
讀寫文件
得到了目標(biāo)文件 DocumentFile 后,getUri()
即可獲得 Uri 對象侨糟。
然后通過 ContentResolver 打開 FileDescriptor碍扔,便可創(chuàng)建 FileInputStream 文件輸入流或者 FileOutputStream 文件輸出流,進(jìn)行操作了秕重。
// 第二個參數(shù)為讀寫模式不同,"r" 為只讀,"w" 為寫入
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
總結(jié)
基本使用大概就這樣了,如果有什么建議或者錯誤歡迎提出糾正~
文章原文地址: https://feng.moe/archives/11/ 轉(zhuǎn)載前請標(biāo)注原作者和鏈接二拐,謝謝