Android 应用数据和文件

Android应用数据的保存方式有四种,分别是应用专属存储空间共享存储偏好设置数据库

应用专属存储空间

应用专属存储空间:存放应用专属文件,主要包括两个空间,卸载后移除

  • 内部存储空间:位于系统内部,通常情况下其他应用无法访问,空间较小,写入前应查询可用空间

    • 内部持久文件目录:/data/user/0/com.xxx/files

      • 获取方法:context.getFilesDir()

      • 可使用java.io.File创建和访问文件

      • 查看文件列表

        1
        Array<String> files = context.fileList();
      • 流操作:

        • 使用信息流存储文件
        1
        2
        3
        4
        5
        String filename = "myfile";
        String fileContents = "Hello world!";
        try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
        fos.write(fileContents.toByteArray());
        }
        • 使用信息流读取文件
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        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();
        }
    • 内部缓存文件目录:/data/user/0/com.xxx/cache

      • 获取方法:context.getCacheDir()

      • 可使用java.io.File访问文件

      • 创建文件:

        1
        File.createTempFile(filename, null, context.getCacheDir());
      • 移除文件:

        • 对缓存文件:cacheFile.delete();
        • 对应用上下文:context.deleteFile(cacheFileName);
        • 若系统空间不足,缓存文件有被系统直接删除的可能
  • 外部存储空间:通常指的是用户使用文件管理器可直接管理的那部分空间,也包含使用SD卡拓展的空间。

    • 物理卷

      • 通常情况下,Android手机的外部存储空间总是有一个物理卷,所以一般不用进行可用性验证。验证方法:

        1
        Environment.getExternalStorageState()		//如果返回的状态为 Environment.MEDIA_MOUNTED,则可以读写
      • 由于外部存储空间的物理卷可能不止一个(插入SD卡的情况),需要选择用于应用专属存储空间的物理卷。一般情况下,统一文件写入主外部存储卷。获取主外部存储卷路径的方法:

        1
        2
        File[] externalStorageVolumes = ContextCompat.getExternalFilesDirs(getApplicationContext(), null);	//物理卷数组
        File primaryExternalStorage = externalStorageVolumes[0];
    • 可使用java.io.File创建和访问文件

    • 持久文件目录:/storage/emulated/0/Android/data/com.xxx/files

      获取方法:

      1
      context.getExternalFilesDir(null);  //null 可由 android.os.Environment 下的 预定义子目录名称进行替换,以为保存特定媒体文件进行分类
    • 缓存文件目录:/storage/emulated/0/Android/data/com.xxx/cache

      • 获取方法:context.getExternalCacheDir()
      • 删除方法:externalCacheFile.delete();

共享存储

