The background,
Recently, a new problem was reported by the business side in the album component of the company. When running the album on Android 10 phones with targetSdk=30, the thumbnail could not be loaded, so the road to this pit was started.
Location problem
First, I set targetSdk to 30 in the album Demo and ran it on an Android 10 test machine and found that the thumbnails showed up perfectly.
Why does the same code work on demo but not on business side app?
There must be some configuration difference that causes this result.
After looking for different…
I noticed that demo has an extra attribute in its Androidmanifest.xml
<application android:requestLegacyExternalStorage="true" ... >Copy the code
So, officially opened my adaptation of the road…
Second, the requestLegacyExternalStorage is what?
This attribute can be temporarily disabled when the application runs on Android 10 or later with targetSdk >= 29 configured.
What is “partitioned storage”?
Partition storage
To help users better manage their files and reduce clutter, apps targeting Android 10 (API level 29) and later are granted partitioned access to external storage by default. An application can access only application-specific directories on the external storage space and specific media files created by the application.
On devices running Android 9 (API level 28) or lower, any application can access application-specific files in the external storage space as long as other applications have corresponding storage permissions. To help users better manage their files and reduce clutter, apps targeting Android 10 (API level 29) and later are granted partitioned access to external storage by default. After partitioned storage is enabled, applications cannot access application-specific directories of other applications.
With partitioned storage enabled in Android 10, your application will no longer be able to access public folders in other external storage Spaces if it has permission to do so
2. What is the impact of partitioned storage?
For example, when displaying album thumbnails in the App, we’ll pass Filepath to the Image loading frame to help render the thumbnails, like this
ImageLoader.load(imageView, Uri.fromFile(path);
Copy the code
The path is usually ‘sdcard/DCIM/… ` `, and it was obvious to the folder in the external storage space, and not the application exclusive file, then the image loading framework layer will throw an exception. Java IO. FileNotFoundException. If you use Glide, an exception will be thrown at the code location in the diagram
3, 11 of the Android requestLegacyExternalStorage attribute failure
As I continued to scroll through official documents, I learned another message:
Note: When you will be used to update to the Android after 11 (API level 30) as the target platform, if applied in carrying 11 devices running Android, the system will ignore requestLegacyExternalStorage properties, Therefore, your applications must be prepared to support partitioned storage and migrate application data for users on these devices.
This piece of information that can be understood as a simple requestLegacyExternalStorage = true only a stopgap, onto the Android 11, or adapter work to do.
This success for me to take a detour, foreshadowing…
Four, start to take detours
1. Android 10 only (not recommended)
Add in the Manifest
<application android:requestLegacyExternalStorage="true" ... >Copy the code
We just know, if the application running on Android devices of 11, the system will ignore requestLegacyExternalStorage properties, forced open the partition storage. There may still be exceptions (I’m not really using an Android 11 machine here). So I think the default, requestLegacyExternalStorage = true only concern, but don’t understand the essential question.
2. Discard File path and use Uri
Had mentioned above, we use access to the File path way to load the thumbnail, throws Java. IO. FileNotFoundException. So what is the official recommendation to do? There are roughly three steps
- Gets the MEDIA data ID
- Gets the thumbnail URI
- Load thumbnails with URIs
val projection = arrayOf( MediaStore.Video.Media._ID, MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DURATION, MediaStore.Video.Media.SIZE ) ... val query = ContentResolver.query( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder ) query? .use { cursor -> media.id = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID) ... media.thumbnailUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, media.id) } // Load thumbnail of a specific media item. val thumbnail: Bitmap = applicationContext.contentResolver.loadThumbnail( media.thumbnailUri, Size(640, 480), null)Copy the code
The complete code, reference developer.android.com/training/da…
Because this change involves a change in the data source, there are a lot of changes, and there is a lot of if else version differentiation, so a lot of glue code was written…
However, the thumbnail was successfully displayed on a targetSdk=29 Android 10 phone.
3. New problems arise
The photo preview function of the album also cannot be used. After investigation, it is found that the problem is the same. The glue code has been written, and they are all within the range. So it took half an hour to fix the image preview problem.
Just when I was excited to be done, I clicked on the video preview…
Ok, see the familiar but desperate error messages, relying on the player raise the familiar abnormal Java library. IO. FileNotFoundException open failed: EACCES (Permission denied). The file path is also passed to FFmPEG for playback, but when initializing the player, it simply hangs because it has no permissions.
4. Take a detour
First of all, I went to the player developer and talked to him about whether he could initialize it by passing a URI or a FileDescriptor. Several not-so-friendly conclusions were drawn:
- 1. Pass urIs to Native layer
content://media/external/images/media/{media_id}
The Uri Native layer does not seem to be open - 2. Transferring fd to Native layer may involve the problem that fd of Java layer is referenced by Native and then cannot be released. In order to release FD, the interface for releasing FD needs to be opened
- 3. In addition to photo albums, there are many other places to pass File path to Native layer
Then, I started thinking about how to get around this problem, and I came up with two questionable solutions:
- Copy file to private directory because public directory cannot be accessed
- 2. Request MANAGE_EXTERNAL_STORAGE permission
It’s an interesting privilege, it’s official
Most applications that require access to shared storage space can follow best practices for sharing media and non-media files. However, the core use cases of some applications require extensive access to files on the device, but cannot be done efficiently using privacy-aware storage best practices. For these cases, Android provides a special application access called “All file access.
Some of the apps mentioned in this paragraph, such as “anti-virus apps” and “file browsers”, need to scan all files in sdCard and will not work properly without permission
If I were a user and I saw an App that didn’t need these permissions and applied for them, it would be a form of persuasion
5. Calm down and review the document
By the time I reached the fourth step, I began to realize that it was possible to take a detour, and the usual adaptation had never been so abnormal. So I did some research and found this video, www.youtube.com/watch?v=Rjy…
The useful information in the video is probably this: in Android 10, many developers reported similar problems, such as the inability to use the File Api when using some native libraries, which caused a lot of difficulties. So, in Android 11, compatibility was made and media library files could be accessed via the Java File Api again (I don’t know if I should be happy at this point, Android is better for developers than Apple dad).
Later, I looked through the official documents carefully and did find a small paragraph of unremarkable text
To help your application work better with third-party media libraries, Android 11 allows you to use apis other than the MediaStore API to access media files in shared storage via direct file paths. These include:
- The File API.
- Native libraries, such as fopen().
Five, the conclusion
Ok…
After going around in a big circle, there are several results:
- 1, white glue code can be written, in · targetSdk = 29, running on Android applications of 10, requestLegacyExternalStorage properties, fully covered (waste I started I despise it
- 2. Android 11 doesn’t need to adapt anything, though
requestLegacyExternalStorage
Property is invalid, but only media library files are accessed through the File Api in the album, and there will be no problem. - 3. If the App has code to access the external storage common directory through File Api, it still needs to be adapted. As for how to do this, this article will not discuss
lesson
After going all the way around, there are two lessons:
- When adapting a new version, it is best to test it on a real machine first, in case it works perfectly
- Read the document, read the document, read the document
Glide loads thumbnails
Finally, say a topic that is not quite relevant to the adaptation, just want to see the adaptation of the content of friends can skip first.
In the process of adaptation, I also followed the glide loading thumbnail process, and also figured out some problems, by the way to share with you
1. Why is there no error when sending Content-URI to Glide and an error when sending file Path?
As mentioned above, the official way to obtain album thumbnails is
// Load thumbnail of a specific media item.
val thumbnail: Bitmap =
applicationContext.contentResolver.loadThumbnail(
media.thumbnailUri, Size(640, 480), null)
Copy the code
But most of our development is directly using images to load the framework, such as Glide
Glide.with(imageView).asbitmap ().load(uri) // or file path.into ()Copy the code
Passing a File path will throw an exception when we are not adapting to Android 10, as we explained earlier. Adaptation after we introduced into the content: / / media/external/images/media / {media_id} to Glide, Glide is how to identify and then load the bitmap? I took the problem to track the Glide loading picture of the process of the source, here we directly say the conclusion.
StreamLocalUriFetcher
private InputStream loadResourceFromUri(Uri uri, ContentResolver contentResolver) throws FileNotFoundException { switch (URI_MATCHER.match(uri)) { case ID_CONTACTS_CONTACT: return openContactPhotoInputStream(contentResolver, uri); case ID_CONTACTS_LOOKUP: case ID_LOOKUP_BY_PHONE: // If it was a Lookup uri then resolve it first, then continue loading the contact uri. uri = ContactsContract.Contacts.lookupContact(contentResolver, uri); if (uri == null) { throw new FileNotFoundException("Contact cannot be found"); } return openContactPhotoInputStream(contentResolver, uri); case ID_CONTACTS_THUMBNAIL: case ID_CONTACTS_PHOTO: case UriMatcher.NO_MATCH: default: return contentResolver.openInputStream(uri); }}Copy the code
Uri after matching logic to the default branch, using contentResolver. OpenInputStream (uri) to read the bitmap, since contentResolver access is through the system, it’s no problem.
2. Talk about Glide loading picture process
This is my simple summary of Glide loading picture process, not to do a detailed explanation, a brief introduction to the key elements in the figure below:
- The green circle is the timing
- The yellow squares represent input and output
- Thick solid wireboxes represent classes
- Thin solid wire boxes represent key methods
- The dotted line represents which class the method belongs to
The procedure in the diagram is how this code runs
Glide.with(imageView).asbitmap ().load(uri) // or file path.into ()Copy the code
Android Advanced Development System Advanced notes, the latest interview review notes PDF,My lot
At the end of the article
Your likes collection is the biggest encouragement to me!
Welcome to follow me, share Android dry goods, exchange Android technology.
If you have any comments or technical questions about this article, please leave a comment in the comments section.