1. 程式人生 > >android 7.0以上共享檔案(解決呼叫系統照相和圖片剪切出現的FileUriExposedException崩潰問題)

android 7.0以上共享檔案(解決呼叫系統照相和圖片剪切出現的FileUriExposedException崩潰問題)

    在android7.0開始試共享“file://”URI 將會導致引發 FileUriExposedException。 如果應用需要與其他應用共享私有檔案,則應該使用 FileProvider, FileProvider的 getUriForFile() 方法可以產生一個檔案的content URI, FLAG_GRANT_READ_URI_PERMISSION,FLAG_GRANT_WRITE_URI_PERMISSION標記可以給客戶端一個指定檔案的臨時訪問許可權。下面內容將一步步介紹FileProvider的使用

 1.註冊FileProvider並配置共享路徑

FileProvider是實現了ContentProvider的一個子類,其實也就是一個ContentProvider,首先必須在清單檔案中用 <provider> 註冊FileProvider

<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>
其中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。具體如下:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
</paths>
根標籤是paths標籤,<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崩潰的問題可以解決了吧