關於Android 7.0相機FileUriExposedException解決
在開發Android專案的時候,我們會用到相機,有些時候只是開發一個普通的掃碼,僅僅賦予一下 許可權 就好了,但是有些時候是需要拍照和從相簿中獲取照片的。 我們在Android 5.0以及5.0之前呼叫相機可以這樣寫
Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); File savePhoto = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"/test/"+System.currentTimeMillis() + ".png"); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(savePhoto)); startActivityForResult(intent,200);
這樣寫在6.0之前是完全沒有問題的,拍照也可以按照指定的路徑進行儲存,一切的一切都是OK的,除非部分機型會有問題
到了6.0,在呼叫相機就得這樣寫 當我們開發者把sdk升級到了23後,這樣寫就會存在一點缺陷。那就是許可權管理,需要動態進行獲取了。OK,然後我們改成動態獲取的,如下:
if(checkSelfPermission(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},200); return; } Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); File savePhoto = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"/test/"+System.currentTimeMillis() + ".png"); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(savePhoto)); startActivityForResult(intent,200);
這樣寫和5.0幾乎沒有區別,只是在前邊加了一個許可權校驗。因為6.0到了動態許可權時代
然而到了7.0,一切的一切都不一樣了,因為Android 7.0不允許intent帶有file://的URI離開自身的應用了,要不然會丟擲FileUriExposedException
想要在自己應用和其他應用之間共享File資料,只能使用content://的方式 所以我們在想按照5.0和6.0的方式去呼叫相機是不可行的了,我們需要在7.0的時候。把所有應用與應用之間的檔案傳遞改成content://的方式,並且還需要把該URI賦予臨時的訪問許可權,使用如下:
1.現在清單檔案裡配置一個provider
<provider android:name="android.support.v4.content.FileProvider" android:authorities="應用包名" android:exported="false" android:grantUriPermissions="true"> </provider>
2.在xml目錄下建立file_paths檔案
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="my_images" path="images/"/> </paths>
在file_paths資料夾內宣告的都是需要對外共享的目錄,比如上面的配置等於 new File(Context.getFilesDir(),"images") 路徑 paths內還可以宣告很多種型別的標籤,每一種標籤都代表了一個路徑,如下:
<files-path /> = getFilesDir() <cache-path /> = getCacheDir() <external-path /> = Environment.getExternalStorageDirectory() <external-files-path /> = Context#getExternalFilesDir(String) 或 Context.getExternalFilesDir(null) <external-cache-path /> = Context.getExternalCacheDir() <external-media-path /> = Context.getExternalMediaDirs()
我們在配置的時候。name的作用就是為了隱藏後邊的真實路徑,為了安全考慮 而後邊的path則是需要共享的路徑,用標籤所代表的路徑加上path上的值,就是完整的路徑。 寫好path檔案後,我們在回到清單檔案內繼續更新 android.support.v4.content.FileProvider 這個Provider的配置,需要把剛剛的file_paths檔案和這個provider關聯起來,如下
<provider android:name="android.support.v4.content.FileProvider" android:authorities="應用包名" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
3.寫完這些配置資訊後,我們就可以在應用內直接獲取需要共享檔案的content://URI了,如下
File imagePath = new File(Context.getFilesDir(), "images"); File newFile = new File(imagePath, "test.jpg"); Uri contentUri = FileProvider.getUriForFile(getContext(), "應用包名", newFile);
getUriForFile的第二個引數內也可以不是應用包名,只要和清單檔案內的authorities一致即可 這個contentUri的最後結果就是 content://應用包名/my_images/test.jpg 之所以會生成這個uri,相信很多同學看到這裡就明白了,因為這個uri是要對其他應用共享的,所以不能直接共享真實路徑,便衍生了FileProvider這種東西來專門生成這個Uri,他的生成規則便是 content:// 應用包名(或者是其他字串)/ 偽名 (path檔案內配置的name屬性) / 檔名
4.然後賦予臨時訪問許可權
我們可以呼叫 Context.grantUriPermission 方法來給Uri賦予許可權,呼叫 revokeUriPermission() 函式來撤銷許可權,也可以通過 Intent.setFlags() 的方式來賦予臨時訪問許可權
最終在7.0上呼叫相機就成了這個樣子,前提是file_paths檔案已經配置OK
if(checkSelfPermission(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},200); return; } Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); File imagePath = new File(Context.getFilesDir(), "images"); File newFile = new File(imagePath, "test.jpg"); Uri contentUri = FileProvider.getUriForFile(getContext(), "應用包名", newFile); intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); startActivityForResult(intent,200);
到這裡就結束了,我也是剛剛踩完相機的坑,所以來一篇部落格來記錄這個經歷,由於該內容全部都是用記事本手敲的,所以或多或少可能會有一些拼寫錯誤之類的,歡迎在評論區指出錯誤,謝謝!