Android Scoped storage 分區(qū)存儲

Android存儲目錄

  1. 內(nèi)部存儲
  • getFilesDir - 應(yīng)用內(nèi)部存儲 放在data/data/packagename/files/
  • getCacheDir - 應(yīng)用內(nèi)部存儲 放在data/data/packagename/cache/
  1. 外部存儲
  • getExternalFilesDir - 放在外部存儲mnt/sdcard/Android/data/packagename/files/ 外部存儲私有目錄
    • 應(yīng)用卸載就會刪除
    • 5.0及以上不需要WRITE_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE
    • 不安全剿配,別的應(yīng)用可以寫入數(shù)據(jù)到此目錄
    • Media掃描不出來铃慷,不會出現(xiàn)在相冊
  • getExternalCacheDir - 存放臨時緩存數(shù)據(jù) 放在外部存儲mnt/sdcard/Android/data/packagename/cache/

對應(yīng)設(shè)置選項設(shè)置->應(yīng)用->應(yīng)用詳情里面的"清除數(shù)據(jù)"與"清除緩存“選項"

  • getExternalStorageDirectory - 在sd卡目錄mnt/sdcard,外部存儲共享目錄 外部存儲中,除了私有目錄以外的目錄细疚,都是共享目錄。程序保存在共享目錄中的數(shù)據(jù)川梅,在應(yīng)用被刪除后疯兼,仍然保留。

Scoped storage in Android 10

在Android 10以前贫途,只要程序獲得了READ_EXTERNAL_STORAGE權(quán)限吧彪,就可以隨意讀取外部存儲的共享目錄;只要程序獲得了WRITE_EXTERNAL_STORAGE權(quán)限丢早,就可以隨意在外部存儲的共享目錄上新建文件夾或文件姨裸。

于是Google終于開始動手了,在Android 10中提出了分區(qū)存儲怨酝,意在限制程序?qū)ν獠看鎯χ泄蚕砟夸浀臑樗麨榭酢7謪^(qū)存儲對 內(nèi)部存儲目錄 和 外部存儲私有目錄 都沒有影響。

簡而言之农猬,在Android 10中赡艰,對于私有目錄的讀寫沒有變化,仍然可以使用File那一套斤葱,且不需要任何權(quán)限慷垮。而對于共享目錄的讀寫,則不能按照原來操作方法揍堕。

Android 11對共享目錄的訪問
共享目錄文件需要通過MediaStore API或者Storage Access Framework方式訪問换帜。

  • MediaStore API在共享目錄指定目錄下創(chuàng)建文件或者訪問文件自己創(chuàng)建文件,不需要申請存儲權(quán)限
  • MediaStore API訪問其他應(yīng)用在共享目錄創(chuàng)建的媒體文件(圖片鹤啡、音頻惯驼、視頻), 需要申請存儲權(quán)限递瑰,未申請存儲權(quán)限祟牲,通過ContentResolver查詢不到文件Uri,即使通過其他方式獲取到文件Uri抖部,讀取或創(chuàng)建文件會拋出異常
  • MediaStore API 目錄對應(yīng)系統(tǒng)文件的文件夾
    • MediaStore.Images --> DCIM/ 和 Pictures
    • MediaStore.Video --> DCIM/, Movies/, 和 Pictures/
    • MediaStore.Audio --> Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/ 和 Ringtones/
    • MediaStore.Downloads --> android 10新加目錄说贝,Download/
    • MediaStore.Files --> android 10之后可用。如果使用分區(qū)存儲慎颗,只包含當(dāng)前應(yīng)用的圖片乡恕,視頻言询,音頻,如果不使用分區(qū)存儲傲宜,則包含所有其它媒體類型
  • MediaStore API不能夠訪問其他應(yīng)用創(chuàng)建的非媒體文件(pdf运杭、office、doc函卒、txt等)辆憔, 只能夠通過Storage Access Framework方式訪問

Storage Access Framework 存儲訪問框架方式訪問

Android 4.4 就引入了存儲訪問框架 (SAF)。借助 SAF报嵌,用戶可輕松在其所有首選文檔存儲提供程序中瀏覽并打開文檔虱咧、圖像及其他文件。用戶可通過易用的標(biāo)準(zhǔn)界面锚国,以統(tǒng)一方式在所有應(yīng)用和提供程序中瀏覽文件腕巡,以及訪問最近使用的文件。

