添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
酷酷的茴香  ·  android android11 ...·  昨天    · 
奔跑的青椒  ·  FlutterActivity·  2 天前    · 
强健的红薯  ·  island/shared/src/main ...·  2 周前    · 
开朗的牛排  ·  Intent not defined in ...·  1 月前    · 
个性的饼干  ·  Migrating from get ...·  1 月前    · 
憨厚的火腿肠  ·  Vision ...·  1 月前    · 
豪气的墨镜  ·  python实现YUV转RGB_python ...·  1 月前    · 
帅呆的汉堡包  ·  账户注销·  1 月前    · 
Migrating from get External Storage Public Directory in Android 10 & 11

Migrating from get External Storage Public Directory in Android 10 & 11

Android 11 introduces breaking changes to working with files on Android. One of these breaking changes includes the deprecation of the following method:

Environment
  .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)

The Canned Replies Android app used to use this deprecated API. As of Android 11, both importing and exporting features broke. 😬 This article will go through the solution I used to fix these bugs.

Why did Google deprecate get External Storage Public Directory?

In the Android documentation, Google mentions that access to the user's shared and external storage directories is deprecated as of API level 29 for privacy reasons.

What should I do instead?

The documentation makes a few suggestions as to which alternative API's to use instead. Some suggestions include MediaStore and getExternalFilesDir, which may not always be available.

You may, however, be able to get away with solving this problem with a much simpler approach by using the intent system.

Do users access these files?

Does your app read and write private files to disk, e.g. for caching reasons? Or will the user specifically be involved in the process?

If you are creating features like allowing the user to import or export their data using files, then you can do this with using the intent system. You don't actually need permissions to write to any storage directories and can do so via intents.

Using Intents to read and write files

You can uses the Intent API to read and write files in Android. This API implementation has 2 steps:

  • Creating your intent to perform an action, e.g. get a file
  • Handling the system's response to your intent, i.e. success and failure cases
  • This blog post will go through the example of reading and writing JSON files. This blog post code is written in Kotlin.

    Reading files using Intents

    To read files using Intents, we can prompt the user to pick files and then we can perform actions with those files.

    This example will read a file of any type.

    Creating our intent to read a file

    First, we need to create an intent to read a file. We can attach this to a user action, for example, the tapping of a button or menu item. Once the user interacts with our view, we can execute requestImportIntent() which is implemented as follows:

    private fun requestImportIntent() {
      val pickIntent = Intent(Intent.ACTION_GET_CONTENT)
      pickIntent.type = "*/*"
      startActivityForResult(pickIntent, INTENT_IMPORT_REQUEST)
    

    The above code is creating an intent for ACTION_GET_CONTENT which allows the user to pick a file of any type.

    Then, we start an activity for request with the request code stored in the INTENT_IMPORT_REQUEST, which is an Int value.

    Once this intent activity is created, the user will be prompted to provide a file. This will allow the user to browse their file system and choose the file they want to provide.

    Responding to our intent to read the file

    To respond to this intent, we need to implement the override method onActivityResult. We can use the following code, which only cares about successful results, i.e. Activity.RESULT_OK:

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
      super.onActivityResult(requestCode, resultCode, data)
      if (resultCode != Activity.RESULT_OK) {
          return
      when (requestCode) {
        INTENT_IMPORT_REQUEST -> handleIntentImport(data)
    

    When the requestCode is for the intent we assigned to INTENT_IMPORT_REQUEST, then we should handle it. I've created a method handleIntentImport(data) which takes the nullable Intent data:

    private fun handleIntentImport(intent: Intent?) {
      val importedData: Uri = intent?.data ?: return
      try {
        // Do things with imported data
      } catch (e: IOException) {
        e.printStackTrace()
    

    The above code allows us to read the data from the intent, which may be null. If it's null, we make an early return out of the function, otherwise we try to perform our actions on the file data.

    Writing files using Intents

    Similar to reading files with Intents, we can also write files with Intents. The following example will write a JSON file to the user's desired destination. It will allow the user to name the file.

    Creating our intent to write a JSON file

    We need to create an intent to write to a file. The following code will allow us to prompt the user to choose where they'd like the document we create to be written:

    private fun requestExportIntent() {
      val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
      intent.addCategory(Intent.CATEGORY_OPENABLE)
      intent.type = "application/json"
      intent.putExtra(Intent.EXTRA_TITLE, generatedFileNameWithTimestamp)
      startActivityForResult(intent, INTENT_EXPORT_REQUEST)
    

    In the above code, we first create an intent for ACTION_CREATE_DOCUMENT and specify that the category is openable. We need to add the category CATEGORY_OPENABLE in order to be able to have full control over the file via the ContentResolver API's.

    Specifying the right type is required to ensure that the file has the right file extension. For example, to ensure we have the .json file extension, we need to set the type to application/json.

    We can also provide a default file name. In this case, I'm using a generated file name with a timestamp. The user will be able to change this.

    Next, the user will choose the location and the file name. Once they successfully create the file, we will be able to respond to the intent.

    Responding to our intent to write the file

    Similar to our read intent above, we will need to implement the override method onActivityResult. The following code has both the existing read intent code and the new code added:

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
      super.onActivityResult(requestCode, resultCode, data)
      if (resultCode != Activity.RESULT_OK) {
          return
      when (requestCode) {
        INTENT_IMPORT_REQUEST -> handleIntentImport(data) // old
        INTENT_EXPORT_REQUEST -> handleExportIntent(data) // new ✨
    

    Given that we get a successful export intent request (i.e. the user has chosen a directory and filename), we can handle that in our handleExportIntent method:

    private fun handleExportIntent(intent: Intent?) {
      val uri = intent?.data ?: return
      val outputStream = contentResolver.openOutputStream(uri)
      val exportText = myStringifiedJsonData
      if (exportText == null || outputStream == null) {
        showExportFailureMessage()
        return
      try {
        outputStream.write(exportText.toByteArray())
        outputStream.close()
        showExportSuccessMessage()
      } catch (e: Exception) {
        showExportFailureMessage()
        e.printStackTrace()
    

    We can now use the Content Resolver API's to write to this data URI using an output stream.

    The example above of myStringifiedJsonData is just a serialized list of POJO's (Plain Old Java Objects). So I serialize the data as JSON, write it to the file, and close the output stream.

    Benefits of using intents

  • No permissions required
  • User has full control over the files
  • No permissions required

    What's great about using the intent system is that no extra permissions are required. You do not need access to read and write to storage if you are using intents.

    If Google starts cracking down on permissions for Android like they did with the Chrome Web Store, we may find ourselves with more Play Store rejections for requesting permissions we may not need.

    User has full control over the files

    Unlike when working with getExternalStoragePublicDirectory, your users can choose where the file goes instead of you choosing a default directory. This is more user-friendly as it would allow the user to put it somewhere specific so they can retrieve it later.