三種方法,刷新 Android 的 MediaStore!讓你保存的圖片當即出如今相冊裏!

公衆號原標題:測試:「系統相冊裏怎麼看不到我剛保存的圖片,是我操做不對嗎?」html

1、序

Hi,你們好,我是承香墨影!java

App 內,建立一個文件並保存文件到本地的需求,是很常見的 I/O 操做。而若是這個文件變成了一張圖片,那你涉及到的就不只僅是一個 I/O 操做了,還須要考慮如何更新 MediaStore,這樣才能夠在系統相冊中,看到它。android

這裏說的 MediaStore,本質上是 Android 維護的一個文件系統的數據庫,它記錄了當前磁盤上全部的文件索引,咱們能夠經過它,快速的查找當前系統的文件。程序員

MediaStore 刷新的時機是不必定的,也就是說,保存的一張圖片文件,MediaStore 並不會當即刷新文件系統,將此文件索引記錄下來。而系統自己是存在一些自動刷新 MediaStore 的時機,例如:重啓手機。表現就是,當你保存了一張圖片到本地文件夾中以後,經過文件管理器類的 App,能夠在目錄下找到這漲照片,可是在系統相冊中,是沒法當即看到它的,同時你想用諸如 微信、QQ 去分享這張圖片的時候,也是找不到的。因此在咱們保存圖片文件以後,去觸發系統刷新 MediaStore 就尤其重要了。數據庫

本文就來說講,如何在保存圖片以後,刷新系統 MediaStore 那些事。緩存

刷新系統 Media 一般有以下幾種方式:bash

  • 經過操做 MediaStore 類。
  • 發送廣播更新 MediaStore。
  • 經過操做 MediaScannerConnection 類。

這三種方式,各有優缺點,咱們慢慢分析。微信

2、操做 MediaStore

這裏說的操做 MediaStore,實際是操做它的一個內部類 MediaStore.Images.Media,它提供了幾個 inserImage () 方法,供咱們向 MediaStore 中插入圖片數據,併產生一個縮略圖。ide

這個方法傳遞進去的是一個 Bitmap 對象,其他的 titledescription 分別是圖片文件的名稱和一段描述。學習

舉個 Kotlin 的例子:

MediaStore.Images.Media.insertImage(
        contentResolver,
        mShareBitmap!!,
        "image_file",
        "file")
複製代碼

使用 inserImage() 方法,不須要咱們指定路徑,會自動將圖片保存至 Picture 目錄下。它也不支持咱們指定路徑。若是咱們對圖片保存的路徑沒有要求,而且保存的是一個 Bitmap 對象,此方法是很是的方便的。

細心的朋友可能已經發現了 inserImage() 還有一個其餘的重載方法,支持咱們傳遞進去一個圖片文件路徑,不過我並不推薦使用這個方法,由於它會將本來的圖片,再 Copy 一份,到 Picture 目錄下,也就是說你最終在磁盤上會獲得兩張相同的圖片。

這一點,看源碼是最清晰的。它首先使用 BitmapFactory.decodeFile() 方法,獲得一個 Bitmap,而後再去調用保存 Bitmap 對象的 inserImage() 方法,因此咱們最終在磁盤上會有兩張如出一轍的圖片。

3、發送廣播

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 中發起一個刪除文件的操做,只須要傳遞進去一個文件的絕對路徑便可。

4、操做 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、查缺補漏

5.1 掃描其餘類型的媒體文件

在 Android 下,不只僅只有圖片,對於其餘媒體文件,使用本文介紹的方法,也是適用的。

5.2 避免某個目錄被 MediaStore 掃描

看完到這裏應該會知道,哪怕咱們什麼都不作,在手機下次重啓的時候,系統依然會去全盤掃描文件系統,更新 MediaStore。

可是有時候,咱們有一些目錄下的媒體文件,並不想讓 MediaStore 掃描到,例如在 SDCard 上緩存的圖片、圖標等,這些咱們都不想出如今系統相冊內。

解決辦法其實在官方文檔中已經寫了。

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

這裏簡單說一下,當不須要被 MediaStore 掃描的目錄下,建立一個名爲 .nomedia 的空文件,它將阻止媒體掃描程序讀取這個目錄下的媒體文件。也就沒法經過 MediaStore 分享給其餘程序。

固然,一些重要的文件,依然建議放在本身的私有目錄下。

6、小結

關於在 MediaStore 刷新圖片,本文基本上就算是講清楚了。我推薦的方法,是使用 MediaScannerConnection 來實現。

你看了本文,還有什麼更多的問題能夠在留言區討論,若是以爲好,能夠這篇文章,分享給你須要的朋友們。

今天在公衆號後臺回覆成長『成長』,將會獲得我整理的一些學習資料,也能回覆『加羣』,一塊兒學習進步。

推薦閱讀:

相關文章
相關標籤/搜索