Android7.0權(quán)限適配

谷歌在Android7.0(API 24)做了一些權(quán)限的更改,對用戶私有目錄或私有文件的訪問和共享做了限制,具體可看官網(wǎng)描述權(quán)限更改

這里簡單解釋一下,就是涉及傳遞file://URI在7.0+上就會拋FileUriExposedException異常。下面舉兩個常見的開發(fā)場景來解釋一下傳遞file://URI具體什么操作(下面例子不涉及具體實(shí)現(xiàn),只貼出關(guān)鍵代碼做解釋)。

拍照

當(dāng)實(shí)現(xiàn)拍照需要設(shè)置拍照后照片存放目錄,一般會涉及如下代碼

Uri uri = Uri.fromFile(new File("指定你要保存的目錄"));
//設(shè)置拍照后保存目錄
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);

上面代碼最終會轉(zhuǎn)為file:///你指定的目錄/指定的照片名,在7.0以下運(yùn)行正常,7.0及以上會拋出上面所說的FileUriExposedException

應(yīng)用更新

應(yīng)用更新下載完安裝包啟動系統(tǒng)安裝界面涉及下面一句代碼

intent.setDataAndType(Uri.fromFile(new File("安裝包路徑")), "application/vnd.android.package-archive");

這里跟上面拍照例子也用到了Uri.fromFile();這個api,同樣在7.0下運(yùn)行正常,7.0及以上依舊拋FileUriExposedException異常

小結(jié)

凡是涉及Uri.fromUri(),又跟Intent相關(guān)的,如在安卓7.0及以上不做適配,就會拋異常。下面具體講解如何適配

聲明Provider
<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.example.myapplication.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

將以上復(fù)制粘貼到清單文件<application>節(jié)點(diǎn)下再修改下即可

幾點(diǎn)說明:

1.其中下面這一行中的com.example.myapplication必須改為自己項(xiàng)目的包名,包名在Module下build.gradle下

android:authorities="com.example.myapplication.fileprovider"
20180330161203887.png

2.倒數(shù)第二行涉及一個xml文件聲明,這個在下一個點(diǎn)介紹

android:resource="@xml/file_paths" 
編寫xml

Android Studio可以鼠標(biāo)點(diǎn)一個上面那個@xml/file_paths,然后按快捷鍵Alt+Enter,就會提示創(chuàng)建文件夾和文件,按回車,再點(diǎn)擊OK即可自動在項(xiàng)目res目錄下新建一個xml目錄和一個名為file_paths的xml文件(不會快捷鍵的按照目錄結(jié)構(gòu)逐個創(chuàng)建即可)


20180330161730902.png
20180330161831891.png

然后打開xml文件

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path name="" path=""/>
    <cache-path name="" path="" />
    <external-path name="" path="" />
    <external-files-path name="" path="" />
    <external-cache-path name="" path="" />
    <external-media-path name="" path="" />
</paths>

標(biāo)簽說明

<paths>為頂層標(biāo)簽,它下面可添加任意個(0,1或多個)上面出現(xiàn)的標(biāo)簽

<paths>子標(biāo)簽解釋

可以看到上面每個標(biāo)簽都含有一個name和path屬性

name:這個可以隨意寫(用于隱藏真實(shí)的目錄,后面再演示)

path:要共享的目錄,只能是目錄,不能是某個具體的文件

<files-path> 代表Context.getFilesDir()所指向的目錄

<cache-path>代表Context.getCacheDir()所指向的目錄

<external-path>代表Environment.getExternalStorageDirectory()所指向的目錄

<external-files-path>代表Context.getExternalFilesDir()所指向的目錄

<external-cache-path>代表Context.getExternalCacheDir()所指向的目錄

<external-media-path>代表Context.getExternalMediaDirs()所指向的目錄(API21+設(shè)備才能使用,所以一般不用)

以上解釋可能有的小伙伴還是一臉懵逼,下面舉個具體的下載例子說一下(不涉及代碼)

比如我現(xiàn)在把新版本安裝包下載在SD卡根目錄下的Download文件夾下,那么我的xml如下設(shè)計(jì)即可

其中path就得指定為Download,name的值可隨意寫


20180330163531905.png

關(guān)于name和path最終去向這里盜用博客里面一張圖(如涉及侵權(quán)麻煩博主找我撤回)

由下圖可知最終我們將原本的file://URI轉(zhuǎn)為了content://URI,這個正是安卓7.0要求的正確傳遞方式,而我們上面的name最終傳遞到了URI的后面作為一個隱藏的目錄,隱藏了原文件的真實(shí)目錄

20180330163650829.png

使用

上面說了這么多,都只是熱身,單純配置而已,還沒涉及一行代碼,接下來就得開始用代碼實(shí)現(xiàn)我們的功能了,這里也主要針對上面提到的兩個例子(其它例子看完可舉一反三,下面只展示修改部分,不涉及全部代碼)這里得用到一個FileProvider類,它繼承自ContentProvider,這也是為什么我們第一步得在清單文件注冊provider的原因。