文檔和其它文件寫入外部共享存儲
不需要申請寫權(quán)限血筑,會彈出系統(tǒng)文件頁面逸雹,用戶手動創(chuàng)建文件

val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
            type = "text/plain"
            addCategory(Intent.CATEGORY_OPENABLE)
            intent.putExtra(Intent.EXTRA_TITLE, )
        }
 startActivityForResult(intent, REQUEST_CREATE_DOCUMENT)

Android 11 Scoped storage變更

  • 對于啟用了Scoped storage的應(yīng)用,如果有需要授權(quán)云挟,對話框發(fā)生變化,提示應(yīng)用正在請求訪問照片和媒體转质,以前包含文件
    [圖片上傳失敗...(image-7b0557-1592559787255)]
  • 在android 11上园欣,WRITE_EXTERNAL_STORAGEWRITE_MEDIA_STORAGE將不能提供相應(yīng)的訪問權(quán)限
  • 在 Android 11 上,應(yīng)用無法再訪問外部存儲設(shè)備中的任何其他應(yīng)用的私有目錄的文件

實例演練

  1. 寫入外部公有存儲休蟹,在Android 11上向外部存儲創(chuàng)建一個文件沸枯,通過傳統(tǒng)file path形式創(chuàng)建, 先授權(quán),不能創(chuàng)建成功赂弓。授權(quán)已經(jīng)不起作用
val file = File(Environment.getExternalStorageDirectory(), packageName)
        if (!file.exists()) {
            Log.d("ScopedStorageActivity", "create external file state:${file.mkdirs()}")
        }

create external file state:false
  1. 讀取外部共享存儲绑榴,android 11通過傳統(tǒng)方式讀取外部存儲
val file = File(Environment.getExternalStorageDirectory(), "test.txt")
Log.d("ScopedStorageActivity", "test read external file content:${file.readText()}")

Caused by: java.io.FileNotFoundException: /storage/emulated/0/test.txt: open failed: EACCES (Permission denied)

不管授權(quán)與否都會報錯,不能獲取文件盈魁。
可以采采用存儲訪問框架來讀取, 這樣需要用戶打開文件翔怎,來選擇
https://developer.android.com/guide/topics/providers/document-provider?hl=zh-cn

val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
            type = "application/*"
            // 我們需要使用ContentResolver.openFileDescriptor讀取數(shù)據(jù)
            addCategory(Intent.CATEGORY_OPENABLE)
        }
        startActivityForResult(intent, REQUEST_OPEN_DOCUMENT)

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            REQUEST_OPEN_DOCUMENT -> {
                if (resultCode == Activity.RESULT_OK) {
                    data?.data.also { documentUri ->
                        Log.d("ScopedStorageActivity", "fileDescriptor documentUri:$documentUri")
                    }
                }
            }
        }
    }

在onActivityResult回調(diào)中就能獲取到文件的uri,然后對其進行相應(yīng)的操作

  1. 通過MediaStore API 向共享存儲中寫入媒體文件,不需要申請權(quán)限
    MediaStore API
    ContentResolver API
val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.timg)
        val displayName = "${System.currentTimeMillis()}.png"
        val mimeType = "image/png"
        val compressFormat = Bitmap.CompressFormat.PNG

        val contentValues = ContentValues()
        contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
        contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
        val path = getAppPicturePath()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, path)
        } else {
            val fileDir = File(path)
            if (!fileDir.exists()) {
                fileDir.mkdir()
            }
            contentValues.put(MediaStore.MediaColumns.DATA, path)
        }

        val uri =
            contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
        uri?.also {
            val outputStream = contentResolver.openOutputStream(it)
            outputStream?.also { os ->
                bitmap.compress(compressFormat, 100, os)
                os.close()
                Toast.makeText(this, "添加圖片成功", Toast.LENGTH_SHORT).show()
            }
        }
  1. 通過MediaStore API 向共享存儲中讀取媒體文件杨耙,不需要申請權(quán)限