共享存储:包括媒体、文档和其他文件

  • 媒体:MediaStore

    • 分区存储

      • 说明:以Android 10(targetApi = 29)以及更高版本为目标的应用默认情况下仅有对外部存储空间分区访问权限,应用只能访问外部存储空间**私有目录/storage/emulated/0/Android/data/com.xxx/files)和自己创建的文件**,其他文件无法直接访问。

      • 使用分区存储后,访问外部存储空间公有目录的方法:

        • 使用MediaStore提供的API创建和访问图片、视频、音频资源

          • 示例1:将bitmap图片保存到公有目录Pictures文件夹下

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            Bitmap bitmap =  ((BitmapDrawable) img.getDrawable()).getBitmap();
            ContentValues contentValues = new ContentValues();
            contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, System.currentTimeMillis());
            contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
            //指定Pictures文件夹下的二级文件夹名称
            String path = String.format("%s/GalleryApplication/", Environment.DIRECTORY_PICTURES);

            contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, path);

            Uri uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
            if(uri != null) {
            try {
            OutputStream outputStream = contentResolver.openOutputStream(uri);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
            outputStream.close();
            Toast.makeText(MainActivity.this, "添加图片成功", Toast.LENGTH_SHORT).show();
            } catch (IOException e) {
            }
            }
          • 示例2:获取公有目录Pictures文件夹下的图片

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            Cursor cursor = contentResolver.query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            null,
            null,
            null,
            MediaStore.MediaColumns.DATE_ADDED + " desc"
            );
            if (cursor != null) {
            while (cursor.moveToNext()) {
            long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
            String displayName = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));
            Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
            Log.d("wangxiang", "displayName: " + displayName); //文件名
            Log.d("wangxiang", "uri: " + uri); //uri
            }
            cursor.close();
            }
            • 注意:在没有获取读取存储空间的权限READ_EXTERNAL_STORAGE的情况下,只能获取到应用自己创建的图片
        • 使用MediaStore提供的API下载文件到Download目录

        • 使用SAF(存储访问框架)创建和访问其它任意类型的资源

  • 文档和其他文件(媒体文件亦可):使用存储访问框架

    - 创建新文件:使用[`ACTION_CREATE_DOCUMENT`](https://developer.android.com/reference/android/content/Intent?hl=zh-cn#ACTION_CREATE_DOCUMENT) intent 操作
    
         - 示例:另存bitmap图片到手机内存
    
           
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("image/*");
    intent.putExtra(Intent.EXTRA_TITLE, System.currentTimeMillis() + ".png");
    startActivityForResult(intent, 666);

    //startActivityForResult回调时调用
    Uri uri = data.getData();
    try {
    OutputStream outputStream = getContentResolver().openOutputStream(uri); //只选中了目录,数据为空
    Bitmap bitmap = ((BitmapDrawable) img.getDrawable()).getBitmap();
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
    outputStream.close();
    Toast.makeText(MainActivity.this, "另存图片成功", Toast.LENGTH_SHORT).show();
    } catch (IOException e) {
    Log.e("wangxiang", "Exception: " + e.getMessage());
    }
    • 打开文件:ACTION_OPEN_DOCUMENT intent 操作

      • 示例:使用SAF选择图片并打开

        1
        2
        3
        4
        5
        6
        7
        8
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("image/*");
        startActivityForResult(intent, 777);

        //startActivityForResult回调时调用
        Uri uri = data.getData();
        Glide.with(this).load(uri).into(img);

偏好设置 - 未完待续

数据库 - 未完待续

总结

Android文件操作一共可以分多少种?

不管分多少种,在本地环境下,除应用私有目录外,Android 11 以后只能获取到uri,逐渐地对文件的操作也就转化成对uri的操作。

对获取到的uri,如何进行文件操作?

使用uri获取输出流outputstream,将其转化为输入流inputstream,再将文件保存至私有目录,就可对其进行传统的文件操作。

对获取到的uri,怎么进行文件上传?

可以通过获取到的流,进行操作。

  • 对Android 11 APP 、Android 11 设备 来说:

    1. 存储空间权限更名为文件和媒体权限。
    2. 只请求READ_EXTERNAL_STORAGE权限时,相当于只获取了媒体库权限只能读写媒体库公有目录下的文件、私有目录下的文件和使用SAF获取到的有权限访问的目录
    3. WRITE_EXTERNAL_STORAGE权限被弃用,要想直接对内部存储空间读写,需要申请所有文件访问权限MANAGE_EXTERNAL_STORAGE
  • 对Android 10 APP(未启用分区存储)、Android 11 设备来说:

    1. 存储权限更名为文件和媒体权限。
    2. 只请求READ_EXTERNAL_STORAGE权限,也相当于请求了Android 11 上的 MANAGE_EXTERNAL_STORAGE权限。
  • 对于Android 10 APP(未启用分区存储)、Android 10 及以下设备来说:权限处理同 Android 9 及以下设备。

  • 对于Android 10 APP(启用分区存储)、Android 10 及以上设备来说:

    1. WRITE_EXTERNAL_STORAGE权限无用,要想直接对内部存储空间读写,需关闭分区存储
    2. 请求READ_EXTERNAL_STORAGE权限时,相当于只获取了媒体库权限只能读写媒体库公有目录下的文件、私有目录下的文件和使用SAF获取到的有权限访问的目录