1. 程式人生 > >關於Android 7.0相機FileUriExposedException解決

關於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);

複製程式碼

到這裡就結束了,我也是剛剛踩完相機的坑,所以來一篇部落格來記錄這個經歷,由於該內容全部都是用記事本手敲的,所以或多或少可能會有一些拼寫錯誤之類的,歡迎在評論區指出錯誤,謝謝!