preface

The biggest change in Android Q is the further protection of user privacy. One feature that Android users (especially domestic users) will appreciate is Scoped Storage. So far, Google has released the fourth beta version of Android Q (QPP4), and many developers are already adapting. Recently, in order not to be in a hurry at the end of the year, I also began to prepare Q adaptation. At present, there are many articles about Scoped Storage adaptation, but I think most of them are too general and lack of practical operation guide. Therefore, the author decided to combine the existing articles, their own practical action to step on the pit, summed up some practical adaptation skills.

The purpose of this article is not to write a comprehensive adaptation guide, but to supplement some of the shortcomings of the existing adaptation articles, and give some personal proven adaptation techniques for Scoped Storage.

Everything about Scoped Storage on Android Q is verified on an emulator on AndroidStudio QPP4.

About the Scoped Storage

Before we begin, a brief description of Scoped Storage. To understand why Google introduced this feature, you just need to pick up any Android phone and open the file manager:

Does that make sense now? Before Q, any APP, once granted external permission (WRITE_EXTERNAL_STORAGE), could create folders in the root directory of your internal storage. As a result, almost every Android user’s internal storage was like a garbage can. Most of us have experienced the pain of trying to locate one of our documents in a pile of folders.

Google has introduced Scoped Storage to prevent apps from creating folders all over the place. And it’s taking a hard line. Whether you set your targetSDK to 29 or not, as long as it’s running on Q, Scoped Storage is enforced. So after the release of the second beta, many users found that many apps, including wechat’s media selector, were down. But it didn’t take long for Google to relent, and in beta3 they relaxed their policy, saying they would give everyone some time to adapt, but not next year when Android R is released.

So far, the applicable strategies for Scoped Storage are as follows:

  • TargetSDK = 29, Scoped Storage is enabled by default, but can be added in manifestrequestLegacyExternalStorage = trueClose;
  • TargetSDK < 29, Scoped Storage is not enabled by default, but can be added in manifestrequestLegacyExternalStorage = falseOpen;

Two things to note:

  1. When you have targetSDK < 29 and want to passrequestLegacyExternalStorageTo open the Scoped Storage policy, you need to putcompileSdkVersionUpgrade to 29, or the compiler will fail. Alternatively, it can be passed at run timeEnvironment.isExternalStorageLegacy()Check whether the Scoped Storage policy is enabled.
  2. When modifying therequestLegacyExternalStorageProperty, you must uninstall the old APK and reinstall it to take effect.

Next, we compare some behavior changes before and after Scoped Storage policy is applied through practical examples.

Adapter tips

1. External.getexternalstoragedirectory (), getExternalStoragePublicDirectory () to read and write permissions

Previously, as long as you had access to external storage, you could build your own directory structure for internal storage by doing the following:

    File dir = new File(Environment.getExternalStorageDirectory(), "my_dir");
    if(! dir.exists()){ dir.mkdir(); }Copy the code

But when Scoped Storage is introduced, the above code won’t work at all, and the APP won’t be able to create folders anymore.

Java File API, bitmapFactory.decodefile () cannot read or write to places outside the app-specific directory

  • App-specific directory: that is, the directory returned by context.getexternalfilesdir ()/storage/emulated/0/Android/data/<package name>/files/, this is a private directory belonging to APP. You do not need to apply for permission for reading and writing in this directory. When the APP is uninstalled, the system will clear this directory. It is worth mentioning that before Q, other apps with external storage permission could actually read and write this directory, but from Q onwards, this behavior was prohibited.

When you get a file path outside of an app-specific directory, you might do something like this: pass the file path to FileOutputStream or FileWriter and start reading and writing. Or if the file is an image, you can retrieve the Bitmap object via bitmapFactory.decodefile ().

For example, I’ve seen this on projects: get the path of the image through the DATA field in the MediaStore API, and then get the Bitmap object through bitmapFactory.decodefile ().

This is fine as long as you have access to external storage. But after Scoped Storage, these behaviors were banned. Google recommends using FileDescriptor as follows:

    ContentResolver cr = context.getContentResolver();
    ParcelFileDescriptor fd = cr.openFileDescriptor(captureUri, "r");
    
    // Now you can read and write
    FileInputStream istream = new FileInputStream(fd.getFileDescriptor());/ / read
    FileOutputStream ostream = new FileOutputStream(fd.getFileDescriptor());/ / write
    
    // In the case of images, you can do this
    Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor());
Copy the code

By the way, media. DATA is also quoted on the Scoped Storage page:

Don’t load media files using the deprecated DATA columns.

As you may have noticed, all of the above operations must be performed on the premise of obtaining the Uri of the file. There are many ways to obtain the Uri of the file, which will not be discussed here. All you need to know is that you can no longer use file paths to work with files outside the app-specific directory.

3. Files generated by APP can only be written to disk through MediaStore API

As mentioned earlier, you can no longer read or write locations outside the app-specific directory directly from the file path. You might say, well, why don’t I just go to app-specific, not to mention ask for storage permission, and not worry about other apps snooping on the file? Yes, Google does recommend doing this, but not all data fits in here. If your APP is an image or video application, images and videos generated in the process of use are not suitable for app-specific. Firstly, the directory path is too deep for users to find, and secondly, this kind of data users do not want to be deleted with the application uninstall. So you have to look for places outside of the app-specific directory. But as mentioned earlier, you must have urIs to read and write, which is where the MediaStore API comes in. Here’s an example of creating an image:

    ContentValues contentValues = new ContentValues();
    contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
    contentValues.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
    contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
    Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
Copy the code

So at this point you have a Uri, and then you can write the FileDescriptor the way I mentioned above. The problem with this is that when you insert a record into MediaStore, the Uri may be retrieved by other applications, but you may not be able to find the file for that record (because your file may not have been written yet), and Google has a solution for this problem.

Another more common example — calling the camera to take and store photos, which is a best practice in training on Android Developer — is storing photos in an app-specific directory, In the real business we’re more likely to keep it outside of the app-specific directory, which is possible if you have external Storage privileges, but under Scoped Storage you have to generate the Uri of the photo via the MediaStore API. And then through the following statements to Intent takePictureIntent. PutExtra (MediaStore. EXTRA_OUTPUT photoURI);

Then you might have two questions:

  • Question 1

Create a Uri through MediaStore above, we do not specify a file path (MediaStore. Images. Media. The DATA), which will save the file to?

System will automatically help you deposit by category to the appropriate folder, by default in the Environment. The getExternalStoragePublicDirectory (Environment. DIRECTORY_XXXX) under the path of return, For example, an image would be Environment.DIRECTORY_PICTURES, an audio file would be Environment. DIRECTORY_MUSIC…

  • Question 2

In this case, wouldn’t the pictures generated by my APP be placed in the folder with those of other apps, which is also very confusing? Don’t worry, you can create your own secondary directory with media. RELATIVE_PATH. If I want to place the above image under the Pictures/MY_PIC/ directory, just do this:

contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/MY_PIC");
Copy the code

Images don’t have to be stored only in Pictures. They can also be stored in the DCIM directory using the fields above, but if you do:

contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment. DIRECTORY_MOVIES);
Copy the code

You will receive the following prompt:

Primary directory Movies not allowed for content://media/external/images/media; allowed directories are [DCIM, Pictures]
Copy the code

After the speech

These are my adaptations of Scoped Storage. I hope they will be helpful. If there are any mistakes, please correct them. In addition, this behavior may change when Android Q is officially released. For more comprehensive information on Scoped Storage, please refer to the reference link.

Refer to the link

  1. Segmentfault.com/a/119000001…
  2. Developer.android.com/preview/pri…