天天看點

Android 7.0調用系統相機(檔案通路crash android.os.FileUriExposedException)

最近項目中做圖檔上傳的功能中遇到一個問題,就是7.0的手機上調用系統相機指定圖檔路徑的情況下回crash,報錯android.os.FileUriExposedException uri暴露的錯誤。

Android7.0對應用共享檔案這塊做了一些強制性的要求。從7.0開始,android架構預設執行StrictMode,禁止向應用外公開file://的URI,也就是說必須将此URI轉換為content://,才能給别的應用使用,并授予URI臨時通路權限。

知道錯誤原因了,那麼解決辦法就好說了:

第一種辦法:繞過7.0的檔案權限檢查

既然預設執行了StrictMode,那最簡單的辦法就是禁止執行StrictMode,繞過7.0的檔案權限檢查,也是最簡單的一種方式,隻需要添加少量代碼,而可以不改動原來的代碼

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
            StrictMode.setVmPolicy(builder.build());
 }   
           
這段代碼可以寫在Application的onCreate()裡,也可以寫在需要調用檔案通路的地方。
當然這是一種比較流氓的方法,放棄了設計者的初衷;
           

第二種辦法:使用 FileProvider類授權

使用方法:

1.在manifest.xml中添加provider

<application
    ...>
    <provider
        android:authorities="你的包名.fileProvider"
         android:name="android.support.v4.content.FileProvider"
         android:grantUriPermissions="true"
         android:exported="false">
         <meta-data
             android:name="android.support.FILE_PROVIDER_PATHS"
             android:resource="@xml/file_paths"/>
     </provider>
</application>
           

2.在res檔案夾下建立檔案夾xml,在xml裡建立檔案file_paths.xml,

file_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path path="Android/data/包名/" name="files_root" />
    <external-path path="." name="external_storage_root" />
    <cache-path name="cache_paths" path="cache/"/>
</paths>
           

files-path代表的根目錄: Context.getFilesDir()

external-path代表的根目錄: Environment.getExternalStorageDirectory()

cache-path代表的根目錄: getCacheDir()

path路徑當然也可以自己定義,與實際檔案路徑存在就行;“.”類似于通配符,使用所有的路徑

例如檔案路徑“/storage/emulated/0/Android/data/包名/cache/1512556077575.jpg”

7.0的URI:”content://包名.fileProvider/files_root/cache/1512556077575.jpg”

其中“包名.fileProvider”為在manifest.xml中配置的authorities。

3.在調用相機時:

private void openCamera() {
        String photoName = Calendar.getInstance().getTimeInMillis() + ".jpg";
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        file = new File(getExternalCacheDir(), photoName);//file是一個全局變量
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        //7.0 以上
            picUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileProvider", file);
        }else {
        //7.0 以下
            picUri = Uri.fromFile(file);
        }
        intent.putExtra(MediaStore.EXTRA_OUTPUT, picUri);
        startActivityForResult(intent, CAMERA_REQUEST);
    }
           

其中FileProvider.getUriForFile()的第二個參數必須與在在manifest.xml中配置的authorities一緻,也可以不使用包名,但必須保持一緻。

最終7.0上:

file:/storage/emulated/0/Android/data/包名/cache/1512556077575.jpg

picUri :content://包名.fileProvider/files_root/cache/1512556077575.jpg

然後就可以正常打開相機了。

4.拍照傳回

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode != RESULT_OK) {
            return;
        }
        switch (requestCode){
            case CAMERA_REQUEST://拍照傳回
                ivPic.setImageURI(picUri);//顯示圖檔
                //以下代碼用于擷取圖檔檔案,7.0如果也用picUri.getPath(),則得到的檔案f為空,是以通過全局變量file來擷取
              String url = picUri.getPath();
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
                    url = file.getPath();
                }
                File f = new File(url);
                System.out.println("~file bytes = " + f.length() + " b");
                 //todo
                break;
            case ALNUM_REQUEST:
                break;
        }
    }
           

關于6.0權限問題不要忘了,就不在此列出了。

此文章借鑒很多他人部落格,不在一一列舉,謝謝各位。