private fun readExternalFileByMediaStore() {
        var pathKey = ""
        var pathValue = ""
        pathKey = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            MediaStore.MediaColumns.DATA
        } else {
            MediaStore.MediaColumns.RELATIVE_PATH
        }
        // RELATIVE_PATH會在路徑的最后自動添加/
        pathValue = getAppPicturePath()
        val cursor = contentResolver.query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            null,
            if (pathKey.isEmpty()) {
                null
            } else {
                "$pathKey LIKE ?"
            },
            if (pathValue.isEmpty()) {
                null
            } else {
                arrayOf("%$pathValue%")
            },
            "${MediaStore.MediaColumns.DATE_ADDED} desc"
        )

        cursor?.also {
            while (it.moveToNext()) {
                val id = it.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
                val displayName = it.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME))
                val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
                Log.d("ScopedStorageActivity", "read external uri:$uri, name:$displayName")
                Toast.makeText(this, "$displayName", Toast.LENGTH_LONG).show()
            }
        }
        cursor?.close()
    }

然后通過uri可以得到Bitmap

val openFileDescriptor = contentResolver.openFileDescriptor(uri, "r")
        openFileDescriptor?.apply {
            val bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor)
            readImg.setImageBitmap(bitmap)
        }
        openFileDescriptor?.close()
  1. 如果要讀取其它應(yīng)用的媒體文件赤套,掃出應(yīng)用的相冊,就需要申請READ_EXTERNAL_STORAGE權(quán)限珊膜,然后通過MediaStorage API讀取

Scoped storage兼容

  • 對于通過filepath讀取比較重的應(yīng)用容握,requestLegacyExternalStorage = true 繼續(xù)保留,在android 10,維持原狀
  • android 11之后requestLegacyExternalStorage失效车柠,必須適配
  • 如果當(dāng)前應(yīng)用以兼容模式運行剔氏,覆蓋安裝后應(yīng)用仍然會以兼容模式運行塑猖,卸載重新安裝應(yīng)用才會以分區(qū)存儲模式運行
  • 文件遷移是將應(yīng)用共享目錄文件遷移到應(yīng)用私有目錄或者Android10要求的media集合目錄
  • android 11可以用傳統(tǒng)File path直接操作方式讀媒體文件,性能會有影響谈跛,底層還是要轉(zhuǎn)化成ContentResolver.openFileDescriptor讀取, 會比較耗時羊苟,官方建議用MediaStore
val file = File(
            Environment.getExternalStorageDirectory().absolutePath
                    + File.separator + Environment.DIRECTORY_PICTURES + File.separator + APP_FOLDER_NAME,
            "rebase.png")
        Log.d("ScopedStorageActivity", "${file.path}")
        showImage(Uri.fromFile(file))

官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市币旧,隨后出現(xiàn)的幾起案子践险,更是在濱河造成了極大的恐慌,老刑警劉巖吹菱,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巍虫,死亡現(xiàn)場離奇詭異,居然都是意外死亡鳍刷,警方通過查閱死者的電腦和手機占遥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來输瓜,“玉大人瓦胎,你說我怎么就攤上這事∮却В” “怎么了搔啊?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長北戏。 經(jīng)常有香客問我负芋,道長,這世上最難降的妖魔是什么嗜愈? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任旧蛾,我火速辦了婚禮,結(jié)果婚禮上蠕嫁,老公的妹妹穿的比我還像新娘而咆。我一直安慰自己甚亭,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凌外,像睡著了一般乐疆。 火紅的嫁衣襯著肌膚如雪骑脱。 梳的紋絲不亂的頭發(fā)上渴庆,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機與錄音纤壁,去河邊找鬼左刽。 笑死,一個胖子當(dāng)著我的面吹牛酌媒,可吹牛的內(nèi)容都是我干的欠痴。 我是一名探鬼主播迄靠,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼喇辽!你這毒婦竟也來了掌挚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤菩咨,失蹤者是張志新(化名)和其女友劉穎吠式,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抽米,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡特占,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了云茸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片是目。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖标捺,靈堂內(nèi)的尸體忽然破棺而出懊纳,到底是詐尸還是另有隱情,我是刑警寧澤亡容,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布嗤疯,位于F島的核電站,受9級特大地震影響闺兢,放射性物質(zhì)發(fā)生泄漏茂缚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一列敲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧戴而,春花似錦、人聲如沸催首。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽车猬。三九已至珠闰,卻和暖如春伏嗜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背八酒。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工画饥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抖甘,地道東北人薇宠。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親狱意。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354