background

Recently, when I started to adapt Android10 and Android11 in the project, I encountered a lot of difficulties. Before have written specially qq, wechat to share the adaptation. But this time in view of partial business side adaptation work or encountered some new problems. Record down, convenient later refer to, hope to be able to help the relevant students who encounter this problem.

If you are not familiar with the concept of storage partitions, you can refer to the previous article:

Android Q (10) Partition storage for wechat, QQ sharing adaptation

Access to resources in private directories

There is such a scenario: if we want to share a picture to QQ or wechat, the first step is to get the bitmap (locally generated or loaded from the network), and then store it on the local SD card, and finally send the absolute path of the stored picture to QQ or wechat.

In the above scenario, these key points are covered:

  • Store images to an SD card
  • Pass the absolute path to QQ or wechat

1.1 Directly Accessing the Root Directory of the SD Card

This is done via a FileOutPutStream, which is fine under Android10. The path is as follows:

/storage/emulated/0/demo/sharePicture/1637048769163_share.jpg
Copy the code

However, on Android10 and above, there will be an error:

java.io.FileNotFoundException: /storage/emulated/0/demo/sharePicture/1637048769163_share.jpg: open failed: EACCES (Permission denied) // The storage Permission was approvedCopy the code

This is because we are limited by the storage partition and cannot directly access the external directory. Therefore, we need to modify the app-Specific directory where the store path is scope.

1.2 Change to app-specific private directory

This directory does not require permissions for its own access, if third party access requires permissions! Therefore, we will use the FileProvider to grant temporary authorization later. If you are not familiar with FileProviders, refer to the first article in this article.

/storage/emulated/0/Android/data/com.demo.test/files
Copy the code

It works when you store images via FileOutPutStream.

private fun saveImage(bitmap: Bitmap, storePath: String, filePath: String): Boolean { val appDir = File(storePath) if (! appDir.exists()) { appDir.mkdirs() } val file = File(filePath) if (file.exists()) { file.delete() } var fos: FileOutputStream? = null try { fos = FileOutputStream(file) bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos) fos.flush() return true } catch (e: IOException) { e.printStackTrace() } catch (e: FileNotFoundException) { e.printStackTrace() } finally { fos?.close() } return false }Copy the code

After testing, sharing QQ and wechat was successful on 29 devices and 29 devices.

1.3 Summary of Sharing Principles

The essence of sharing is to give the picture path to QQ or wechat access, so that they can access our pictures. The partition was previously stored on an external SD card and had no problem.

After partition, QQ or wechat cannot access our private directory app-specific. Therefore, we need to convert the fileprovider into content: // format to share and temporarily authorize qq or wechat to access our pictures.

Qq has made internal fileProvider adaptation, so we only need to pass in the absolute path fileProvider: // format, while wechat needs to receive content: // format, so we need to external conversion.

Specific adaptation logic reference header article ~

Access to resources in the public directory

Google suggests we use mediaStore or SAF to access it. An image in a public directory on Android10 cannot be accessed using the file:// format, indicating that the path cannot be found. Glide loading, picture selection library, cropping frame and so on will be affected.

But, here’s the catch: it doesn’t work on Android10, it works on Android11!! Why is that?

Because Google changed it back and made Android11 support the file:// format… (WTF? Thank you ~~)

** WHEN I say Android10 and Android 11, I mean targetSdkVersion

2.1 Insert an image into the public directory

MediaStore only:

ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DESCRIPTION, "This is an image"); values.put(MediaStore.Images.Media.DISPLAY_NAME, "Image.png"); values.put(MediaStore.Images.Media.MIME_TYPE, "image/png"); values.put(MediaStore.Images.Media.TITLE, "Image.png"); values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/test"); Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; ContentResolver resolver = context.getContentResolver(); InsertUri Uri insertUri = resolver.insert(external, values); LogUtil.log("insertUri: " + insertUri); OutputStream os = null; try { if (insertUri ! = null) { os = resolver.openOutputStream(insertUri); } if (os ! = null) { final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); bitmap.compress(Bitmap.CompressFormat.PNG, 90, os); // write what you want } } catch (IOException e) { LogUtil.log("fail: " + e.getCause()); } finally { try { if (os ! = null) { os.close(); } } catch (IOException e) { LogUtil.log("fail in close: " + e.getCause()); }}Copy the code

2.2 Content URI Path to File format

public static String getFilePathFromContentUri(Uri selectedVideoUri, ContentResolver contentResolver) { String filePath;  String[] filePathColumn = {MediaStore.MediaColumns.DATA}; Cursor cursor = contentResolver.query(selectedVideoUri, filePathColumn, null, null, null); cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); filePath = cursor.getString(columnIndex); cursor.close(); return filePath; }Copy the code

2.3 Obtain the file format path according to the image name

String imageName="test"; Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; ContentResolver resolver = BaseApp.getContext().getContentResolver(); String selection = MediaStore.Images.Media.TITLE + "=?" ; String[] args = new String[] {imageName}; String[] projection = new String[] {MediaStore.Images.Media._ID}; Cursor cursor = resolver.query(external, projection, selection, args, null); // Get content format uri imageUri = null; //content://media/external/images/media/318952 if (cursor ! = null && cursor.moveToFirst()) { imageUri = ContentUris.withAppendedId(external, cursor.getLong(0)); cursor.close(); }Copy the code

Get the absolute path, glide on Android11, QQ sharing, third-party picture selection framework can be accessed normally.

Three, the ultimate adaptation scheme

  • On the Android10

Open flags: android: requestLegacyExternalStorage = “true” to open the compatibility mode, close the partition boards, equivalent to targetSdkVersion = 29 as a combination of running in the old way, completely no problem. Perfect for avoiding pits that cannot access public directories!!

  • On the Android11

The above marks will automatically become invalid. So, the App store is still in the app-specific directory. Sharing private directories can be adapted using fileProvider. To share the public directory, the File API supports direct access to the public directory, so you can simply convert the Content format to File, as described in part 2 of this article.

Finally, I would like to ask two questions:

1. TargetSdk = 30, android: requestLegacyExternalStorage = “false” running on the device of Android10 zha yao?

A: There are definitely access issues. Android10 devices still run in Android10 compatibility mode. So I’m going to change it to true.

2. TargetSdk = 30, android: requestLegacyExternalStorage = “false” running on the device of Android11 zha yao?

Answer: if according to the above normal adaptation, certainly have no problem completely!

The above is their own adaptation experience, unavoidably there are omissions, if the article has a problem or better advice, welcome to comment ~