三種方法,重新整理 Android 的 MediaStore!讓你儲存的圖片立即出現在相簿裡!

承香墨影發表於2018-04-25

三種方法,重新整理 Android 的 MediaStore!讓你儲存的圖片立即出現在相簿裡!

公眾號原標題:測試:“系統相簿裡怎麼看不到我剛儲存的圖片,是我操作不對嗎?”

一、序

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

App 內,建立一個檔案並儲存檔案到本地的需求,是很常見的 I/O 操作。而如果這個檔案變成了一張圖片,那你涉及到的就不僅僅是一個 I/O 操作了,還需要考慮如何更新 MediaStore,這樣才可以在系統相簿中,看到它。

這裡說的 MediaStore,本質上是 Android 維護的一個檔案系統的資料庫,它記錄了當前磁碟上所有的檔案索引,我們可以通過它,快速的查詢當前系統的檔案。

MediaStore 重新整理的時機是不一定的,也就是說,儲存的一張圖片檔案,MediaStore 並不會立即重新整理檔案系統,將此檔案索引記錄下來。而系統本身是存在一些自動重新整理 MediaStore 的時機,例如:重啟手機。表現就是,當你儲存了一張圖片到本地資料夾中之後,通過檔案管理器類的 App,可以在目錄下找到這漲照片,但是在系統相簿中,是無法立即看到它的,同時你想用諸如 微信、QQ 去分享這張圖片的時候,也是找不到的。所以在我們儲存圖片檔案之後,去觸發系統重新整理 MediaStore 就尤為重要了。

本文就來講講,如何在儲存圖片之後,重新整理系統 MediaStore 那些事。

重新整理系統 Media 通常有如下幾種方式:

  • 通過操作 MediaStore 類。
  • 傳送廣播更新 MediaStore。
  • 通過操作 MediaScannerConnection 類。

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

二、操作 MediaStore

這裡說的操作 MediaStore,實際是操作它的一個內部類 MediaStore.Images.Media,它提供了幾個 inserImage () 方法,供我們向 MediaStore 中插入圖片資料,併產生一個縮圖。

三種方法,重新整理 Android 的 MediaStore!讓你儲存的圖片立即出現在相簿裡!

這個方法傳遞進去的是一個 Bitmap 物件,其餘的 titledescription 分別是圖片檔案的名稱和一段描述。

舉個 Kotlin 的例子:

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

使用 inserImage() 方法,不需要我們指定路徑,會自動將圖片儲存至 Picture 目錄下。它也不支援我們指定路徑。如果我們對圖片儲存的路徑沒有要求,並且儲存的是一個 Bitmap 物件,此方法是非常的方便的。

細心的朋友可能已經發現了 inserImage() 還有一個其他的過載方法,支援我們傳遞進去一個圖片檔案路徑,不過我並不推薦使用這個方法,因為它會將原本的圖片,再 Copy 一份,到 Picture 目錄下,也就是說你最終在磁碟上會得到兩張相同的圖片。

三種方法,重新整理 Android 的 MediaStore!讓你儲存的圖片立即出現在相簿裡!

這一點,看原始碼是最清晰的。它首先使用 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 的 MediaStore!讓你儲存的圖片立即出現在相簿裡!

有興趣可以仔細閱讀原始碼,這裡是 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() 方法,你就會知道這樣的方式是行不通的。

三種方法,重新整理 Android 的 MediaStore!讓你儲存的圖片立即出現在相簿裡!

在這裡可以看到,當你傳遞進去的檔案路徑,指向的檔案不存在的時候,會直接 return 出去了,就執行不到重新整理的邏輯裡。

所幸的是,我在 DownloadManager 類中,找到了重新整理刪除檔案的解決辦法,依然是通過 ContentResolver 來解決。

三種方法,重新整理 Android 的 MediaStore!讓你儲存的圖片立即出現在相簿裡!

這裡通過 ContentResolver 來向 MediaStore 中發起一個刪除檔案的操作,只需要傳遞進去一個檔案的絕對路徑即可。

四、操作 MediaScannerConnection 類

4.1 使用 MediaScannerConnection

重新整理 MediaStore 還有一個最通用也是我推薦的一個方法,那就是使用 MediaScannerConnection 進行操作。

不同於 MediaStore.Image.Media 和廣播的方式,使用 MediaScannerConnection 不僅可以儲存檔案,還可以指定檔案路徑,最好的就是,它還支援重新整理完成的回撥。

如果我們對時序有要求,並且需要制定檔案儲存路徑的話,最好的方式就是直接使用 MediaScannerConnection 類進行操作,並且這也應該是相容最好的方式。

這裡我們主要是利用 MediaScannerConnection 類的 scanFile() 方法進行觸發掃描。

三種方法,重新整理 Android 的 MediaStore!讓你儲存的圖片立即出現在相簿裡!

通過 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() 方法的實現。

三種方法,重新整理 Android 的 MediaStore!讓你儲存的圖片立即出現在相簿裡!

scanFile() 裡,建立了一個 MediaScannerConnection 並呼叫了 connect() 方法。接下來我們繼續看 connect() 方法。

三種方法,重新整理 Android 的 MediaStore!讓你儲存的圖片立即出現在相簿裡!

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 類,邏輯都在這裡面。

三種方法,重新整理 Android 的 MediaStore!讓你儲存的圖片立即出現在相簿裡!

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!讓你儲存的圖片立即出現在相簿裡!

相關文章