android 10 分區(qū)儲(chǔ)存適配

搜索文檔

以下代碼段使用 `[ACTION_OPEN_DOCUMENT]來(lái)搜索包含圖片文件的文檔提供程序:

{".3gp", "video/3gpp"},

  {".apk", "application/vnd.android.package-archive"},

  {".asf", "video/x-ms-asf"},

  {".avi", "video/x-msvideo"},

  {".bin", "application/octet-stream"},

  {".bmp", "image/bmp"},

  {".c", "text/plain"},

  {".class", "application/octet-stream"},

  {".conf", "text/plain"},

  {".cpp", "text/plain"},

  {".doc", "application/msword"},

  {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},

  {".xls", "application/vnd.ms-excel"},

  {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},

  {".exe", "application/octet-stream"},

  {".gif", "image/gif"},

  {".gtar", "application/x-gtar"},

  {".gz", "application/x-gzip"},

  {".h", "text/plain"},

  {".htm", "text/html"},

  {".html", "text/html"},

  {".jar", "application/java-archive"},

  {".java", "text/plain"},

  {".jpeg", "image/jpeg"},

  {".jpg", "image/jpeg"},

  {".js", "application/x-javascript"},

  {".log", "text/plain"},

  {".m3u", "audio/x-mpegurl"},

  {".m4a", "audio/mp4a-latm"},

  {".m4b", "audio/mp4a-latm"},

  {".m4p", "audio/mp4a-latm"},

  {".m4u", "video/vnd.mpegurl"},

  {".m4v", "video/x-m4v"},

  {".mov", "video/quicktime"},

  {".mp2", "audio/x-mpeg"},

  {".mp3", "audio/x-mpeg"},

  {".mp4", "video/mp4"},

  {".mpc", "application/vnd.mpohun.certificate"},

  {".mpe", "video/mpeg"},

  {".mpeg", "video/mpeg"},

  {".mpg", "video/mpeg"},

  {".mpg4", "video/mp4"},

  {".mpga", "audio/mpeg"},

  {".msg", "application/vnd.ms-outlook"},

  {".ogg", "audio/ogg"},

  {".pdf", "application/pdf"},

  {".png", "image/png"},

  {".pps", "application/vnd.ms-powerpoint"},

  {".ppt", "application/vnd.ms-powerpoint"},

  {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},

  {".prop", "text/plain"},

  {".rc", "text/plain"},

  {".rmvb", "audio/x-pn-realaudio"},

  {".rtf", "application/rtf"},

  {".sh", "text/plain"},

  {".tar", "application/x-tar"},

  {".tgz", "application/x-compressed"},

  {".txt", "text/plain"},

  {".wav", "audio/x-wav"},

  {".wma", "audio/x-ms-wma"},

  {".wmv", "audio/x-ms-wmv"},

  {".wps", "application/vnd.ms-works"},

  {".xml", "text/plain"},

  {".z", "application/x-compress"},

  {".zip", "application/x-zip-compressed"},

  {"", "*/*"}

  };
————————————————
版權(quán)聲明:本文為CSDN博主「Work_Times」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議圃阳,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明渤早。
原文鏈接:https://blog.csdn.net/qq_34099401/article/details/64439869
private const val READ_REQUEST_CODE: Int = 42
...
/**
 * Fires an intent to spin up the "file chooser" UI and select an image.
 */
fun performFileSearch() {

    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
    // browser.
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        // Filter to only show results that can be "opened", such as a
        // file (as opposed to a list of contacts or timezones)
        addCategory(Intent.CATEGORY_OPENABLE)

        // Filter to show only images, using the image MIME data type.
        // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
        // To search for all documents available via installed storage providers,
        // it would be "*/*".
        type = "image/*"
    }

    startActivityForResult(intent, READ_REQUEST_CODE)
}
  • 當(dāng)應(yīng)用觸發(fā) `[ACTION_OPEN_DOCUMENT] Intent 時(shí)胁孙,該 Intent 會(huì)啟動(dòng)選擇器,以顯示所有匹配的文檔提供程序袭异。
  • 在 Intent 中添加 `[CATEGORY_OPENABLE]類別可對(duì)結(jié)果進(jìn)行過濾焙糟,從而只顯示可打開的文檔(如圖片文件)。
  • intent.setType("image/*") 語(yǔ)句可做進(jìn)一步過濾晾捏,從而只顯示 MIME 數(shù)據(jù)類型為圖像的文檔蒿涎。

處理結(jié)果

當(dāng)用戶在選擇器中選擇文檔后,系統(tǒng)會(huì)調(diào)用 [onActivityResult()]惦辛。resultData參數(shù)包含指向所選文檔的 URI劳秋。您可以使用getData()` 提取該 URI。獲得 URI 后,您可以用它來(lái)檢索用戶所需文檔玻淑。例如:

override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {

    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
    // response to some other intent, and the code below shouldn't run at all.

    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        // The document selected by the user won't be returned in the intent.
        // Instead, a URI to that document will be contained in the return intent
        // provided to this method as a parameter.
        // Pull that URI using resultData.getData().
        resultData?.data?.also { uri ->
            Log.i(TAG, "Uri: $uri")
            showImage(uri)
        }
    }
}
fun dumpImageMetaData(uri: Uri) {

    // The query, since it only applies to a single document, will only return
    // one row. There's no need to filter, sort, or select fields, since we want
    // all fields for one document.
    val cursor: Cursor? = contentResolver.query( uri, null, null, null, null, null)

    cursor?.use {
        // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
        // "if there's anything to look at, look at it" conditionals.
        if (it.moveToFirst()) {

            // Note it's called "Display Name".  This is
            // provider-specific, and might not necessarily be the file name.
            val displayName: String =
                    it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
            Log.i(TAG, "Display Name: $displayName")

            val sizeIndex: Int = it.getColumnIndex(OpenableColumns.SIZE)
            // If the size is unknown, the value stored is null.  But since an
            // int can't be null in Java, the behavior is implementation-specific,
            // which is just a fancy term for "unpredictable".  So as
            // a rule, check if it's null before assigning to an int.  This will
            // happen often:  The storage API allows for remote files, whose
            // size might not be locally known.
            val size: String = if (!it.isNull(sizeIndex)) {
                // Technically the column stores an int, but cursor.getString()
                // will do the conversion automatically.
                it.getString(sizeIndex)
            } else {
                "Unknown"
            }
            Log.i(TAG, "Size: $size")
        }
    }
}

檢查文檔元數(shù)據(jù)

獲得文檔的 URI 后嗽冒,您可以訪問該文檔的元數(shù)據(jù)。以下代碼段用于獲取 URI 所指定文檔的元數(shù)據(jù)补履,并將其記入日志:

@Throws(IOException::class)
private fun getBitmapFromUri(uri: Uri): Bitmap {
    val parcelFileDescriptor: ParcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r")
    val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor
    val image: Bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor)
    parcelFileDescriptor.close()
    return image
}

請(qǐng)注意添坊,您不應(yīng)在界面線程上執(zhí)行此操作。請(qǐng)使用 [AsyncTask] 在后臺(tái)執(zhí)行此操作箫锤。打開位圖后贬蛙,您可以在ImageView` 中顯示該位圖。

獲取 InputStream

以下示例展示了如何從 URI 中獲取 [InputStream](https://developer.android.google.cn/reference/java/io/InputStream)谚攒。在此代碼段中阳准,系統(tǒng)會(huì)將文件行讀取到字符串中:

@Throws(IOException::class)
private fun readTextFromUri(uri: Uri): String {
    val stringBuilder = StringBuilder()
    contentResolver.openInputStream(uri)?.use { inputStream ->
        BufferedReader(InputStreamReader(inputStream)).use { reader ->
            var line: String? = reader.readLine()
            while (line != null) {
                stringBuilder.append(line)
                line = reader.readLine()
            }
        }
    }
    return stringBuilder.toString()
}

創(chuàng)建文檔

您的應(yīng)用可通過使用 `[ACTION_CREATE_DOCUMENT]Intent,在文檔提供程序中創(chuàng)建新文檔馏臭。如要?jiǎng)?chuàng)建文件野蝇,請(qǐng)為您的 Intent 提供 MIME 類型和文件名,然后使用唯一的請(qǐng)求代碼啟動(dòng)該 Intent括儒。系統(tǒng)會(huì)為您執(zhí)行其余操作:

private const val WRITE_REQUEST_CODE: Int = 43
private fun createFile(mimeType: String, fileName: String) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        // Filter to only show results that can be "opened", such as
        // a file (as opposed to a list of contacts or timezones).
        addCategory(Intent.CATEGORY_OPENABLE)

        // Create a file with the requested MIME type.
        type = mimeType
        putExtra(Intent.EXTRA_TITLE, fileName)
    }

    startActivityForResult(intent, WRITE_REQUEST_CODE)
}

