android partition storage adaptation summary

1, Partitioned storage concept

Storage in Android can be divided into two categories: private storage and shared storage

1. Private storage: each application has its own private directory in the internal storage. Other applications can't see it and can't access it.
Address: / storage/emulated/0/Android/data / package name / files
The private directory stores the private files of the app, which will be deleted with the uninstall of the app.

2. Shared storage: in addition to private storage, everything else is recognized as shared storage, such as Downloads, Documents, Pictures, DCIM, Movies, Music, etc.
Address: / storage/emulated/0/Downloads(Pictures), etc
The files in the public directory will not be deleted with APP uninstallation.

Before partitioned storage, in some applications, even if the function is very simple, most of them do not need such broad permissions.
This makes some applications

  • 1. Occupy space indiscriminately: various files are scattered everywhere on the disk. After users uninstall the application, these abandoned "orphans" are stranded in place, homeless and occupy disk space, which will eventually lead to insufficient disk space
  • 2. Read user's data at will
  • 3. Read application data at will

Therefore - partitioned storage, also known as sandbox storage ~

Applications targeting Android 10 (API level 29) and higher are given partition access to external storage devices (i.e. partition storage) by default, so that users can better manage external storage files. If they do not meet the conditions, they will run in compatibility mode. As before, files can be stored directly according to the path.

On the other hand, the introduction of partitioned storage can better protect users' privacy. By default, for applications targeting Android 10 and later, their access rights are limited to external storage, that is, partitioned storage. Such applications can view the following types of files in external storage devices without requesting any storage related user rights:

Apply files in a specific external directory (accessed using getExternalFilesDir()).
Apply your own photos, videos, and audio (accessed through the MediaStore).

This means that when our app stores files on an external storage device (i.e. SD card), we need to first understand whether the data to be stored belongs to the app private or needs to be shared. If it is app private, it exists in the folder returned by getExternalFilesDir(), that is, Android/data / package name / files / folder; if it needs to be shared, we need to use the media library (MediaStore). It should be noted that access to shared media files under the partitioned storage model does not require storage permission, while the old storage model requires storage permission.

The following table summarizes how partitioned storage affects file access:

typepositionAccess the files generated by the application itselfAccess files generated by other applicationsAccess methodUninstall app delete file
External storagePhoto/ Video/ Audio/No permission requiredPermission READ_EXTERNAL_STORAGE requiredMediaStore Apino
External storageDownloadsNo permission requiredNo permission requiredLoad the system file selector through the storage access framework SAFno
External storageApply specific directoriesNo permission requiredNot directly accessiblegetExternalFilesDir() gets the file path belonging to the application itselfyes

Why fit?
Under the partitioned storage model, the public area of external storage devices is not accessible. If forced access, an error will be reported in the api for creating or reading and writing files
Under the partitioned storage model, the public area of external storage devices is not accessible. If forced access, an error will be reported in the api for creating or reading and writing files. For details, see the example of accessing the public area of SD card under the partitioned storage model.

Is there any way to turn off the partitioned storage model?
There are two ways:
The first is that the targetSdkVersion of the app is always lower than 29, which is unrealistic;
The second method is to set android:requestLegacyExternalStorage = "true" in the targetSdkVersion = 29 application , you can not start partitioned storage to make the previous files read normally, but targetSdkVersion = 30 doesn't work. You can force partitioned storage to be enabled. Of course, as a humanized android, it still leaves a small hand for developers. If you want to overwrite the installation, you can add android:preserveLegacyExternalStorage = "true" , temporarily shut down the partitioned storage so that developers can complete data migration. Why is it temporary? As long as you uninstall and reinstall, it will become invalid.
The following is a list of all situations encountered in partitioned storage. Let's start with the code:

      fun saveFile() {
        if (checkPermission()) {
            //getExternalStoragePublicDirectory is deprecated. Access to partitioned storage is not allowed after it is opened
            val filePath = Environment.getExternalStoragePublicDirectory("").toString() + "/test3.txt"
            val fw = FileWriter(filePath)
            fw.write("hello world")
            fw.close()
            showToast("File written successfully")
        }
    }

Operation by case:

1) targetSdkVersion = 28, read and write normally after operation.

2) targetSdkVersion = 29. The application is not deleted. The targetSdkVersion is modified from 28 to 29. The installation is overwritten. It can be read and written normally after operation.

3) targetSdkVersion = 29, delete the application, re run, read / write error, program crash (open failed: EACCES (Permission denied))

