一、序

Hi,大家好,我是承香墨影!

App 内,创建一个文件并保存文件到本地的需求,是很常见的 I/O 操作。而如果这个文件变成了一张图片,那你涉及到的就不仅仅是一个 I/O 操作了,还需要考虑如何更新 MediaStore,这样才可以在系统相册中,看到它。

这里说的 MediaStore,本质上是 Android 维护的一个文件系统的数据库,它记录了当前磁盘上所有的文件索引,我们可以通过它,快速的查找当前系统的文件。

MediaStore 刷新的时机是不一定的,也就是说,保存的一张图片文件,MediaStore 并不会立即刷新文件系统,将此文件索引记录下来。而系统本身是存在一些自动刷新 MediaStore 的时机,例如:重启手机。表现就是,当你保存了一张图片到本地文件夹中之后,通过文件管理器类的 App,可以在目录下找到这涨照片,但是在系统相册中,是无法立即看到它的,同时你想用诸如 微信、QQ 去分享这张图片的时候,也是找不到的。所以在我们保存图片文件之后,去触发系统刷新 MediaStore 就尤为重要了。

本文就来讲讲,如何在保存图片之后,刷新系统 MediaStore 那些事。

刷新系统 Media 通常有如下几种方式:

  • 通过操作 MediaStore 类。
  • 发送广播更新 MediaStore。
  • 通过操作 MediaScannerConnection 类。

这三种方式,各有优缺点,我们慢慢分析。

二、操作 MediaStore

这里说的操作 MediaStore,实际是操作它的一个内部类 MediaStore.Image.Media,它提供了几个 inserImage () 方法,供我们向 MediaStore 中插入图片数据,并产生一个缩略图。

这个方法传递进去的是一个 Bitmap 对象,其余的 title 和 description 分别是图片文件的名称和一段描述。

举个 Kotlin 的例子:

MediaStore.Images.Media.insertImage(contentResolver,mShareBitmap!!,"image_file","file")

使用 inserImage() 方法,不需要我们指定路径,会自动将图片保存至 Picture 目录下。它也不支持我们指定路径。如果我们对图片保存的路径没有要求,并且保存的是一个 Bitmap 对象,此方法是非常的方便的。

细心的朋友可能已经发现了 inserImage() 还有一个其他的重载方法,支持我们传递进去一个图片文件路径,不过我并不推荐使用这个方法,因为它会将原本的图片,再 Copy 一份,到 Picture 目录下,也就是说你最终在磁盘上会得到两张相同的图片。

这一点,看源码是最清晰的。它首先使用 BitmapFactory.decodeFile() 方法,得到一个 Bitmap,然后再去调用保存 Bitmap 对象的 inserImage() 方法,所以我们最终在磁盘上会有两张一模一样的图片。

三、发送广播

3.1 那些广播可以更新 MediaStore

说到广播,在 Android 4.4 之前,是可以通过 ACTION_MEDIA_MOUNTED 广播,来通知系统刷新 MediaStore 的,不过假如你现在还在依赖这条广播,你会得到一个错误信息。

E/AndroidRuntime(23718): java.lang.SecurityException: Permission Denial: not allowed to send broadcast android.intent.action.MEDIA_MOUNTED from pid=23718, uid=10097

在 Android 4.4 之后,这个广播只能由系统进行广播,App 只能对该广播进行监听,在当前的系统分布环境下,这条路已经走不通了。

这样设计也很好理解,毕竟扫描全盘是非常的耗资源,所以系统肯定要把全盘扫描的权限拿在自己手里不开放出来,避免被第三方 App 滥用。

不过 Android 依然给我们提供了替代方案,那就是用 MediaScannerConnection 或者发送 ACTION_MEDIA_SCANNER_SCAN_FILE 广播。

接下来就来说说 ACTION_MEDIA_SCANNER_SCAN_FILE 这个广播。

3.2 使用广播刷新

通过广播刷新 MediaStore 的方式非常的简单,只需要指定文件路径和 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)

正常情况下,它是没有问题的,不过假如你发现它不生效,就需要检查一下你文件的路径是否传递正确。

通过查看 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/ 就无法使用,所以只要我们不硬编码文件路径,这个问题基本上也就不存在。

这里也提醒我们,一定不要在代码里,硬编码文件路径,算是一个编码规范了。

3.3 删除文件后刷新MediaStore

本文一直都在说添加新文件的时候,如何刷新 MediaStore 的问题。但是其实还涉及到另外一个问题,我们删除了一个已经被收录在 MediaStore 中的文件,怎么办?在本文里也顺便讲一下。

既然放在这一小节讲,首先想到的是,直接再发一个广播出去,刷新这个路径,但是查阅最终执行扫描前的 MediaScanner 的 scanSingleFile() 方法,你就会知道这样的方式是行不通的。

在这里可以看到,当你传递进去的文件路径,指向的文件不存在的时候,会直接 return 出去了,就执行不到刷新的逻辑里。

所幸的是,我在 DownloadManager 类中,找到了刷新删除文件的解决办法,依然是通过 ContentResolver 来解决。

这里通过 ContentResolver 来向 MediaStore 中发起一个删除文件的操作,只需要传递进去一个文件的绝对路径即可。

四、操作 MediaScannerConnection 类

4.1 使用 MediaScannerConnection

刷新 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() 方法的使用还是很简单的,没什么需要额外交代的了。

4.2 MediaScannerConnection 原理

依然是从源码中找答案,我们先来看看 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 下,不仅仅只有图片,对于其他媒体文件,使用本文介绍的方法,也是适用的。

5.2 避免某个目录被 MediaStore 扫描

看完到这里应该会知道,哪怕我们什么都不做,在手机下次重启的时候,系统依然会去全盘扫描文件系统,更新 MediaStore。