創(chuàng)建新文檔后绕沈,您可以在 `[onActivityResult()] 中獲取該文檔的 URI,以便繼續(xù)向其寫入內(nèi)容塑崖。

刪除文檔

如果您獲得了文檔的 URI七冲,并且文檔的 [Document.COLUMN_FLAGS] 包含[SUPPORTS_DELETE],則便可刪除該文檔规婆。例如

DocumentsContract.deleteDocument(contentResolver, uri)

編輯文檔

您可以隨時(shí)使用 SAF 編輯文本文檔。以下代碼段會(huì)觸發(fā) [ACTION_OPEN_DOCUMENT]Intent 并使用CATEGORY_OPENABLE` 類別蝉稳,從而只顯示可打開的文檔抒蚜。它會(huì)進(jìn)一步過濾,從而只顯示文本文件:

private const val EDIT_REQUEST_CODE: Int = 44
/**
 * Open a file for writing and append some text to it.
 */
private fun editDocument() {
    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
    // file browser.
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        // Filter to only show results that can be "opened", such as a
        // file (as opposed to a list of contacts or timezones).
        addCategory(Intent.CATEGORY_OPENABLE)

        // Filter to show only text files.
        type = "text/plain"
    }

    startActivityForResult(intent, EDIT_REQUEST_CODE)
}

接下來(lái)耘戚,您可以從 onActivityResult()(請(qǐng)參閱[處理結(jié)果](https://developer.android.google.cn/guide/topics/providers/document-provider#results))調(diào)用代碼嗡髓,以執(zhí)行編輯操作。以下代碼段將從ContentResolver獲取FileOutputStream`收津。其默認(rèn)使用寫入模式饿这。最佳做法是請(qǐng)求獲得最少的所需訪問權(quán)限,因此如果您只需要寫入權(quán)限撞秋,請(qǐng)勿請(qǐng)求獲得讀取/寫入權(quán)限:

private fun alterDocument(uri: Uri) {
    try {
        contentResolver.openFileDescriptor(uri, "w")?.use {
            // use{} lets the document provider know you're done by automatically closing the stream
            FileOutputStream(it.fileDescriptor).use {
                it.write(
                    ("Overwritten by MyCloud at ${System.currentTimeMillis()}\n").toByteArray()
                )
            }
        }
    } catch (e: FileNotFoundException) {
        e.printStackTrace()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

保留權(quán)限
當(dāng)應(yīng)用打開文件進(jìn)行讀取或?qū)懭霑r(shí)长捧,系統(tǒng)會(huì)為其提供針對(duì)該文件的 URI 授權(quán),有效期直至用戶設(shè)備重啟吻贿。但假定您的應(yīng)用是圖像編輯應(yīng)用串结,而且您希望用戶能直接從應(yīng)用中訪問其編輯的最后 5 張圖像。如果用戶的設(shè)備已重啟,則您必須讓用戶回到系統(tǒng)選擇器以查找這些文件肌割,而這顯然不是理想的做法卧蜓。

為防止出現(xiàn)此情況,您可以保留系統(tǒng)向應(yīng)用授予的權(quán)限把敞。實(shí)際上弥奸,您的應(yīng)用是“獲取”了系統(tǒng)提供的 URI 持久授權(quán)。如此一來(lái)奋早,用戶便可通過您的應(yīng)用持續(xù)訪問文件其爵,即使設(shè)備已重啟也不受影響:

val takeFlags: Int = intent.flags and
        (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
// Check for the freshest data.
contentResolver.takePersistableUriPermission(uri, takeFlags)

官網(wǎng)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市伸蚯,隨后出現(xiàn)的幾起案子摩渺,更是在濱河造成了極大的恐慌,老刑警劉巖剂邮,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摇幻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡挥萌,警方通過查閱死者的電腦和手機(jī)绰姻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)引瀑,“玉大人狂芋,你說我怎么就攤上這事『┰裕” “怎么了帜矾?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)屑柔。 經(jīng)常有香客問我屡萤,道長(zhǎng),這世上最難降的妖魔是什么掸宛? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任死陆,我火速辦了婚禮,結(jié)果婚禮上唧瘾,老公的妹妹穿的比我還像新娘措译。我一直安慰自己,他們只是感情好饰序,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布领虹。 她就那樣靜靜地躺著,像睡著了一般菌羽。 火紅的嫁衣襯著肌膚如雪掠械。 梳的紋絲不亂的頭發(fā)上由缆,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音猾蒂,去河邊找鬼均唉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肚菠,可吹牛的內(nèi)容都是我干的舔箭。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼蚊逢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼层扶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起烙荷,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤镜会,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后终抽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戳表,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年昼伴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匾旭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡圃郊,死狀恐怖价涝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情持舆,我是刑警寧澤色瘩,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站吏廉,受9級(jí)特大地震影響泞遗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜席覆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汹买。 院中可真熱鬧佩伤,春花似錦、人聲如沸晦毙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)见妒。三九已至孤荣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盐股。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工钱豁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人疯汁。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓牲尺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親幌蚊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谤碳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361