4) targetSdkVersion = 29, add android:requestLegacyExternalStorage = "true" (partition storage is not enabled), read and write normally without error

5) targetSdkVersion = 30, the application is not deleted, targetSdkVersion is modified from 29 to 30, read and write errors are reported, and the program crashes (open failed: EACCES (Permission denied))

6) targetSdkVersion = 30. The application is not deleted. targetSdkVersion is modified from 29 to 30. android:preserveLegacyExternalStorage = "true" is added. There is no error when reading and writing normally

7) targetSdkVersion = 30, delete the application, re run, read / write error, program crash (open failed: EACCES (Permission denied))

The performances of requestLegacyExternalStorage and preserveLegacyExternalStorage in different versions are summarized as follows:


With regard to targetSdkVersion, Google Play stipulates that since August this year, the target API of all newly launched applications, that is, targetSdkVersion, must be upgraded to more than 30. The requirement of updating the new version of existing applications will take effect from November. Leaving aside the provisions of Google Play, about minSdkVersion, compileSdkVersion and targetSdkVersion in Gradle For the specific role of, please refer to this blog:
https://blog.csdn.net/qq_23062979/article/details/81294550

2, Partition adaptation scheme

Partitioned storage needs to follow three principles:

  • Better file attributes: the system application knows what files belong to which app, making it easier for users to manage their own files. When the app is uninstalled, the content created by the application should not be retained unless the user wants to retain it.
  • User data security: when users download files, such as sensitive e-mail attachments, these files should not be visible to most applications
  • Application data security: when app writes application specific files to external storage, other applications should not see these files

1. File access regulations for application partition storage

(1) . application specific directory

Each application reads and writes files to its own private directory without reading and writing permissions.
Even if an application obtains read-write permission, it cannot access the private directory of other applications.
Specific path of private file directory: storage/emulated/0/android/data/packageName /

Access method:
this.getExternalMediaDirs() ==[/storage/emulated/0/Android/media/com.yoshin.tspsdk]
this.getExternalCacheDir() /storage/emulated/0/Android/data/com.yoshin.tspsdk/cache
this.getExternalFilesDir(Environment.DIRECTORY_SCREENSHOTS)/storage/emulated/0/Android/data/com.yoshin.tspsdk/files/Screenshots

When these methods are called, if there is no corresponding folder, they will be created.
Environment.DIRECTORY_SCREENSHOTS can be replaced by other parameters, and other folders will be accessed. Then, there will be exceptions in the lower version. This is not recommended.

(2) . shared catalog files

Shared directory files need to be accessed through MediaStore API or Storage Access Framework

(1) MediaStore API creates files in the specified directory of the shared directory or accesses the files created by the application itself without applying for any storage permission. The owner has the ownership of the files;
(2) MediaStore API accesses media files (pictures, audio, video) created by other applications in the shared directory,
You need to apply for storage permission. If you don't apply for storage permission, you can't query the file Uri through ContentResolver. Even if you get the file Uri through other methods, an exception will be thrown when reading or creating the file;
(3) MediaStore API cannot access non media files (pdf, office, doc, txt, etc.) created by other applications, but only through the Storage Access Framework.

2. MediaStore API introduction

The system will automatically scan external storage and add files to the set of Images, Videos, Audio files and Downloaded files defined by the system. Android 10 accesses shared directory file resources through MediaStore.Images, MediaStore.Video, MediaStore.Audio and MediaStore.Downloads

MediaStore API create file

MeidaStore API of Android 10 version only allows files to be created in the specified directory of the shared directory. If a file is created in a non specified directory, an IllegalArgumentException will be thrown. The created file directory is summarized as follows:

media typeUriDefault create directoryAllow storage of files
Imagecontent://media/external/images/mediaPicturesOnly pictures can be placed
Audiocontent://media/external/audio/mediaMusicAudio only
Videocontent://media/external/video/mediaMoviesVideo only
Downloadcontent://media/external/downloadsDownloadAny type

MediaStore.Downloads.EXTERNAL_CONTENT_URI is a new API in Android 10. It is used to create and access non media files. It should be noted that although it has obtained storage permission, it is still unable to delete and modify files of other applications.

MediaStore API file access

val external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    val projection = arrayOf(MediaStore.Images.Media._ID)
    val cursor = contentResolver.query(external, projection, null, null, null)
    if (cursor != null && cursor.moveToFirst()) {
        queryUri = ContentUris.withAppendedId(external, cursor.getLong(0))
        // queryUri is the corresponding uri in the figure above
        cursor.close()
    }

