Android 7.0之訪問檔案的許可權和FileProvider類
轉載請標明出處:
http://blog.csdn.net/djy1992/article/details/72533310
本文出自:【奧特曼超人的部落格】
許可權更改
Android 7.0 做了一些許可權更改,這些更改可能會影響您的應用。Android7.1的可以看這篇文章:
《android 7.1懸浮窗系統許可權問題》
系統許可權更改
目錄許可權
為了提高私有檔案的安全性,面向 Android 7.0 或更高版本的應用私有目錄被限制訪問 (0700)。此設定可防止私有檔案的後設資料洩漏,如它們的大小或存在性。此許可權更改有多重副作用:
私有檔案的檔案許可權不應再由所有者放寬,為使用 MODE_WORLD_READABLE 和或 MODE_WORLD_WRITEABLE 而進行的此類嘗試將觸發 SecurityException。
- 注:迄今為止,這種限制尚不能完全執行。應用仍可能使用原生 API 或 File API 來修改它們的私有目錄許可權。但是,我們強烈反對放寬私有目錄的許可權。
檔案許可權更改
FileUriExposedException 異常
傳遞軟體包網域外的 file:// URI 可能給接收器留下無法訪問的路徑。
因此,嘗試傳遞 file:// URI 會觸發 FileUriExposedException。分享私有檔案內容的推薦方法是使用 FileProvider。
DownloadManager 不再按檔名分享私人儲存的檔案。舊版應用在訪問 COLUMN_LOCAL_FILENAME 時可能出現無法訪問的路徑。
面向 Android 7.0 或更高版本的應用在嘗試訪問 COLUMN_LOCAL_FILENAME 時會觸發 SecurityException。
通過使用
DownloadManager.Request.setDestinationInExternalFilesDir()
或者
DownloadManager.Request.setDestinationInExternalPublicDir()
將下載位置設定為公共位置的舊版應用仍可以訪問 COLUMN_LOCAL_FILENAME 中的路徑,但是我們強烈反對使用這種方法。
對於由 DownloadManager 公開的檔案,首選的訪問方式是使用ContentResolver.openFileDescriptor()
在應用間共享檔案 StrictMode API
對於面向 Android 7.0 的應用,Android 框架執行的 StrictMode API 政策禁止在您的應用外部公開 file:// URI。
如果一項包含檔案 URI 的 intent 離開您的應用,則應用出現故障,並出現 FileUriExposedException 異常。
要在應用間共享檔案,您應傳送一項 content:// URI,並授予 URI 臨時訪問許可權。
進行此授權的最簡單方式是使用 FileProvider 類。如需瞭解有關許可權和共享檔案的詳細資訊,請谷歌開發中的《共享檔案許可權操作方法》
設定檔案共享
瞭解如何設定您的應用程式共享檔案。共享檔案
瞭解如何通過生成檔案的內容URI,向URI授予訪問許可權,並嚮應用程式傳送URI,從而向另一個應用程式提供檔案。請求共享檔案
瞭解如何請求另一個應用程式共享的檔案,接收該檔案的內容URI,並使用內容URI開啟檔案。獲取檔案資訊
學習如何應用程式可以使用由fileprovider檢索包括MIME型別和檔案大小,檔案資訊生成的內容URI。
FileProvider
FileProvider,是Android 7.0新增的一個類,該類位於v4包下的android.support.v4.content.FileProvider,使用方法和ContentProvider類似,操作步驟如下:
一、在資原始檔夾res/xml下新建file_provider.xml檔案,檔案宣告許可權請求的路徑,程式碼如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--對應外部記憶體卡根目錄:Environment.getExternalStorageDirectory()-->
<external-path name="ext_root" path="/" />
<!--指定檔案儲存的區塊和區塊的相對路徑-->
<files-path name="my_path" path="images/"/>
</paths>
屬性說明:
指定檔案儲存的區塊和區塊的相對路徑
name
:是一個虛設的檔名(可以自由命名),對外可見路徑的一部分,隱藏真實檔案目錄path
:是一個相對目錄,相對於當前的子標籤根目錄,:表示內部記憶體卡根目錄,對應根目錄等價於Context.getFilesDir(),完整路徑:/data/use/mnt/sdcard/com.immqy.dujinyang/files
這裡的path要對應上相關路徑,這裡是想根目錄,不指定特別目錄,所以直接用"/"
即可。
<paths>
根標籤下可以新增的子標籤也是有限的,除了上述的提到的這個子標籤外,還包括下面幾個:
<cache-path>
,表示應用預設快取根目錄,對應根目錄等價於getCacheDir(),檢視完整路徑:/data/user/0/cn.teachcourse.demos/cache<external-path>
,表示外部記憶體卡根目錄,對應根目錄等價於
Environment.getExternalStorageDirectory(), 路徑:/storage/emulated/0<external-files-path>
,表示外部記憶體卡根目錄下的APP公共目錄,對應根目錄等價於
Context的getExternalFilesDir(String) , 路徑:
/storage/emulated/0/Android/data/com.immqy.www<external-cache-path>
,表示外部記憶體卡根目錄下的APP快取目錄,對應根目錄等價於Context.getExternalCacheDir(),路徑:
/storage/emulated/0/Android/data/cn.teachcourse.demos/cache
示例:
最終,在file_provider.xml檔案中,新增上述5種型別的臨時訪問許可權的檔案目錄,程式碼如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--
1、name對應的屬性值,開發者可以自由定義;
2、path對應的屬性值,當前external-path標籤下的相對路徑
比如:/storage/emulated/0/92Recycle-release.apk
sdcard路徑:/storage/emulated/0(WriteToReadActivity.java:176)
at cn.teachcourse.nougat.WriteToReadActivity.onClick(WriteToReadActivity.java:97)
at android.view.View.performClick(View.java:5610)
at android.view.View$PerformClick.run(View.java:22265)
相對路徑:/
-->
<!--1、對應內部記憶體卡根目錄:Context.getFileDir()-->
<files-path
name="int_root"
path="/" />
<!--2、對應應用預設快取根目錄:Context.getCacheDir()-->
<cache-path
name="app_cache"
path="/" />
<!--3、對應外部記憶體卡根目錄:Environment.getExternalStorageDirectory()-->
<external-path
name="ext_root"
path="pictures/" />
<!--4、對應外部記憶體卡根目錄下的APP公共目錄:Context.getExternalFileDir(String)-->
<external-files-path
name="ext_pub"
path="/" />
<!--5、對應外部記憶體卡根目錄下的APP快取目錄:Context.getExternalCacheDir()-->
<external-cache-path
name="ext_cache"
path="/" />
</paths>
所以生成指定檔案的Content URI的步驟:
- 確定上述用哪一種 (5種型別)
- 明確指定檔案的完整路徑(包括目錄、檔名)
- 呼叫getUriForFile()方法生成
除了普通的授權,還有一種是Intent傳送對外授權,對外提供可訪問的Content URI,在重寫的startActivityResult()方法中獲取授予臨時許可權的Content URI或向使用者提供可訪問的介面來獲取檔案,後面的這種方式獲取檔案後轉換成Content URI。
- 請求授予訪問公共目錄的許可權,程式碼如下
if (Build.VERSION.SDK_INT > 23) {
/**Android 7.0以上的方式**/
mStorageManager = this.getSystemService(StorageManager.class);
StorageVolume storageVolume = mStorageManager.getPrimaryStorageVolume();
Intent intent = storageVolume.createAccessIntent(Environment.DIRECTORY_PICTURES);
startActivityForResult(intent, REQUEST_DCODE_GRAINT_URI);
}
- 重寫的startActivityResult()方法中獲取授予臨時許可權的Content URI,程式碼如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_DCODE_GRAINT_URI:
updateDirectoryEntries(data.getData());
Log.d(TAG, "onActivityResult:Uri= "+data.getData());
break;
}
}
- 查詢Environment.DIRECTORY_PICTURES目錄,返回的Content URI包含的檔案和檔案型別相關資訊,程式碼如下:
private static final String[] DIRECTORY_SELECTION = new String[]{
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE,
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
};
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void updateDirectoryEntries(Uri uri) {
ContentResolver contentResolver = this.getContentResolver();
Uri docUri = DocumentsContract.buildDocumentUriUsingTree(uri,
DocumentsContract.getTreeDocumentId(uri));
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri,
DocumentsContract.getTreeDocumentId(uri));
try (Cursor docCursor = contentResolver
.query(docUri, DIRECTORY_SELECTION, null, null, null)) {
while (docCursor != null && docCursor.moveToNext()) {
mPath_tv.setText(docCursor.getString(docCursor.getColumnIndex(
DocumentsContract.Document.COLUMN_DISPLAY_NAME)));
}
}
try (Cursor childCursor = contentResolver
.query(childrenUri, DIRECTORY_SELECTION, null, null, null)) {
while (childCursor != null && childCursor.moveToNext()) {
String fileName = childCursor.getString(childCursor.getColumnIndex(
DocumentsContract.Document.COLUMN_DISPLAY_NAME));
String mimeType = childCursor.getString(childCursor.getColumnIndex(
DocumentsContract.Document.COLUMN_MIME_TYPE));
Log.e(TAG, "Directory: "+fileName+"\n"+mimeType);
}
}
}
生成Content URI的臨時授權:
上一步並沒有獲得指定檔案的讀寫許可權,想要獲得檔案的讀寫許可權需要呼叫Context.grantUriPermission(package, Uri, mode_flags)方法,該方法向指定包名的應用程式申請獲得讀取或者寫入檔案的許可權,引數說明如下:
package
:指定應用程式的包名,Android Studio真正的包名指build.gradle宣告的applicationId屬性值;getPackageName()
:指AndroidManifest.xml檔案宣告的package屬性值,如果兩者不一致,就不能提供getPackageName()獲取包名,否則報錯!Uri
:指定請求授予臨時許可權的URI,例如:contentUrimode_flags
:指定授予臨時許可權的型別,選擇其中一個常量或兩個:Intent.FLAG_GRANT_READ_URI_PERMISSION,Intent.FLAG_GRANT_WRITE_URI_PERMISSION
授予檔案的臨時讀取或寫入許可權,如果不再需要了,TeachCourse該如何撤銷授予呢?撤銷許可權有兩種方式:第一種:通過呼叫revokeUriPermission()撤銷,第二種:重啟系統後自動撤銷
File imagePath = new File(Environment.getExternalStorageDirectory(), "download");
File newFile = new File(imagePath, "miqiyun_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.immqy.karl-dujinyang", newFile);
二、AndroidManifest.xml 新增元件 provider 相關資訊,類似元件 activity ,指定resource屬性引用上一步建立的xml檔案(後面會詳細介紹各個屬性的用法),程式碼如下:
<!-- 定義FileProvider -->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="@string/pack_name"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider" />
</provider>
屬性說明:
android:name
:對應屬性值:android.support.v4.content.FileProvider或者子類完整路徑android:authorities
:對應屬性值是一個常量,通常定義的方式packagename.fileprovider,如:com.immqy.dujinyang.fileproviderandroid:exported
:對應屬性值是一個boolean變數,設定為falseandroid:grantUriPermissions
:對應屬性值也是一個boolean變數,設定為true,允許獲得檔案臨時的訪問許可權關聯res/xml資料夾下建立的file_provider.xml檔案,需要在標籤內,新增子標籤,設定標籤的屬性值,包括:
- 標籤
android:name
:對應屬性值是一個固定的系統常量android.support.FILE_PROVIDER_PATHS
- 標籤
android:resource
:對應屬性值指向我們的xml檔案@xml/file_provider
三、程式碼上做動態許可權申請,使用getUriForFile()和grantUriPermission(),程式碼如下:
if (Build.VERSION.SDK_INT > 23) {
/**Android 7.0以上的方式**/
Uri contentUri = getUriForFile(this, getString(R.string.pack_name), file);
grantUriPermission(getPackageName(), contentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
}
注意:申請的是FLAG_GRANT_WRITE_URI_PERMISSION
測試:修改build.gradle檔案compileSdkVersion大於或等於24,targetSdkVersion等於24,然後使用Android 7.0模擬器執行Demo,完成無誤。
|| 版權宣告:本文為博主杜錦陽原創文章,轉載請註明出處。
相關文章
- Android7.0檔案訪問許可權Android訪問許可權
- win共享檔案沒有許可權訪問怎麼辦 win10共享檔案許可權訪問的方法Win10
- linux 檔案許可權 s 許可權和 t 許可權解析Linux
- java的訪問許可權Java訪問許可權
- android自定義訪問許可權permissionAndroid訪問許可權
- android:各種訪問許可權PermissionAndroid訪問許可權
- 採坑之Android手機訪問相簿許可權問題Android
- Linux的檔案存取許可權和0644許可權Linux
- 自定義Android應用的訪問許可權Android訪問許可權
- 改變檔案或目錄的訪問許可權命令(轉)訪問許可權
- Java中類的成員方法和變數的訪問許可權Java變數訪問許可權
- Linux改變檔案或目錄的訪問許可權命令Linux訪問許可權
- hdfs檔案本地許可權問題
- linux檔案許可權問題Linux
- 修改檔案的許可權
- 關於oracle檔案許可權的問題Oracle
- Android系統許可權和root許可權Android
- Linux命令:改變檔案或目錄的訪問許可權(轉)Linux訪問許可權
- ClickHouse學習系列之六【訪問許可權和賬戶管理】訪問許可權
- Java 訪問許可權控制(6)Java訪問許可權
- mongoDB 3.0 安全許可權訪問MongoDB
- Swift4.0 訪問許可權Swift訪問許可權
- AndroidPermission訪問許可權大全Android訪問許可權
- public, private, protected 訪問許可權訪問許可權
- Laravel 日誌檔案許可權問題Laravel
- 怎樣提高Windows Azure Cloud Service中的WebRole的檔案訪問許可權WindowsCloudWeb訪問許可權
- android 許可權問題Android
- C++中封裝和繼承的訪問許可權C++封裝繼承訪問許可權
- 【LIUNX】目錄或檔案許可權,許可權授予
- Docker構建的tomcat工程上傳檔案,訪問報403許可權問題DockerTomcat
- DRF內建許可權元件之自定義許可權管理類元件
- win10共享檔案沒有許可權訪問該怎麼辦Win10
- Linux檔案許可權Linux
- chomd檔案許可權授予
- oracle許可權整理檔案Oracle
- Linux 檔案許可權Linux
- 使用nginx控制ElasticSearch訪問許可權NginxElasticsearch訪問許可權
- Think IN JAVA --------JAVA訪問許可權控制Java訪問許可權