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
。
那隻能查找源碼了。在源碼中搜 MediaProvider
的 ensureFileColumns
方法中,的確看到拋出了上述異常。
可以看到,這個一樣應該是 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,文件的描述,最好也要賦值。