Android分区存储
1、分區(qū)存儲(chǔ)概述
分區(qū)存儲(chǔ)是Android 10開始引進(jìn)的Android系統(tǒng)存儲(chǔ)管理機(jī)制,它允許App讀取和寫入App自身創(chuàng)建的文件而不需要任何存儲(chǔ)權(quán)限。其中根據(jù)存儲(chǔ)位置的不同,可以分為內(nèi)部內(nèi)部存儲(chǔ)和外部存儲(chǔ)。內(nèi)部存儲(chǔ)就不用多說了,而外部存儲(chǔ)又分為私有空間和公共空間。私有存儲(chǔ)空間位置是/sdcard/Android/data/包名,而公共空間則是相冊、下載等。對(duì)我們開發(fā)者影響最大的就是對(duì)于公共存儲(chǔ)空間的讀寫了,總結(jié)如下:
Android分區(qū)存儲(chǔ)機(jī)制其實(shí)挺好的,讓很多軟件不能為所欲為,至少提高了“犯罪成本”。就是來得晚了一些,導(dǎo)致我們開發(fā)者要各種兼容…
2、讀取
以讀取相冊(DCIM)為例
val uris = ArrayList<Uri>() contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null,null, "${MediaStore.MediaColumns.DATE_ADDED} desc")?.use {// 這里的it是一個(gè)Cursorwhile (it.moveToNext()) {val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)uris.add(uri)} }這個(gè)contextResolver就是是Context.getContentResolver(),Actvity是Context子類,所以用kotlin就可以直接簡寫。
這樣就拿到了相冊里面的圖片的Uri,它是“content://”形式的uri:
這種讀取形式在低版本也是可用的,但是需要READ權(quán)限。在Android 10和11中,如果有READ權(quán)限,則可以讀取到所有的圖片文件的Uri,否則只能讀取到App本身創(chuàng)建的文件Uri。
在拿到Uri后,我們可以通過流的形式讀取它:
如果是用于展示圖片,可以使用Glide等開源框架,它們本身就支持加載Uri。
3、寫入
同樣以寫入相冊(DCIM)為例:
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.girl) val values = ContentValues() values.put(MediaStore.MediaColumns.DISPLAY_NAME, "test.png") values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // RELATIVE_PATH需要API 29values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM) } else {values.put(MediaStore.MediaColumns.DATA, "${Environment.getExternalStorageDirectory().path}/${Environment.DIRECTORY_DCIM}/test.png") } contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)?.also { uri ->contentResolver.openOutputStream(uri)?.use { os ->bitmap.compress(Bitmap.CompressFormat.PNG, 100, os)} }同樣地,在低版本寫入需要WRITE權(quán)限,在10和11上不需要。但是如果這個(gè)要修改其他App創(chuàng)建的文件,就需要寫成這樣
private var uri: Uri? = null // 通過某些操作獲取這個(gè)uri并賦值 private fun change() {val temp = uri ?: returncontentResolver.openInputStream(temp)?.use { val bitmap = BitmapFactory.decodeStream(it)val values = ContentValues()values.put(MediaStore.MediaColumns.DISPLAY_NAME, "test.png")values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // RELATIVE_PATH需要API 29values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)} else {values.put(MediaStore.MediaColumns.DATA, "${Environment.getExternalStorageDirectory().path}/${Environment.DIRECTORY_DCIM}/test.png")}try {contentResolver.openOutputStream(temp)?.use { os ->bitmap.compress(Bitmap.CompressFormat.PNG, 80, os)Toast.makeText(this@MainActivity, "OK", Toast.LENGTH_SHORT).show()}} catch (e: Exception) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && e is RecoverableSecurityException) {startIntentSenderForResult(e.userAction.actionIntent.intentSender, 10086, null, 0, 0, 0)} else {throw e}}} }override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if(requestCode == 10086 && resultCode == RESULT_OK) {change()} }會(huì)彈出這樣的彈窗
如果是對(duì)多個(gè)文件進(jìn)行寫入,在Android 11上可以這樣寫:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {val request = MediaStore.createWriteRequest(contentResolver, listOf(uri1, uri2))startIntentSenderForResult(request.intentSender, 10086, null, 0, 0, 0) }值得一提的是,用戶如果卸載了App后再重新安裝,即使是卸載前App自身創(chuàng)建的文件也需要相關(guān)權(quán)限。也就是說卸載重裝之后,“同一個(gè)App”其實(shí)在系統(tǒng)眼里不是同一個(gè)App。
3、管理存儲(chǔ)的權(quán)限
分區(qū)存儲(chǔ)機(jī)制很好地規(guī)范了Android App的存儲(chǔ)行為,讓它們讀自己該讀的,寫自己該寫的。但是有的應(yīng)用天生就需要對(duì)SD卡進(jìn)行全方位的訪問,比如各種文件瀏覽器、垃圾清理軟件等等,雖然很多所謂的垃圾清理軟件本身就是最該被清理的垃圾…對(duì)此,Android 11引入了一個(gè)新的權(quán)限:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />有了這個(gè)權(quán)限,就可以跟以前的版本一樣隨意玩耍了。那么是不是可以直接申請(qǐng)這個(gè)權(quán)限就可以了呢?機(jī)智如我,是可以的,不過應(yīng)用市場不讓上架…所以大部分App是不允許使用這個(gè)權(quán)限的。如果要申請(qǐng)此權(quán)限,需要打開設(shè)置界面,讓用戶手動(dòng)設(shè)置
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) startActivityForResult(intent, 10010)如果在manifest中添加了requestLegacyExternalStorage屬性,還可以加上包名
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) intent.data = Uri.parse("package:$packageName") startActivityForResult(intent, 10010)出現(xiàn)的界面長這樣
總結(jié)
以上是生活随笔為你收集整理的Android分区存储的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaWeb QQ邮箱找回密码
- 下一篇: 用计算机时按错了按什么键恢复出厂设置,电