Android 文件数据存储总结

数据存储

官网文档

存储空间分类

内部存储

  • 内部存储只能被自己的应用访问,无需权限申请,app卸载后数据会被删,内部存储缓存的文件在内部空间不足时会被删除
  • getFileDir() 获取内部存储文件目录。路径:/data/user/0/package_name/files
  • getFileDir() 获取内部缓存文件目录/data/user/0/package_name/cache

外部存储

  • 私有存储
    • 空间相对较大,App卸载后会被删除,无需权限申请,缓存目录当空间不足时会被删除
    • getExternalFilesDir() 获取SD卡私有目录,路径:/storage/emulated/0/Android/data/package_name/files
    • getExternalCacheDir() 获取SD卡私有缓存目录,路径/storage/emulated/0/Android/data/package_name/cache
  • 非私有存储
    • 在Android10以下版本,使用外部非私有存储,需要权限申请
    • 在Android10及以上版本,getExternalStorageDirectory()等api被废弃

分区存储

  • Android10以上将媒体文件文件按类型保存在公共目录上,可以使用 MediaStore 访问媒体文件

内部存储

获取内部存储信息

//获取所有文件名
fileList().forEach {
    
    
    Log.e("TAG", "内部存储文件:$it")
}

Log.e("TAG","内部文件存储路径:$filesDir")
Log.e("TAG","内部缓存文件存储路径:$cacheDir")

写入数据

val content = "hello 内部存储"
val fileName = "内部存储测试.txt"
val output = openFileOutput(fileName, MODE_PRIVATE)
output.write(content.toByteArray())
output.flush()
output.close()

读取数据

var content = ""
val fileName = "内部存储测试.txt"
val input = openFileInput(fileName)
val buffer = ByteArray(1024)
var len = 0

while ((input.read(buffer).also {
    
     len = it }) != -1) {
    
    
    val str = String(buffer, 0, len)
    content += str
}
input.close()
Log.e("TAG", "content: $content")

写入内部缓存数据

val content = "hello 内部缓存存储"
val fileName = "内部缓存存储测试.txt"
val file = File(cacheDir, fileName)
val output = FileOutputStream(file)
output.write(content.toByteArray())
output.flush()
output.close()

读取内部缓存数据

var content = ""
val fileName = "内部缓存存储测试.txt"
val file = File(cacheDir, fileName)
val input = FileInputStream(file)
val buffer = ByteArray(1024)
var len = 0
while ((input.read(buffer).also {
    
     len = it }) != -1) {
    
    
    val str = String(buffer, 0, len)
    content += str
}
input.close()
Log.e("TAG", "content: $content")

外部私有存储

获取外部私有存储信息

判断SD卡是否挂起

val ret = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED

写入外部私有存储

val content = "hello 外部存储"
val fileName = "外部存储测试.txt"

val file = File(getExternalFilesDir("测试"), fileName)
val output = FileOutputStream(file)
output.write(content.toByteArray())
output.flush()
output.close()

读取外部私有存储

var content = ""
val fileName = "外部存储测试.txt"
val file = File(getExternalFilesDir("测试"), fileName)
val input = FileInputStream(file)
val buffer = ByteArray(1024)
var len = 0
while ((input.read(buffer).also {
    
     len = it }) != -1) {
    
    
    val str = String(buffer, 0, len)
    content += str
}
input.close()
Log.e("TAG", "content:$content")

写入外部私有缓存目录

val content = "hello 外部缓存存储"
val fileName = "外部缓存存储测试.txt"

val file = File(externalCacheDir, fileName)
val output = FileOutputStream(file)
output.write(content.toByteArray())
output.flush()
output.close()

读取外部私有缓存目录

var content = ""
val fileName = "外部缓存存储测试.txt"
val file = File(externalCacheDir, fileName)
val input = FileInputStream(file)
val buffer = ByteArray(1024)
var len = 0
while ((input.read(buffer).also {
    
     len = it }) != -1) {
    
    
    val str = String(buffer, 0, len)
    content += str
}
input.close()
Log.e("TAG", "content:$content")

外部非私有存储

//在Android10以下使用

val content = "hello 外部非私有存储测试"
val fileName = "外部非私有存储测试.txt"
val file = File(Environment.getExternalStorageDirectory(), fileName)
val output = FileOutputStream(file)
output.write(content.toByteArray())
output.flush()
output.close()

分区存储

  • 开启分区存储后,我们访问媒体库的图片、音视频将会自动拥有读写权限,无需额外的权限申请;读取其他应用向媒体库贡献的图片、音视频,则必须申请READ_EXTERNAL权限_STORAGE

  • 读取SD卡上非图片、音视频类文件,需要使用手机系统内置的文件选择器

检查是否开启分区存储

fun isScopedStorage(): Boolean =
	Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()

保存数据

/**
     * 保存文档
     */
