Android saves pictures to local compatible Android 10+ and ScopedStorage (partitioned storage) introduction

Introduction to Scoped Storage

Derived from Scoped Storage

Android has long supported the function of external storage space, which is what we often call SD card storage. This function is widely used, almost all developers like to create a dedicated directory for their own applications in the root directory of the SD card to store various files and data. The file management of the SD card becomes extremely chaotic. Moreover, even if I uninstall a program that I no longer use at all, the junk files it generates may remain on my phone and will not be automatically deleted, which makes the user's storage space in a very tight state. , but also wastes a lot of storage resources.

In order to solve the above problems, Google has added a Scoped Storage partition storage mechanism to Android 10 .

Scoped Storage mechanism

To allow users to better manage their files and reduce clutter, apps targeting Android 10 (API level 29) and higher are given partitioned access to external storage by default (i.e. partitioned storage ). Such apps can only see the app-specific directories (accessed via context.getExternalFilesDir() ) and specific types of media files created by the app. If your application does not meet this condition, it will run in compatibility mode. The compatibility mode is the same as before, and files can be stored directly according to the path. However, it is very likely that it will not be available with the update of the SDK, so it is recommended to complete the adaptation of Scoped Storage as soon as possible.

Scoped Storage Features

The Scoped Storage mechanism is a security mechanism used to prevent applications from reading data of other applications; and has the following characteristics:

  1. Each application has its own storage space, that is, the  application-specific directory ;

  2. Applications cannot go through their own directories to access public directories;

  3. The data requested by the application must pass the permission detection, and it will not be released if it does not meet the requirements;

  4. Using the  MediaStore  related API allows you to access the shared storage space

Notice:

Set the compatibility mode by android:requestLegacyExternalStorage="true" , the above configuration is still valid in Android 11 , but only when the targetSdkVersion is less than or equal to 29 . If your targetSdkVersion >=30 , Scoped Storage will be enforced and the android:requestLegacyExternalStorage="true" flag will be ignored.

Android saves pictures to local (compatible with Android 10+)

The Android10+ system automatically scans external storage volumes and adds media files to the following well-defined collections:

  • Pictures (including photos and screenshots), stored in  DCIM/ and  Pictures/ directories. The system adds these files to the  MediaStore.Images  table.

Pictures are saved in the DCIM public directory, API<=28 versions use new File() to save files to the specified DCIM public directory, API>=29 use MediaStore to save files to the specified DCIM public directory;

The code to save the image to the DCIM public directory is as follows:

public class ImageSaveUtil {

    /**
     * 保存图片到公共目录DCIM
     * API<=28,需要提前申请文件读写权限
     * API>=29,不需要权限
     * 保存的文件在 DCIM 目录下
     *
     * @param context 上下文
     * @param bitmap  需要保存的bitmap
     * @param format  图片格式
     * @param quality 压缩的图片质量
     * @param recycle 完成以后,是否回收Bitmap,建议为true
     * @return 文件的 uri
     */
    @Nullable
    public static Uri saveAlbum(Context context, Bitmap bitmap, Bitmap.CompressFormat format, int quality, boolean recycle) {
        String suffix;
        if (Bitmap.CompressFormat.JPEG == format)
            suffix = "JPG";
        else
            suffix = format.name();
        String fileName = System.currentTimeMillis() + "_" + quality + "." + suffix;
        if (Build.VERSION.SDK_INT < 29) {
            if (!isGranted(context)) {
                Log.e("ImageUtils", "save to album need storage permission");
                return null;
            }
            File picDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
            File destFile = new File(picDir, fileName);
            if (!save(bitmap, destFile, format, quality, recycle))
                return null;
            Uri uri = null;
            if (destFile.exists()) {
                uri = Uri.parse("file://" + destFile.getAbsolutePath());
                Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                intent.setData(uri);
                context.sendBroadcast(intent);
            }
            return uri;
        } else {
            // Android 10 使用
            Uri contentUri;
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else
                contentUri = MediaStore.Images.Media.INTERNAL_CONTENT_URI;
            ContentValues contentValues = new ContentValues();
            contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
            contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
            contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/");
            // 告诉系统,文件还未准备好,暂时不对外暴露
            contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1);
            Uri uri = context.getContentResolver().insert(contentUri, contentValues);
            if (uri == null) return null;
            OutputStream os = null;
            try {
                os = context.getContentResolver().openOutputStream(uri);
                bitmap.compress(format, quality, os);
                // 告诉系统,文件准备好了,可以提供给外部了
                contentValues.clear();
                contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0);
                context.getContentResolver().update(uri, contentValues, null, null);
                return uri;
            } catch (Exception e) {
                e.printStackTrace();
                // 失败的时候,删除此 uri 记录
                context.getContentResolver().delete(uri, null, null);
                return null;
            } finally {
                try {
                    if (os != null)
                        os.close();
                } catch (IOException e) {
                    // ignore
                }
            }
        }
    }

    private static boolean save(Bitmap bitmap, File file, Bitmap.CompressFormat format, int quality, boolean recycle) {
        if (isEmptyBitmap(bitmap)) {
            Log.e("ImageUtils", "bitmap is empty.");
            return false;
        }
        if (bitmap.isRecycled()) {
            Log.e("ImageUtils", "bitmap is recycled.");
            return false;
        }
        if (!createFile(file, true)) {
            Log.e("ImageUtils", "create or delete file <$file> failed.");
            return false;
        }
        OutputStream os = null;
        boolean ret = false;
        try {
            os = new BufferedOutputStream(new FileOutputStream(file));
            ret = bitmap.compress(format, quality, os);
            if (recycle && !bitmap.isRecycled()) bitmap.recycle();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null)
                    os.close();
            } catch (IOException e) {
                // ignore
            }
        }
        return ret;
    }

    private static boolean isEmptyBitmap(Bitmap bitmap) {
        return bitmap == null || bitmap.isRecycled() || bitmap.getWidth() == 0 || bitmap.getHeight() == 0;
    }

    private static boolean createFile(File file, boolean isDeleteOldFile) {
        if (file == null) return false;
        if (file.exists()) {
            if (isDeleteOldFile) {
                if (!file.delete()) return false;
            } else
                return file.isFile();
        }
        if (!createDir(file.getParentFile())) return false;
        try {
            return file.createNewFile();
        } catch (IOException e) {
            return false;
        }
    }

    private static boolean createDir(File file) {
        if (file == null) return false;
        if (file.exists())
            return file.isDirectory();
        else
            return file.mkdirs();
    }

    private static boolean isGranted(Context context) {
        return (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE));
    }
}

refer to

Introduction to Android 10's ScopedStorage (partitioned storage) - Kawaii Loli's Blog - CSDN Blog

Android saves pictures locally compatible with Android 10+_OneGreenHand's Blog-CSDN Blog_android saves pictures locally

Android 10 (Android10\API29) save pictures to album DCIM/Camera - Programmer Sought

Data and File Storage Overview | Android Developers | Android Developers

Guess you like

Origin blog.csdn.net/ahou2468/article/details/123208065