The original title of the official account: Test: "Why can't I see the picture I just saved in the system album, is it my operation?"
1. Introduction
Hi, everyone, I'm Chengxiang Moying!
In an app, the need to create a file and save the file locally is a very common I/O operation. And if this file becomes a picture, then you are not only involved in an I/O operation, but also need to consider how to update the MediaStore, so that you can see it in the system album.
The MediaStore mentioned here is essentially a file system database maintained by Android . It records all the file indexes on the current disk. We can quickly find the files of the current system through it.
The timing of MediaStore refresh is uncertain , that is to say, for a saved image file, MediaStore will not immediately refresh the file system and record the file index. The system itself has some opportunities to automatically refresh the MediaStore, such as restarting the phone. The performance is that after you save a picture to the local folder, you can find the photo in the directory through the file manager app, but you can't see it immediately in the system album, and at the same time you When I wanted to share this picture with WeChat and QQ, I couldn't find it. So after we save the image file, it is especially important to trigger the system to refresh the MediaStore.
This article will talk about how to refresh the system MediaStore after saving the picture.
There are usually the following ways to refresh the system Media:
- By manipulating the MediaStore class.
- Send a broadcast to update the MediaStore.
- By manipulating the MediaScannerConnection class.
These three methods have their own advantages and disadvantages, and we will analyze them gradually.
2. Operating MediaStore
The operation MediaStore mentioned here is actually an inner class that operates on it MediaStore.Image.Media
. It provides several inserImage ()
methods for us to insert image data into the MediaStore and generate a thumbnail.
This method passes in a Bitmap object, and the rest title
and description
are the name of the image file and a description, respectively.
Take a Kotlin example:
MediaStore.Images.Media.insertImage(
contentResolver,
mShareBitmap!!,
"image_file",
"file")
Using the inserImage()
method , we do not need to specify the path, the picture will be automatically saved to Picture
the directory. It also doesn't support us specifying paths. This method is very convenient if we have no requirements for the path where the image is saved and a Bitmap object is saved.
Careful friends may have discovered that inserImage()
there is another overloaded method that allows us to pass in an image file path, but I do not recommend using this method, because it will copy the original image to the Picture
directory. , which means you end up with two identical images on disk.
This is the most clear from looking at the source code. It first uses the BitmapFactory.decodeFile()
method to get a Bitmap, and then calls the inserImage()
method that saves the Bitmap object, so we end up with two identical images on disk.
3. Send broadcast
3.1 Those broadcasts can update the MediaStore
Speaking of broadcasting, before Android 4.4, it was possible to notify the system to refresh the MediaStore through ACTION_MEDIA_MOUNTED
broadcasting , but if you are still relying on this broadcasting, you will get an error message.
E/AndroidRuntime(23718): java.lang.SecurityException: Permission Denial: not allowed to send broadcast android.intent.action.MEDIA_MOUNTED from pid=23718, uid=10097
After Android 4.4, this broadcast can only be broadcast by the system, and the App can only monitor the broadcast. In the current system distribution environment, this road has no way to go.
This design is also easy to understand. After all, scanning the entire disk is very resource-intensive, so the system must take the full-disk scanning permission in its own hands and not open it to avoid being abused by third-party apps.
But Android still provides us with an alternative, which is to use MediaScannerConnection
or send a ACTION_MEDIA_SCANNER_SCAN_FILE
broadcast.
Next, let's talk about ACTION_MEDIA_SCANNER_SCAN_FILE
this broadcast.
3.2 Using broadcast refresh
The way to refresh the MediaStore by broadcasting is very simple, you only need to specify the file path and Action.
val saveAs = "Your_Created_Image_File_Path"
val contentUri = Uri.fromFile(File(saveAs))
val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,contentUri)
sendBroadcast(mediaScanIntent)
Under normal circumstances, it's fine, but if you find that it doesn't work, you need to check that the path to your file is passed correctly.
By looking at the source code of MediaScannerReceiver, you can find onReceive()
that ACTION_MEDIA_SCANNER_SCAN_FILE
there is another restriction in the method, that is, the absolute path of the file passed in must start with the return value of the Environment.getExternalStorageDirectory()
method .
If you are interested, you can read the source code carefully. Here is the source code of Android 6.0:
In essence, the /mnt/sdcard/
path is still recognized, and /sdcard/
can't be used, so as long as we don't hardcode the file path, this problem basically doesn't exist.
It also reminds us that we must not hardcode the file path in the code, which is a coding specification.
3.3 Refresh MediaStore after deleting files
This article has been talking about how to refresh the MediaStore when adding new files. But in fact, there is another problem involved. We delete a file that has been included in the MediaStore. What should we do? It is also mentioned in this article.
Since it is mentioned in this section, the first thing that comes to mind is to send another broadcast directly to refresh the path, but check the scanSingleFile()
method , and you will know that this method will not work.
As you can see here, when the file path you pass in does not exist, it will return
go out , and the refresh logic will not be executed.
Fortunately, I found a solution to refresh and delete files in the DownloadManager class, which is still solved by ContentResolver.
Here, a ContentResolver is used to initiate an operation to delete a file in the MediaStore, and only the absolute path of a file needs to be passed in.
Fourth, operate the MediaScannerConnection class
4.1 Using MediaScannerConnection
There is also one of the most common and recommended methods for refreshing the MediaStore, and that is to use MediaScannerConnection.
Different from MediaStore.Image.Media
and broadcasting, using MediaScannerConnection can not only save the file, but also specify the file path, the best thing is, it also supports the callback of refresh completion.
If we have timing requirements and need to specify a file storage path, the best way is to use the MediaScannerConnection class to operate directly, and this should also be the best way to be compatible.
Here we mainly use the scanFile()
methods to trigger scanning.
With the scanFile()
method , we only need to formulate a file path to be refreshed and the corresponding MimeType. It supports passing multiple paths, or batch scanning.
Note that the MimeType here must be filled in, and the wildcard */*
or null
, otherwise the refresh will fail. Usually, if we save a picture, we only need image/jpeg
to .
The last parameter, onScanCompletedListener, can monitor the results of our scan. It should be noted that if multiple file paths are scanned here, it will also be called back multiple times. So if there is any follow-up operation after the refresh, it needs special treatment (the reason is mentioned later).
MediaScannerConnection.scanFile(this
, arrayOf(picFile.absolutePath)
, arrayOf("image/jpeg"), { path, uri ->
Log.i("cxmyDev", "onScanCompleted : " + path)
})
scanFile()
The use of the method is still very simple, and there is nothing that needs to be explained.
4.2 Principle of MediaScannerConnection
Still looking for the answer from the source code, let's take a look at the implementation of the scanFile()
method .
scanFile()
In , a MediaScannerConnection is created and the connect()
method is called. Next we continue to look at the connect()
method .
In the connect()
method , you can see that it is actually thebindServer()
system service, and all operations are in MediaScannerService.MediaScannerService
The source code of MediaScannerService, if you are interested, you can check it here:
This is a system service, I will not continue to follow it here, I will go back and continue to look at the source code.
However, when we see a connect()
method , there must be a corresponding disconnect()
method. There is a system service in front bindService()
of us. We must have an opportunity to call unbindService()
it, otherwise it will cause leakage.
MediaScannerConnection does provide a disconnect()
method , but we scanFile()
can't get this object through the method. The processing here is very clever, it does not need us to trigger manually disconnect()
, it is self-maintaining.
Continue to look scanFile()
at the ClientProxy
classes we ignored in , the logic is all in it.
scanNextPath()
In , it will judge whether all the file paths passed in have been scanned. If there are no more paths to be scanned, it will call the disconnect()
method by itself to recycle resources.
At this point, we have answered the question we just asked, MediaScannerConnection has already helped us consider a lot of things, we just need to call its standard API.
5. Check for omissions
5.1 Scan other types of media files
Under Android, not only pictures, but also for other media files, the method described in this article is also applicable.
5.2 Avoid a directory from being scanned by MediaStore
After reading this, you should know that even if we do nothing, the next time the phone restarts, the system will still scan the file system and update the MediaStore.
But sometimes, we have some media files in the directory that we don't want to scan by MediaStore, such as pictures and icons cached on SDCard, which we don't want to appear in the system album.
The solution is actually written in the official documentation.
https://developer.android.com/guide/topics/data/data-storage.html
Here is a brief introduction, when a directory that does not need to be scanned by MediaStore, create .nomedia
an , which will prevent the media scanner from reading the media files in this directory. It cannot be shared with other programs through MediaStore.
Of course, some important files are still recommended to be placed in their own private directory.
6. Summary
About refreshing pictures in MediaStore, this article basically makes it clear. The way I recommend is to use MediaScannerConnection.
After reading this article, you can discuss any more questions in the message area. If you think it is good, you can share this article with your friends who need it.
Today, I will reply to " Growth " in the background of the official account, and I will get some study materials that I have organized. You can also reply to " Add Group " to learn and progress together.
Recommended reading:
- Comic: Programmer, can you "manage" your product manager?
- App multi-language translation, machine translation can also be lightning fast!
- 2017 Most Authoritative Blockchain Report (download included)
- Google's Flutter Learning Materials!
- Remote control smart TV, the solution has been open source!