fun saveDocument() {
    
    
    if (isScopedStorage()) {
    
    
        val content = "Hello 分区存储,保存一些文本信息"
        val externalUri = MediaStore.Files.getContentUri("external")
        val dirName = "测试"
        val fileName = "Hello.txt"
        val path = Environment.DIRECTORY_DOWNLOADS + File.separator + dirName
        val contentValues = ContentValues().apply {
    
    
            put(MediaStore.Downloads.RELATIVE_PATH, path)
            put(MediaStore.Downloads.DISPLAY_NAME, fileName)
            put(MediaStore.Downloads.TITLE, fileName)
        }
        val uri: Uri? = contentResolver.insert(externalUri, contentValues)
        uri?.let {
    
    
            val output = contentResolver.openOutputStream(it)
            val bos = BufferedOutputStream(output)
            bos.write(content.toByteArray())
            bos.flush()
            bos.close()
            output?.close()
        }
    }
}

/**
     * 保存图片
     */
fun saveImage() {
    
    
    if (isScopedStorage()) {
    
    
        val imageName = "hello.jpg"
        val dirName = "图片测试"
        val path = Environment.DIRECTORY_PICTURES + File.separator + dirName
        val contentValue = ContentValues().apply {
    
    
            put(MediaStore.Images.ImageColumns.RELATIVE_PATH, path)
            put(MediaStore.Images.ImageColumns.DISPLAY_NAME, imageName)
            put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/jpg")
            put(MediaStore.Images.ImageColumns.TITLE, imageName)
        }
        val uri =
        contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValue)
        val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
        uri?.let {
    
    
            val output = contentResolver.openOutputStream(it)
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output)
            output?.close()
        }
    }
}

查询数据

if (isScopedStorage()) {
    
    
    val imageName = "hello.jpg"
    val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    val selection = MediaStore.Images.Media.DISPLAY_NAME + "=?"
    var args = arrayOf(imageName)
    val cursor = contentResolver.query(uri, null, selection, args, null)
    cursor?.let {
    
    
        if (it.moveToFirst()) {
    
    
            val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.Images.Media._ID))
            queryUri = ContentUris.withAppendedId(uri, id)
            binding.imageView.setImageURI(queryUri)
            Log.e("TAG", "查询成功: $queryUri")
        }
        it.close()
    }
}

删除数据

if (isScopedStorage()) {
    
    
    queryUri?.let {
    
    
        val row: Int = contentResolver.delete(it, null, null)
        Log.e("TAG", "删除成功: $row")
    }
}

更新数据

if (isScopedStorage()) {
    
    
    queryUri?.let {
    
    
        val contentValues = ContentValues().apply {
    
    
            put(MediaStore.Images.ImageColumns.DISPLAY_NAME, "hello_01.jpg")
        }
        val update: Int = contentResolver.update(it, contentValues, null, null)
        Log.e("TAG", "修改成功:$update")
    }
}

使用SAF(Storage Access Framework)

使用非图片、音视频的文件时,比如PDF文件,这时候不能使用MediaStore,只能使用文件选择器

val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "image/*"
startActivityForResult(intent, 222)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    
    
    super.onActivityResult(requestCode, resultCode, data)
    when (requestCode) {
    
    
        222 -> {
    
    
            if (resultCode == Activity.RESULT_OK && data != null) {
    
    
                val uri = data.data
                uri?.let {
    
    
                    binding.safImageView.setImageBitmap(
                        BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
                    )
                }
            }
        }
    }
}

获取SD卡所有权限

Android 11中强制启用Scoped Storage是为了更好地保护用户的隐私,以及提供更加安全的数据保护。对于绝大部分应用程序来说,使用MediaStore提供的API就已经可以满足大家的开发需求了。

拥有对整个SD卡的读写权限,在Android 11上被认为是一种非常危险的权限,同时也可能会对用户的数据安全造成比较大的影响。

对于这类危险程度比较高的权限,Google通常采用的做法是,使用Intent跳转到一个专门的授权页面,引导用户手动授权,比如悬浮窗,无障碍服务等。

没错,在Android 11中,如果你想要管理整个设备上的文件,也需要使用类似的技术。

在AndroidManifest文件中声明权限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.myapplication">

    <uses-permission
        android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />
</manifest>

在代码中开启权限

fun requestAllFilesAccessPermission() {
    
    
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || Environment.isExternalStorageManager()) {
    
    
        Toast.makeText(this,
                       "We can access all files on external storage now",
                       Toast.LENGTH_SHORT).show()
    } else {
    
    
        val builder = AlertDialog.Builder(this)
        .setTitle("Tip")
        .setMessage("We need permission to access all files on external storage")
        .setPositiveButton("OK") {
    
     _, _ ->
                                  val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
                                  startActivityForResult(intent, 333)
                                 }
        .setNegativeButton("Cancel", null)
        builder.show()
    }
}

权限开启成功后,可以自由操作SD卡文件了

感谢

https://juejin.cn/post/6991197604969185311#heading-5

https://guolin.blog.csdn.net/article/details/105419420

https://guolin.blog.csdn.net/article/details/113954552

https://juejin.cn/post/6844904063432130568

代码下载

Guess you like

Origin blog.csdn.net/qq_14876133/article/details/119383926