但是有时候,我们有一些目录下的媒体文件,并不想让 MediaStore 扫描到,例如在 SDCard 上缓存的图片、图标等,这些我们都不想出现在系统相册内。

解决办法其实在官方文档中已经写了。

https://developer.android.com/guide/topics/data/data-storage.html

这里简单说一下,当不需要被 MediaStore 扫描的目录下,创建一个名为 .nomedia 的空文件,它将阻止媒体扫描程序读取这个目录下的媒体文件。也就无法通过 MediaStore 分享给其他程序。

当然,一些重要的文件,依然建议放在自己的私有目录下。

六、小结

关于在 MediaStore 刷新图片,本文基本上就算是讲清楚了。我推荐的方法,是使用 MediaScannerConnection 来实现。

你看了本文,还有什么更多的问题可以在留言区讨论,如果觉得好,可以这篇文章,分享给你需要的朋友们。

三种方法,刷新 Android 的 MediaStore!让你保存的图片立即出现在相册里!相关推荐

  1. Android 实现图片轮播的三种方法,android开发者论坛

    public Object instantiateItem(ViewGroup container, int position) { // 对ViewPager页号求模取出View列表中要显示的项 p ...

  2. html清除图片上下间距,css - 三种方法解决LI和内部Img的上下间距问题

    在火狐浏览器和谷歌浏览器(qq浏览器,谷歌内核)bug类似这张图: img的高度是190*127 但是放到li中,li并没有设置高度,却和内部的图片之间上下错位. 若强行给li设置高度127,他和im ...

  3. mysql在计算机管理中的路径怎么修改_Rstudio中修改工作路径的三种方法

    原文链接: Rstudio中修改工作路径的三种方法_weixin_44370085的博客-CSDN博客_rstudio改变工作目录​blog.csdn.net Rstudio中修改工作路径的三种方法 ...

  4. java持久层用文件_JAVA中用三种方法将字符串持久化到文件中

    经常需要将数据进行持久化,而我们的文件系统是最古老也是最可靠的保存方式.这里就给出一个在JAVA中把字符串保存到文件中的例子.如下: package test.base; import java.io ...

  5. 图片转PDF格式怎么转换?这三种方法随便用

    图片怎么转换成PDF文件格式呢?大家在日常工作和学习中,也会经常使用到图片,不管是拍照记录还是截图办公,当我们想把这些图片打包发送出去的时候,怎么做才能最简单方便呢?做成文件夹发送还得压缩和解压,比较 ...

  6. 最优布线问题(三种方法)

    最优布线问题 题目 学校有n台计算机,为了方便数据传输,现要将它们用数据线连接起来.两台计算机被连接是指它们之间有数据线连接.由于计算机所处的位置不同,因此不同的两台计算机的连接费用往往是不同的. 当 ...

  7. android系统通过图片绝对路径获取URI的三种方法

    老马的技术博客 android系统通过图片绝对路径获取URI的三种方法 最近做项目要通过图片的绝对路径找到图片的URI,然后删除图片,小小总结一下获取URI的方法,亲自试验在 android 4.1. ...

  8. vue项目刷新当前页面的三种方法

    本文介绍了vue项目刷新当前页面的三种方法,本文图文并茂给大家介绍的非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下. 想必大家在刨坑vue的时候也遇到过下面情形:比如在删除或者增加一条记录的时 ...

  9. 【Android】Eclipse自动编译NDK/JNI的三种方法

    [Android]Eclipse自动编译NDK/JNI的三种方法 SkySeraph Sep. 18th  2014 Email:skyseraph00@163.com 更多精彩请直接访问SkySer ...

最新文章

  1. 计算机考试创建数据库,2013年计算机二级access创建数据库
  2. 【推荐系统】推荐系统概述
  3. UNIX 高手的 20 个习惯
  4. 接收用户的输入 Scanner类
  5. xftp实现本地与服务器的文件上传下载(windows)
  6. 网页数据分页显示php,PHP网页设计例子:用PHP3完成MySQL数据的分页显示
  7. Confluence 6 升级以后
  8. 第二章----基本语法
  9. TCP/IP协议 TCP包深入理解
  10. 一个离线的简单的 JSON 格式化编辑器
  11. mesa3d源代码阅读笔记
  12. 摩托罗拉gp3688说明书_摩托罗拉GP3688对讲机充电器电路原理分析
  13. Firefox版哔哩哔哩助手,修改自chrome1.2.1版
  14. 汇日月之精华,集天地之大成—雅点修图神器破解版
  15. MATLAB中plot函数的用法
  16. Moasure魔尺 | 精装礼盒装开箱过程
  17. 【IOS】自己写的一个舒尔特方格app
  18. go语言并发下载电影和视频,根据m3u8索引url下载视频.下载网页中的图片,可以从当前页向下搜索层数.
  19. 图像处理之二值化图像
  20. Rstudio 界面介绍

热门文章

  1. 用matlab画玫瑰花函数,网上收到的用matlab画玫瑰花的代码怎么不行啊,报告错误,求大神...
  2. js根据时间戳获取日期失败,NaN-NaN-NaN NaN:NaN:NaN
  3. oracle12c密码文件,【转】Oracle 12c 关于密码文件(password)的几个新特性
  4. JavaScript--轮播图_带计时器
  5. 云服务器和域名的购买—华为云
  6. 【Android】debug 状态下其签名文件 debug.keystore 相关(如何获得该文件,其密码,获取其sha1、MD5等)
  7. 项目日志20190707
  8. python计算中文文件字数_完成计算文本文件字数的Python代码实例
  9. PHP 中如何正确统计中文字数
  10. 移动端UI一致性解决方案