In versions below Android Q, you can use this method to get the absolute path of media files (such as external/DCIM/xxx.png), that is, the DATA field. However, in versions above Android Q, the DATA field is abandoned and no longer reliable, and relax is added_ The path field indicates the relative address, which can be used to set the storage location of media files (see below for details).

The following versions of Android Q can get the absolute path through the DATA field and convert it to File type to operate the File. It is no longer feasible after Android Q. The general method to access this uri is through the File descriptor FileDescriptor. The example code is as follows:

var pfd: ParcelFileDescriptor? = null
    try {
        pfd = contentResolver.openFileDescriptor(queryUri!!, "r")
        if (pfd != null) {
            val bitmap = BitmapFactory.decodeFileDescriptor(pfd.fileDescriptor)
            imageIv.setImageBitmap(bitmap)
        }
    } catch (e: IOException) {
        e.printStackTrace()
    } finally {
        pfd?.close()
    }

When reading the MedisStore file, if read is not requested_ EXTERNAL_ With storage permission, only the pictures saved by the application can be read. In other words, the application does not need to apply for read to read and operate the media files saved by itself_ EXTERNAL_ Storage permission, but you need to apply for permission to access media files created by other applications.

Only the DATA field is used below Android Q. Android Q and above do not use the DATA field. Instead, use RELATEIVE_PATH field.

3. Introduction to Storage Access Framework

As mentioned above, the MediaStore API cannot access non media files (pdf, office, doc, txt, etc.) created by other applications, but only through the Storage Access Framework.
Saf (storage access framework – Storage Access Framework) is used for file and directory access. The SAF access method does not need to apply for permission
Example code:

 Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
            intent.setType("*/*");//Set the type. I have any type and any suffix here. You can write it like this.
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            startActivityForResult(intent, READ_REQUEST_CODE);

4. Android Q uses ContentResolver to add, delete, modify and query files

1. Get (create) the folder under its own directory
Get and create. If there is no corresponding folder in the phone, it will be automatically generated

//Create apk folder in its own directory
File apkFile = context.getExternalFilesDir("apk");

2. Create files in your own directory
Generate the path to download, and read and write through the input / output stream

String apkFilePath = context.getExternalFilesDir("apk").getAbsolutePath();
File newFile = new File(apkFilePath + File.separator + "temp.apk");
OutputStream os = null;
try {
    os = new FileOutputStream(newFile);
    if (os != null) {
        os.write("file is created".getBytes(StandardCharsets.UTF_8));
        os.flush();
    }
} catch (IOException e) {
} finally {
    try {
        if (os != null) {
            os.close();
        }
    } catch (IOException e1) {
       
    }
}

3. Create and obtain the file path in the public directory
Write via MediaStore.insert

//The fileName here refers to the file name, excluding the path
//relativePath contains a subpath under a media
 private static Uri insertFileIntoMediaStore (String fileName, String fileType,String relativePath) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
       return null;
    }
    ContentResolver resolver = context.getContentResolver();
    //Set file parameters to ContentValues
    ContentValues values = new ContentValues();
    //Set file name
    values.put(MediaStore.Downloads.DISPLAY_NAME, fileName);
    //Set the file description. Here, take the file name as an example
    values.put(MediaStore.Downloads.DESCRIPTION, fileName);
    //set file type
    values.put(MediaStore.Downloads.MIME_TYPE,"application/vnd.android.package-archive");
    //Note RELATIVE_PATH requires targetVersion=29
    //Therefore, this method can only be executed on Android 10 mobile phones
    values.put(MediaStore.Downloads.RELATIVE_PATH, relativePath);
    //EXTERNAL_CONTENT_URI represents external storage
    Uri external = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
    //insertUri indicates the uri path where the file is saved
    Uri insertUri  = resolver.insert(external, values);
    return insertUri;
}

4. Create a file in the specified folder under the public directory
Combined with the above code, we mainly create files or folders in the public directory and get the local path URI. Different URIs can be saved to different public directories. Next, you can write to the file using the input / output stream

The following code only takes file copy storage as an example. sourcePath represents the address of the original file and copies the file to a new directory; File download can be directly downloaded locally without file copy.
Important: Android Q does not support file: / /, which can only be accessed through uri

