android 7.0以上共享檔案(解決呼叫系統照相和圖片剪切出現的FileUriExposedException崩潰問題)
阿新 • • 發佈:2019-02-15
在android7.0開始試共享“file://”URI 將會導致引發 FileUriExposedException。 如果應用需要與其他應用共享私有檔案,則應該使用 FileProvider, FileProvider的 getUriForFile() 方法可以產生一個檔案的content URI, FLAG_GRANT_READ_URI_PERMISSION,FLAG_GRANT_WRITE_URI_PERMISSION標記可以給客戶端一個指定檔案的臨時訪問許可權。下面內容將一步步介紹FileProvider的使用
<files-path name="name" path="path" /> <files-path>標籤代表Context#getFilesDir()返回目錄
<cache-path name="name" path="path" /> <files-path>標籤代表Context#getCacheDir()返回目錄
<external-path name="name" path="path" /> <files-path>標籤代表Environment.getExternalStorageDirectory()返回目錄
<external-files-path name="name" path="path" /> <files-path>標籤代表Context#getExternalFilesDir(null)返回目錄
<external-cache-path name="name" path="path" /> <files-path>標籤代表 Context#getExternalCacheDir()返回目錄
上面的程式碼中就是把Context#getFilesDir()/imges/路徑對映到my_images虛擬路徑中用於共享檔案,這樣我們如果想用共享
該路徑下的default_image.jpg檔案時,用FileProvider為該檔案生成的content URI就是長這樣的:content://com.example.myapp.fileprovider/my_images/default_image.jpg,com.example.myapp.fileprovider為android:authorities設的值,my_images就是路徑部分了。
1.註冊FileProvider並配置共享路徑
FileProvider是實現了ContentProvider的一個子類,其實也就是一個ContentProvider,首先必須在清單檔案中用 <provider> 註冊FileProvider
其中android:name指的是provider的全名,這裡就也就是FileProvider。 android:authorities這個屬性很重要,他的設定的值就是下面我們用FileProvider為檔案生產的content URI的授權部分(content URI包含授權部分和路徑部分,如content://user_dictionary/words,user_dictionary是授權部分,words是路徑部分),在程式碼中getUriForFile()是就會用到它,它的命名規範一般是 應用包名.fileprovider(如上的com.example.myapp.fileprovider)。 android:grantUriPermissions代表該FileProvider是否有權生成content URI,第二個重要的是 <meta-data>標籤下的內容,android:name="android.support.FILE_PROVIDER_PATHS"是固定的,指示我們要共享的檔案路徑,android:resource指定了一xml配置檔案(如上則是filepaths.xml,注意在設定android:resourc不要加.xml字尾),在該配置檔案中定了要共享檔案的對映,該配置檔案的路徑是res/xml/filepaths.xml。具體如下:<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp"> <application ...> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.myapp.fileprovider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider> ... </application> </manifest>
根標籤是paths標籤,<paths>標籤可以是一下子標籤的一個或幾個<paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="my_images" path="images/"/> </paths>
<files-path name="name" path="path" /> <files-path>標籤代表Context#getFilesDir()返回目錄
<cache-path name="name" path="path" /> <files-path>標籤代表Context#getCacheDir()返回目錄
<external-path name="name" path="path" /> <files-path>標籤代表Environment.getExternalStorageDirectory()返回目錄
<external-files-path name="name" path="path" /> <files-path>標籤代表Context#getExternalFilesDir(null)返回目錄
<external-cache-path name="name" path="path" /> <files-path>標籤代表 Context#getExternalCacheDir()返回目錄
上面的程式碼中就是把Context#getFilesDir()/imges/路徑對映到my_images虛擬路徑中用於共享檔案,這樣我們如果想用共享
該路徑下的default_image.jpg檔案時,用FileProvider為該檔案生成的content URI就是長這樣的:content://com.example.myapp.fileprovider/my_images/default_image.jpg,com.example.myapp.fileprovider為android:authorities設的值,my_images就是路徑部分了。
2.在程式碼中用FileProvider實現檔案共享
共享的檔案的content URI是通過Intent配合FLAG_GRANT_READ_URI_PERMISSION標記傳出去的,FLAG_GRANT_READ_URI_PERMISSION,FLAG_GRANT_WRITE_URI_PERMISSION標記給客戶端一個臨時的讀寫許可權。下面就以啟動拍照程式和圖片剪下程式為例
public void startCapture() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File file = new File(getFilesDir(), "images/default.jpg");
//android 7.0以前可以fromFile得到“file://”URI
// Uri uri = Uri.fromFile(file);
//7.0以後必須這樣
//呼叫FileProvider.getUriForFile,傳入Context物件,authorities(上文android:authorities設定的值)和File物件生產content URI
Uri uri = FileProvider.getUriForFile(this, "com.example.myapp.fileprovider", file);
//設定拍照後儲存路徑
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
//新增FLAG_GRANT_WRITE_URI_PERMISSION標記給目標程式提供臨時讀寫許可權,改臨時許可權會在目標程式的任務棧結束後失效
//如果只想要目標程式只擁有讀限權,可只設置FLAG_GRANT_READ_URI_PERMISSION標記
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(intent,0);
}
public void startCrop() {
Intent intent = new Intent("com.android.camera.action.CROP");
File output = new File(getFilesDir(), "images/default.jpg");
//呼叫FileProvider.getUriForFile,傳入Context物件,authorities(上文android:authorities設定的值)和File物件生產content URI
Uri uri = FileProvider.getUriForFile(this, "com.example.myapp.fileprovider", output);
//設定要裁剪的圖片
intent.setDataAndType(uri, "image/*");
intent.putExtra("crop", "true");
//設定裁剪引數
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 200);
intent.putExtra("outputY", 200);
//新增臨時許可權標記
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
//設定檔案輸出uri
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
intent.putExtra("return-data", true);
startActivityForResult(intent, 1);
}
當然,傳遞intent方法不僅是啟動其他Activity,還有其他,比如其他程式用startActivityForResult啟動我們的Acitivy我們可以通過setResult(int resultCode, Intent data)方法把Intent回傳。這樣他們在onActivityResult中就可拿到intent取出content uri了,下面看下怎麼使用其他程式提供的content uri
3.客戶端訪問共享檔案
//假設我們已經setResult(int resultCode, Intent data)方法把content uri放到intent中,那我們可以這樣獲取
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Uri returnUri = data.getData();
try {
//獲取ParcelFileDescriptor物件,"rw"代表可讀寫,"r"代表只讀
ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(returnUri, "rw");
//獲取FileDescriptor物件
if (pfd != null) {
FileDescriptor fd = pfd.getFileDescriptor();
//獲取檔案輸入輸出流,這樣我們就可操作共享檔案了
FileOutputStream fos = new FileOutputStream(fd);
FileInputStream fis = new FileInputStream(fd);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
至此本文完,相信呼叫系統照相和圖片剪切出現的FileUriExposedException崩潰的問題可以解決了吧