preface

Android 10 has been out for a long time, and Android 11 has been used by a large number of people, so has your application adapted to it?

In 10.0, scoped storage became very important. This new feature changed the way we had always used external storage, so a large number of apps were facing code adaptation

This article gives a detailed introduction to scoped storage and how to adapt it

Before 7.0, we could use uri. fromFile to convert files from memory cards to Uri objects that represented the local real path.Copy the code

After 7.0, real-path URIs were considered unsafe, so a new solution was provided to access files through FileProvide, a special type of content provider. He uses a content-provider-like mechanism to protect data.

Prior to 7.0, access an image like this:

String fileName = "defaultImage.jpg";

File file = new File("File path", fileName);
Uri uri = Uri.fromFile(file);
Copy the code

After 7.0, access is as follows:

File file = new File(CACHE_IMG, fileName);
Uri imageUri=FileProvider.getUriForFile(activity,"com.sandan.fileprovider", file);// Here is how to get the alternate URI
Copy the code
 <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.sandan.fileprovider"// This needs to be the same as the above part of the string
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
Copy the code
<resource xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="images"
        path="" />
        //path indicates the shared path. If the value is empty, the entire SD card is shared
</resource>
Copy the code

However this really good, for developers and it is good, but to use the user, this undoubtedly some rascal, because developers can access all the location in memory, and make some changes, leading to an SD card in the space becomes very mess, even if the uninstall the app, but also some junk files in memory.

Scoped storage

In 10.0, Google added scoping to Android 10 to address these issues

What is scope? Since 10.0, each application can only read and create files in the directory associated with its own external storage space, also known as a sandbox. GetExternalFilesDir (); getExternalFilesDir();

/ storage/emulated / 0 / Android/data / < package name > / filesCopy the code

By placing the data in this directory, you can read and write files using the previous method without making any changes or adaptations. However, the files in this folder will be deleted as the application is uninstalled.

What if you need to access other directories, such as getting pictures from an album and adding an image to the album? To do this, the Android system classifies system file types ** : Images, audio, and video files can be accessed through the MediaStore API, which is called shared space. Other system files need to be accessed using the system’s file picker. **

In addition, if an application writes images, videos, or audio files to the media library, it will automatically be used for read/write permissions. You do not need to apply for additional permissions. If you want to read images, videos, or audio files contributed by other applications to the media, you must apply for the READ_EXTERNAL_STORAGE permission. The WRITE_EXTERNAL_STORAGE permission will be deprecated in future versions.

Obtain system pictures:

val cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null.null.null."${MediaStore.MediaColumns.DATE_ADDED} desc")
if(cursor ! =null) {
    while (cursor.moveToNext()) {
        val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
        val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
        println("image uri is $uri")
    }
    cursor.close()
}
Copy the code

Adapter points