private static void saveFile(Context context, Uri insertUri){
    if(insertUri == null) {
        return;
    }
    String mFilePath = insertUri.toString();
    InputStream is = null;
    OutputStream os = null;
    try {
        os = resolver.openOutputStream(insertUri);
        if(os == null){
            return;
        }
        int read;
        File sourceFile = new File(sourcePath);
        if (sourceFile.exists()) { // When file exists
           is = new FileInputStream(sourceFile); // Read in original file
           byte[] buffer = new byte[1024];
           while ((read = is.read(buffer)) != -1) {
               os.write(buffer, 0, read);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
       try {
           if (is != null) {
                is.close();
            }
            if (os != null) {
                os.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5. Use MediaStore to read files in the public directory
Select the corresponding opening method through the ContentResolver openFileDescriptor interface.
For example, "r" means read, "w" means write, and returns a file descriptor of type ParcelFileDescriptor.

private static Bitmap readUriToBitmap (Context context, Uri uri) {
    ParcelFileDescriptor parcelFileDescriptor = null;
    FileDescriptor fileDescriptor = null;
    Bitmap tagBitmap = null;
    try {
        parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r");    
        if (parcelFileDescriptor != null && parcelFileDescriptor.getFileDescriptor() != null) {
            fileDescriptor = parcelFileDescriptor.getFileDescriptor();
            //Convert uri to bitmap type
            tagBitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
           // You can do it~~
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (parcelFileDescriptor != null) {
                parcelFileDescriptor.close();
            }
        } catch (IOException e) {
        }
    }
}

6. Deleting files using MediaStore

public static void deleteFile (Context context, Uri fileUri) {
    context.getContentResolver().delete(fileUri, null, null);
}

3, All file access rights

Although so much has been said, some applications need to access all files, such as anti-virus software and file manager. Don't worry, there's a way! MANAGE_EXTERNAL_STORAGE is not coming. This permission is used to obtain the management permission of all files.

  <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
 
    val intent = Intent()
    intent.action= Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
    startActivity(intent)
 
    //Determine whether to obtain management_ EXTERNAL_ Storage permissions:
    val isHasStoragePermission= Environment.isExternalStorageManager()


If it is the application of file manager, backup and storage. You need to fill out a declaration form on the Google Play Developer Console to explain why you need MANAGE_EXTERNAL_STORAGE permission. After submitting, it will be reviewed whether to join the white list. Once you join successfully, your application can ask the user for permission. If the user also agrees to your application's access permission request, madiasto access will no longer be filtered, including non media library files. However, applications with this permission still cannot access these directories, which are displayed as subdirectories of Android/data / on the storage volume, that is, application specific directories belonging to other applications.

4, Example of error accessing SD card public area under partitioned storage model

FileOutputStream|FileInputStream

In the partitioned storage model, the public directory of SD card is not accessible except for the folders sharing media. Therefore, instantiating FileOutputStream or FileInputStream with the path of a public directory will report FileNotFoundException

java.io.FileNotFoundException: /storage/emulated/0/xxx/SharePic/1603277403193.jpg: open failed: ENOENT (No such fileor directory)
    at libcore.io.IoBridge.open(IoBridge.java:496)
    at java.io.FileOutputStream.<init>(FileOutputStream.java:235)
    at com.xxx.ui.QrCodeActivity.askSDCardSaveImgPermission(QrCodeActivity.java:242)
    ...
java.io.FileNotFoundException: /storage/emulated/0/xxx/data/testusf: open failed: EACCES (Permission denied)
 	at libcore.io.IoBridge.open(IoBridge.java:496)
 	at java.io.FileInputStream.<init>(FileInputStream.java:159)
 	at java.io.FileReader.<init>(FileReader.java:72)
 	at com.android.xxx.sdk.common.toolbox.FileUtils.readSingleLineStringFromFile(FileUtils.java:747)

5, File storage location selection

After the file storage specification is established, when a new business needs to establish a separate file directory, the storage location can be determined according to the following rules

6, Summary

  1. Application specific directories – > no permission required – > access method getExternalFilesDir() – > remove files when uninstalling applications
  2. Media collection (photos, videos, audio) – > permission read required_ EXTERNAL_ Storage (only when accessing files from other apps) – > access method MediaStore – > do not remove files when uninstalling apps
  3. Download content (documents and e-books) – > no permission required – > storage access framework (load the file selector of the system) – > do not remove files when uninstalling applications

Keywords: Android

Added by Michdd on Fri, 26 Nov 2021 17:29:40 +0200