拍照
//創(chuàng)建圖片存放file
File imgFile = new File("照片存放目錄");
Uri uri;
//根據(jù)當(dāng)前系統(tǒng)版本決定使用哪個api,N是Android7.0的代號
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//第一個參數(shù)是上下文,第二個參數(shù)來自清單文件,必須完全一樣,第三個參數(shù)為上面創(chuàng)建的照片file
    uri = FileProvider.getUriForFile(this, "com.example.myapplication.fileprovider", imgFile);
} else {
    //Android7.0還用原先的api
    uri = Uri.fromFile(imgFile);
}
//設(shè)置拍照后保存目錄
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
應(yīng)用更新
//創(chuàng)建安裝包file
File apkFile = new File("安裝包路徑");
Uri uri;
//根據(jù)當(dāng)前系統(tǒng)版本決定使用哪個api,N是Android7.0的代號
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    //第一個參數(shù)是上下文,第二個參數(shù)來自清單文件,必須完全一樣,第三個參數(shù)為上面創(chuàng)建的安裝包file
    uri = FileProvider.getUriForFile(context, "com.example.myapplication.fileprovider", apkFile);
} else {
    //Android7.0還用原先的api
    uri = Uri.fromFile(apkFile);
}
//當(dāng)前代碼在Activity里則下面這句可省略
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//授權(quán)Intent讀取URI和寫URI的權(quán)限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//設(shè)置拍照后保存目錄
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
intent.setDataAndType(uri,"application/vnd.android.package-archive");
context.startActivity(intent);

幾點(diǎn)說明

(1)不管是拍照還是應(yīng)用更新,Android7.0及以上都使用FileProvider提供的API獲取到最終的uri,這里要特別強(qiáng)調(diào),也是上面第二個參數(shù)必須和清單文件設(shè)置的完全一樣,即應(yīng)用包名+fileprovider,也即如下紅框中字符串


20180330235147734.png

(2)眼尖的小伙伴肯定注意到應(yīng)用更新中比拍照多了一行授權(quán)的代碼。這是因?yàn)镕ileProvider內(nèi)部進(jìn)行了一個授權(quán)的判斷,如果未授權(quán),則會拋出異常,所以才需要加上那一行授權(quán)代碼的。具體看下圖代碼和注釋

20180330235620267.png

到這里有的小伙伴又納悶了,那都拋異常了,怎么拍照不也跟隨潮流設(shè)置一波呢?那是因?yàn)榕恼赵趧?chuàng)建Intent時傳入的Action為ACTION_IMAGE_CAPTURE。添加這個值之后startActivity的時候會調(diào)用到Intent的一個方法migrateExtraStreamToClipData()方法,方法最后有段代碼是進(jìn)行判斷Action是否為我們設(shè)置的這個值,如果是,會自動為我們添加上面的權(quán)限,所以我們才不用再次手動添加權(quán)限,具體如下圖所示。由于安裝apk我們只用到讀取的權(quán)限即可,所以上面就沒多添加寫的權(quán)限


20180331000552868.png

最后小伙伴就可以愉快地適配Android7.0關(guān)于file://URI的異常啦,不知道我舉的兩個例子能否讓你能夠舉一反三呢?如發(fā)現(xiàn)內(nèi)容有誤,請指正,在此小弟先謝過了。如果不懂可留言,看到我會及時回復(fù)。

然后貼出官網(wǎng)的適配(原滋原味來一波) 適配步驟

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 上周,寫了個小demo,正好同事使用的小米手機(jī)系統(tǒng)內(nèi)核更新到7.0,遂拿來測試了一番。其中遇到的小問題,現(xiàn)在來跟大...
    monkey_who閱讀 4,892評論 0 13
  • Android7.0發(fā)布已經(jīng)有一個多月了,Android7.0在給用戶帶來一些新的特性的同時,也給開發(fā)者帶來了新的...
    東經(jīng)315度閱讀 1,427評論 0 14
  • 由于 Android 7.0 或更高版本的系統(tǒng)在國內(nèi)手機(jī)市場上的占比不是很高,很多 Android 開發(fā)人員并沒有...
    亦楓閱讀 4,427評論 1 39
  • Android7.0發(fā)布已經(jīng)有一個多月了,Android7.0在給用戶帶來一些新的特性的同時,也給開發(fā)者帶來了新的...
    善良的老農(nóng)閱讀 831評論 0 5
  • 光是想到這些天要碰到數(shù)不清的事情的時候,他就有一些頭大,可曾經(jīng)受不住這么些人的剖析。他還一直以為自己是一個堅(jiān)強(qiáng)的人...
    加倍加倍的努力閱讀 161評論 0 2

友情鏈接更多精彩內(nèi)容