Sample code, and Demo

  • Open the camera

    1. In 10.0, you need to create a Uri of the image address based on the shared file to save the photo taken.

    2. After the photo is taken, get the uri

    3. If you want to display images directly, you can load them directly through the URI

    4. If the image is to be uploaded, the URI needs to be processed as a file object

      In 10.0, only sandbox files and shared folders can be accessed. Note that shared folders can be accessed through URIs such as input/output streams. But you cannot convert it to file. Even shared folders cannot be accessed directly through file.

      Therefore, when uploading the image, you need to use contentProider to convert the URI into an inputStream, then read the data and save it in the sandbox file, and then fetch the file in the sandbox file.

      Note that you can do some compression of the image after you get the URI.

  • Open the photo album

    1, Open album directly with intent

    2, get the returned URI address

    3. In 10.0, you need to perform the same operations as in 3,4 in “Open camera”.

  • The download file

    1. In 10.0, you need to create a URI based on the shared folder to save the file

    2. Get the corresponding inputSteam through network operation

    3. Convert the URI to an outputStream via contentProider

    4, input read data, output write number.

  • Be aware of

    • File objects can only be sandboxed, remember.

    • You need to pay attention to the time required to copy and compress images. If the time required is too long, you need to put the images in the child thread.

  • Upload a file

    • The files need to be copied to the sandbox and then uploaded

      1. Select the file using the file selector

      val mimeTypes = arrayOf(
          FileIntentUtils.getMap("doc"),
          FileIntentUtils.getMap("pdf"), FileIntentUtils.getMap("ppt"),
          FileIntentUtils.getMap("xls"), FileIntentUtils.getMap("xlsx")
      )
      FileIntentUtils.openBle(this, REQUEST_CHOICE_FILE, mimeTypes)
      Copy the code
      /** * select file */
      fun openBle(activity: Activity, code: Int, types: Array<String>) {
          val intent = Intent(Intent.ACTION_GET_CONTENT)
          intent.addCategory(Intent.CATEGORY_OPENABLE)
          intent.type = "application/*";
          intent.putExtra(Intent.EXTRA_MIME_TYPES, types)
          activity.startActivityForResult(intent, code)
      }
      Copy the code
      /** * Get common file types *@param key
       * @return* /
      fun getMap(key: String): String {
          val map: MutableMap<String, String> = HashMap()
          map["rar"] = "application/x-rar-compressed"
          map["jpg"] = "image/jpeg"
          map["png"] = "image/jpeg"
          map["jpeg"] = "image/jpeg"
          map["zip"] = "application/zip"
          map["pdf"] = "application/pdf"
          map["doc"] = "application/msword"
          map["docx"] = "application/msword"
          map["wps"] = "application/msword"
          map["xls"] = "application/vnd.ms-excel"
          map["et"] = "application/vnd.ms-excel"
          map["xlsx"] = "application/vnd.ms-excel"
          map["ppt"] = "application/vnd.ms-powerpoint"
          map["html"] = "text/html"
          map["htm"] = "text/html"
          map["txt"] = "text/html"
          map["mp3"] = "audio/mpeg"
          map["mp4"] = "video/mp4"
          map["3gp"] = "video/3gpp"
          map["wav"] = "audio/x-wav"
          map["avi"] = "video/x-msvideo"
          map["flv"] = "flv-application/octet-stream"
          map[""] = "* / *"
          returnmap[key] ? :"application/msword"
      }
      Copy the code

      After the file is selected, the intent returns a URI that is then converted to a file

      /** * uri to file */
      fun uriToFile(context: Context, uri: Uri): File? = when (uri.scheme) {
          ContentResolver.SCHEME_FILE -> uri.toFile()
          ContentResolver.SCHEME_CONTENT -> {
              val cursor = context.contentResolver.query(uri, null.null.null.null) cursor? .let { it ->if (it.moveToFirst()) {
                      // If the value is above 10.0
                      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                          // Save to the local directory
                          val ois = context.contentResolver.openInputStream(uri)
                          valdisplayName = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) ois? .let { input ->valfile = File( context.externalCacheDir? .absolutePath + File.separator, displayName )if (file.exists()) file.delete()
                              file.createNewFile()
                              file.outputStream().use { input.copyTo(it) }
                              file
                          }
                      } else {
                          / / com. Blankj: utilcodex: 1.30.5
                          UriUtils.uri2File(uri)
                      }
                  } else {
                      it.close()
                      null}}}else -> null
      }
      Copy the code

      With the above steps, you can turn the URI into a file object that supports uploading.

    • What if you add a requirement to open a file?

      At this point, the file has been copied into the sandbox, and you can do whatever you want with it, but if you want to open the file, you need to use another app to open it, and then the file is stored under the sandbox, because no other app can access the file under the current app sandbox.

      Therefore, in this case, you need to copy the file to the shared directory, and then generate the corresponding URI, which can be opened through another app

      // Open the file
      data.fileData? .file? .also { file ->val index = file.name.lastIndexOf(".")
          val suffix = file.name.substring(index + 1, file.name.length)
          // After Android 10, you need to copy the file to the public directory so that other applications can be opened
          showLoading()
          lifecycleScope.launch(Dispatchers.IO) {
              // Copy files to the shared directory
              val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                  FileIntentUtils.copyToDownloadAndroidQ(this@WorkReleaseActivity, suffix, file.inputStream(), file.name,"tidycar")}else {
                  // Otherwise, the uri is changed directly
                  file.toUri()
              }
              launch(Dispatchers.Main) {
                  dismissLoading()
                  // Open the file
                  FileIntentUtils.openFileEx( uri, suffix, this@WorkReleaseActivity)}}}Copy the code
      /** * Copy or download files to public directory **@param context
       * @paramMimeType File type *@paramInput Input stream *@paramFileName fileName *@paramSaveDirName Folder name *@return* /
      @RequiresApi(api = Build.VERSION_CODES.Q)
      fun copyToDownloadAndroidQ(  context: Context, mimeType: String? , input:InputStream, fileName: String,saveDirName: String): Uri? {
          val file = File(
              Environment.getExternalStorageDirectory().path + "/Download/$saveDirName",
              fileName
          )
          // If the same file already exists in the public directory, it is returned directly
          if (file.exists()) {
              return file.toUri()
          }
          if(! FileQUtils.isExternalStorageReadable()) {throw RuntimeException("External storage cannot be written!")}val values = ContentValues()
          // Display the name
          values.put(MediaStore.Downloads.DISPLAY_NAME, fileName)
          // The type of file to store
          values.put(MediaStore.Downloads.MIME_TYPE, mimeType)
          // Public file path
          values.put(
              MediaStore.Downloads.RELATIVE_PATH,
              "Download/" + saveDirName.replace("/".toRegex(), "") + "/"
          )
          // Generate a Uri
          val external = MediaStore.Downloads.EXTERNAL_CONTENT_URI
          val resolver = context.contentResolver
          / / write
          val insertUri = resolver.insert(external, values) ? :return null
          val fos: OutputStream?
          try {
              / / the output stream
              fos = resolver.openOutputStream(insertUri)
              if (fos == null)  return null
              var read: Int
              val buffer = ByteArray(1444)
              while(input.read(buffer).also { read = it } ! = -1) {
                  // Write to the URI
                  fos.write(buffer, 0, read)
              }
          } catch (e: java.lang.Exception) {
              e.printStackTrace()
          }
          return insertUri
      }
      Copy the code

      In the shared directory, create a folder, copy the files into it, and return the URI

      /** * open the file */
      fun openFileEx(uri: Uri? , fileType:String, context: Context) {
          try {
              val intent = Intent()
              intent.action = Intent.ACTION_VIEW
              intent.addCategory("android.intent.category.DEFAULT")
              intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
              // Check whether the version is greater than or equal to 7.0
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                  val builder = VmPolicy.Builder()
                  StrictMode.setVmPolicy(builder.build())
              }
              //getMap has the code at the top
              intent.setDataAndType(uri, getMap(fileType))
              context.startActivity(intent)
          } catch (e: Exception) {
          }
      }
      Copy the code

If your project hasn’t been adapted yet, get it on the agenda!

Happy Codeing!