Android 文件存储浅谈


图片来源于https://blog.csdn.net/wangsen927/article/details/115914821

1.内部存储

1.1内部存储简单认识

内部存储一般指data/data/包名/... 下的路径
image

有些人经常把内部存储和运行内存搞混,这完全是两个截然不同的东西。
运行内存(RAM(Random Access Memory))。用于存储应用运行时的各种对象和变量常量等,主要作用在于提高运行速度。是唯一一种断电后数据会清除的存储器。
(Read-Only Memory,ROM)。电源切断文件依然保留,PC端的硬盘和手机端文件存储都属于ROM。

假如一台Android机器拥有8G运行内存(RAM),128G手机存储(ROM)。那么8G就是运行内存大小,128G就是手机内部存储和外部存储加起来的总空间大小。

1.2 内部存储常用API

对于每个应用,系统都会在内部存储空间中提供目录,应用可以在该存储空间中整理其文件。一个目录专为应用的持久性文件而设计,而另一个目录包含应用的缓存文件。您的应用不需要任何系统权限即可读取和写入这些目录中的文件。

内部存储常用的两个文件目录可分为持久化文件目录和缓存文件目录。

1.2.1 持久化文件目录

持久化文件目录下文件访问

File file = new File(context.getFilesDir(), filename);

持久化文件目录下文件的写入

String filename = "myfile";
String fileContents = "Hello world!";
try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
    fos.write(fileContents.toByteArray());
}

如需允许其他应用访问存储在内部存储空间内此目录中的文件,请使用具有 FLAG_GRANT_READ_URI_PERMISSION 属性的 FileProvider。
getCacheDir() 方法

IO流读取持久化目录文件

FileInputStream fis = context.openFileInput(filename);
InputStreamReader inputStreamReader =
        new InputStreamReader(fis, StandardCharsets.UTF_8);
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
    String line = reader.readLine();
    while (line != null) {
        stringBuilder.append(line).append('\n');
        line = reader.readLine();
    }
} catch (IOException e) {
    // Error occurred when opening raw file for reading.
} finally {
    String contents = stringBuilder.toString();
}

注意:如果在安装时需要以信息流的形式访问文件,请将文件保存在项目的 /res/raw 目录中。您可以使用 openRawResource() 打开这些文件,传入带有 R.raw 前缀的文件名作为资源 ID。此方法将返回一个 InputStream,您可以使用它读取文件。您无法写入原始文件

1.2.2 内部缓存文件目录

创建缓存文件

File.createTempFile(filename, null, context.getCacheDir());

访问缓存文件

File cacheFile = new File(context.getCacheDir(), filename);

注意:当设备的内部存储空间不足时,Android 可能会删除这些缓存文件以回收空间。因此,请在读取前检查缓存文件是否存在。

2.外部存储

2.1外部存储简单认识

外部存储指的是storage/emulated/0 下的目录文件。下面我们从外部存储的路径和权限来了解一下外部存储。
image
如上图:
标注①处的路径和标注②处是同一路径。
②路径下的目录文件①下面同样有一份,但是并不是复制了一份,而是相当于软链接(可以把①处的路径当作②处路径的别名)。

外部存储可分为外部私有目录和外部公有目录。
③就是外部私有目录的路径。storage/emulated/0/Android/data/包名,其它可以称作是外部存储的公有目录。
1)Android4.4以前需要添加以下权限才能对外部存储进行读写操作。

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> 

2)Android4.4 - Android 6.0 不需要添加权限就能对外部存储的私有目录进行操作,对外部存储的公有目录还是必须申请。

(就是说如果我们自身的应用假如包名为com.wind.storage,那么在storage/emulated/0/Android/data/com/wind/storage/下的文件在4.4-6.0的系统上我们不必申请权限就可以访问。假如在我们的应用中想要访问其它应用私有目录下的文件或者公有目录下的文件,需要加上读写权限.)

3)Android6.0以上,访问外部存储下的目录文件时,不仅需要在清单文件中声明读写权限,还要对读写权限进行动态申请。访问自己应用私有目录下的文件不需要权限申请。

申请动态存储权限

    private void requestPermission() {
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                List<String> permissionsToRequire = new ArrayList<>();
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    permissionsToRequire.add(Manifest.permission.READ_EXTERNAL_STORAGE);
                }
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    permissionsToRequire.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
                }
                if (!permissionsToRequire.isEmpty()) {
                    ActivityCompat.requestPermissions(this, mPermissions, 0);
                }
            }
        }
    }

根据是否成功获取权限做出相应的动作,这里如果获取到权限就什么都不做,获取失败退出。

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 0) {
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "You must allow all the permissions.", Toast.LENGTH_SHORT).show();
                    finish();
                }
            }
        }
    }

4)Android10以上,Android采用了分区存储机制。应用只能访问自己的外部存储私有目录以及外部存储公有目录下的文件,而不能访问别的应用的外部存储私有目录。

目标SDK设置Android10以下时,/sdcard/Android/data/包名/ 路径下的文件在Android10上不同应用可以相互访问,在Android11上不能相互访问,会报错。
Android 6.0之前如果要访问外部存储目录下的文件只需要在清单文件上加上读写权限
Android 6.0之后如果要想访问外部存储目录下的文件不仅要在清单文件上加上权限,还要动态申请存储权限(Android6.0开始谷歌要求所有危险权限要动态申请,外部存储的读写权限属于危险权限)。

分区存储机制大大提高了Android存储的安全。有些开发者对外部存储的一些概念含糊不清,往往把应用本身的一些隐私数据放在外部存储的私有目录下面。而其它应用只需要获取申请读写权限,就可以随意读取这些隐私数据。分区存储完美地避免了此种情形。

2.2 外部存储常用API

2.2.1 创建或访问外部私有目录持久化文件
File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);
2.2.2 创建或访问外部私有目录缓存文件
File externalCacheFile = new File(context.getExternalCacheDir(), filename);

Android应用卸载时应用内部存储的私有目录和应用外部存储的私有目录下的文件都会被删除。

2.3 共享存储

外部存储公有目录的简单认识

Android 10之前可以直接用new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)+"/"+"文件名")这种方式来访问外部存储的公有目录。
从Android10开始由于开启了作用域存储不再支持以这种通过绝对路径的方式来访问外部存储的公有目录,Android系统针对文件类型进行了分类,图片、音频、视频这三类文件将可以通过MediaStore API来进行访问,而其他类型的文件则需要使用系统的文件选择器来进行访问。

具体关于作用域存储的适配:例如下载文件到外部存储公有目录,添加图片到图库等可参考博客:https://guolin.blog.csdn.net/article/details/105419420

参阅:https://developer.android.com/training/data-storage?hl=zh-cn

posted @ 2022-09-29 23:53  我的小鱼干嘞  阅读(777)  评论(0编辑  收藏  举报