記一個 Andorid 生成文件失敗的bug

Android生成文件失敗:java.lang.IllegalStateException:Failed to build unique file: /storage/emulated/0/...

1.問題來源

App 調用相機拍照,中間有一些處理過程,然後將這張照片插入系統圖片數據庫中。

MediaStore.Images.Media.insertImage(getContentResolver(), photoPath, null, null);

很長一段時間,這段代碼都運行的好好的,可是突然有一天,測試妹妹報告一個bug,拍照之後,保存圖片失敗了,並且在 Gallery 中也看不到這張圖片。
幸運地是,專業的測試妹妹,抓到了Log,很快找到了錯誤地方:

 E DatabaseUtils: Writing exception to parcel
 E DatabaseUtils: java.lang.IllegalStateException: Failed to build unique file: /storage/emulated/0/Pictures/Image.jpg bucket_display_name=Pictures volume_name=external_primary date_modified=null date_expires=null _display_name=Image.jpg mime_type=image/jpeg _data=/storage/emulated/0/Pictures/Image.jpg _size=null is_trashed=0 is_pending=0 bucket_id=-1617409521 relative_path=Pictures/
 E DatabaseUtils: 	at com.android.providers.media.MediaProvider.ensureFileColumns(MediaProvider.java:2624)
 E DatabaseUtils: 	at com.android.providers.media.MediaProvider.ensureUniqueFileColumns(MediaProvider.java:2368)
 E DatabaseUtils: 	at com.android.providers.media.MediaProvider.updateInternal(MediaProvider.java:5265)
 E DatabaseUtils: 	at com.android.providers.media.MediaProvider.update(MediaProvider.java:4953)
 E DatabaseUtils: 	at android.content.ContentProvider$Transport.update(ContentProvider.java:457)
 E DatabaseUtils: 	at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:230)
 E DatabaseUtils: 	at android.os.Binder.execTransactInternal(Binder.java:1159)
 E DatabaseUtils: 	at android.os.Binder.execTransact(Binder.java:1123)
 W MediaStore: Failed to insert image
 W MediaStore: java.lang.IllegalStateException: Failed to build unique file: /storage/emulated/0/Pictures/Image.jpg bucket_display_name=Pictures volume_name=external_primary date_modified=null date_expires=null _display_name=Image.jpg mime_type=image/jpeg _data=/storage/emulated/0/Pictures/Image.jpg _size=null is_trashed=0 is_pending=0 bucket_id=-1617409521 relative_path=Pictures/
 W MediaStore: 	at android.os.Parcel.createExceptionOrNull(Parcel.java:2381)
 W MediaStore: 	at android.os.Parcel.createException(Parcel.java:2357)
 W MediaStore: 	at android.os.Parcel.readException(Parcel.java:2340)
 W MediaStore: 	at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:190)
 W MediaStore: 	at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
 W MediaStore: 	at android.content.ContentProviderProxy.update(ContentProviderNative.java:649)
 W MediaStore: 	at android.content.ContentResolver.update(ContentResolver.java:2356)
 W MediaStore: 	at android.content.ContentResolver.update(ContentResolver.java:2318)
 W MediaStore: 	at android.provider.MediaStore$Images$Media.insertImage(MediaStore.java:2097)
 W MediaStore: 	at android.provider.MediaStore$Images$Media.insertImage(MediaStore.java:2059)

2.問題分析

顯然這個異常並不是我們 App 中定義的,從日誌可以看到程序執行到 MediaStore.Images.Media.insertImage() 方法出現異常了,異常是 MediaProvider.ensureFileColumns 拋出的,關鍵字是 Failed to build unique file
那隻能查找源碼了。在源碼中搜 MediaProviderensureFileColumns 方法中,的確看到拋出了上述異常。

可以看到,這個一樣應該是 FileUtils.buildUniqueFile() 或者 FileUtils.buidlNonUniqueFile() 方法拋出的。查看源碼,我們發現是在 FileUtils.buildUniqueFile() 調用 FileUtils.buildUniqueFileWithExtension() 方法,拋出了異常。

看下這個方法大致就可以明白,同一名稱的文件會被系統在默認添加(1...)等數字用以標識,例如我有一個aa.txt文件,當我要再次生成aa.txt時,系統會幫我生成aa (1).txt文件,再生成則是aa (2).txt。當括號中的名稱數量大於32(含32,也就是說同一文件名的數量超過33個時)後就拋異常。

至此,破案了。
我們在調用

MediaStore.Images.Media.insertImage(getContentResolver(), photoPath, null, null);

方法時,第三個參數,插入數據庫的文件的名稱,傳的 null, 系統會默認生成一個文件名,Image.jpg, 再次插入的則爲 Image(1).jpg...

3.修改方案

修改方案明瞭了,只需要再插入系統數據庫時,指定文件名。並且要儘量保持唯一。看來,系統接口設計很嚴謹,接口中每一個參數都是有意義的,比如該第四個參數,description,文件的描述,最好也要賦值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章