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)
正常情况下,它是没有问题的,不过假如你发现它不生效,就需要检查一下你文件的路径是否传递正确。
通过查看 MediaScannerReceiver 的源码,可以发现 onReceive()
方法中,针对 ACTION_MEDIA_SCANNER_SCAN_FILE
还有一个限制条件,那就是传递进去的文件绝对路径,必须是以 Environment.getExternalStorageDirectory()
方法的返回值开头。
有兴趣可以仔细阅读源码,这里是 Android 6.0 的源码:
http://androidxref.com/6.0.0_r1/xref/packages/providers/MediaProvider/src/com/android/providers/media/MediaScannerReceiver.java
本质上,还是 /mnt/sdcard/
路径就认,而 /sdcard/
就无法使用,所以只要我们不硬编码文件路径,这个问题基本上也就不存在。
这里也提醒我们,一定不要在代码里,硬编码文件路径,算是一个编码规范了。
本文一直都在说添加新文件的时候,如何刷新 MediaStore 的问题。但是其实还涉及到另外一个问题,我们删除了一个已经被收录在 MediaStore 中的文件,怎么办?在本文里也顺便讲一下。
既然放在这一小节讲,首先想到的是,直接再发一个广播出去,刷新这个路径,但是查阅最终执行扫描前的 MediaScanner 的 scanSingleFile()
方法,你就会知道这样的方式是行不通的。
在这里可以看到,当你传递进去的文件路径,指向的文件不存在的时候,会直接 return
出去了,就执行不到刷新的逻辑里。
所幸的是,我在 DownloadManager 类中,找到了刷新删除文件的解决办法,依然是通过 ContentResolver 来解决。
这里通过 ContentResolver 来向 MediaStore 中发起一个删除文件的操作,只需要传递进去一个文件的绝对路径即可。
刷新 MediaStore 还有一个最通用也是我推荐的一个方法,那就是使用 MediaScannerConnection 进行操作。
不同于 MediaStore.Image.Media
和广播的方式,使用 MediaScannerConnection 不仅可以保存文件,还可以指定文件路径,最好的就是,它还支持刷新完成的回调。
如果我们对时序有要求,并且需要制定文件保存路径的话,最好的方式就是直接使用 MediaScannerConnection 类进行操作,并且这也应该是兼容最好的方式。
这里我们主要是利用 MediaScannerConnection 类的 scanFile()
方法进行触发扫描。
通过 scanFile()
方法,我们只需要制定一个待刷新的文件路径和对应的 MimeType 即可,它支持传递多个路径,也可就是支持批量扫描。
注意这里的 MimeType 是一定要填写的,并且不能写通配符 */*
或 null
,否则会导致刷新失败,通常我们保存的是一个图片的话,只需要传递 image/jpeg
即可。
最后一个参数, onScanCompletedListener 中可以监听我们扫描的结果,需要注意的是,假如这里扫描的是多个文件路径,它也会被回调多次。所以如果有什么在刷新之后的后续操作,就需要特殊处理一下(原因后面是说)。
MediaScannerConnection.scanFile(this
, arrayOf(picFile.absolutePath)
, arrayOf("image/jpeg"), { path, uri ->
Log.i("cxmyDev", "onScanCompleted : " + path)
scanFile()
方法的使用还是很简单的,没什么需要额外交代的了。
依然是从源码中找答案,我们先来看看 scanFile()
方法的实现。
在 scanFile()
里,创建了一个 MediaScannerConnection 并调用了 connect()
方法。接下来我们继续看 connect()
方法。
在 connect()
方法中,可以看到,它实际上是 bindServer()
了 MediaScannerService
这个系统服务,所有的操作都在 MediaScannerService 中。
MediaScannerService 的源码,有兴趣可以去这里查看:
http://androidxref.com/6.0.0_r1/xref/packages/providers/MediaProvider/src/com/android/providers/media/MediaScannerService.java
这是一个系统服务,我到这里就不继续跟下去了,回过头来继续看源码。
不过看到 connect()
方法的时候,那对应的,一定有 disconnect()
方法存在了,前面 bindService()
了一个系统服务,我们一定要有一个时机去调用 unbindService()
,否则就会造成泄露。
MediaScannerConnection 确实提供了 disconnect()
方法,但是我们通过 scanFile()
方法拿不到这个对象。这里处理的非常的巧妙,不需要我们手动去触发 disconnect()
,它是自维护的。
继续看 scanFile()
里被我们忽略的 ClientProxy
类,逻辑都在这里面。
在 scanNextPath()
中,会去判断传递进去的文件路径是否都扫描过,如果已经没有更多需要扫描的路径了,就自己去调用 disconnect()
方法,回收资源。
到这里,也就解答了我们刚才的疑问,MediaScannerConnection 已经帮我们考虑了很多事情,我们只需要调用它的标准 API 就好了。
五、查缺补漏
5.1 扫描其他类型的媒体文件
在 Android 下,不仅仅只有图片,对于其他媒体文件,使用本文介绍的方法,也是适用的。
看完到这里应该会知道,哪怕我们什么都不做,在手机下次重启的时候,系统依然会去全盘扫描文件系统,更新 MediaStore。
但是有时候,我们有一些目录下的媒体文件,并不想让 MediaStore 扫描到,例如在 SDCard 上缓存的图片、图标等,这些我们都不想出现在系统相册内。
解决办法其实在官方文档中已经写了。
https://developer.android.com/guide/topics/data/data-storage.html
这里简单说一下,当不需要被 MediaStore 扫描的目录下,创建一个名为 .nomedia
的空文件,它将阻止媒体扫描程序读取这个目录下的媒体文件。也就无法通过 MediaStore 分享给其他程序。
当然,一些重要的文件,依然建议放在自己的私有目录下。
关于在 MediaStore 刷新图片,本文基本上就算是讲清楚了。我推荐的方法,是使用 MediaScannerConnection 来实现。
你看了本文,还有什么更多的问题可以在留言区讨论,如果觉得好,可以这篇文章,分享给你需要的朋友们。
今天在公众号后台回复成长『成长』,将会得到我整理的一些学习资料,也能回复『加群』,一起学习进步。
推荐阅读:
漫画:程序员,你能“管理”好你的产品经理吗?
App 多语言翻译,机器翻译也能快如闪电!
2017 最权威区块链报告(内含下载)
Google 的 Flutter 学习资料!
远程控制智能电视,方案已开源!