Android 7.0權(quán)限的適配

公司的需求空执,做了一下android7.0適配瞎访,適配7.0主要就是對手機本地文件的Uri做轉(zhuǎn)換處理灿渴。
7.0的適配,就是對手機存儲中的私有文件路徑的保護艺沼,當系統(tǒng)發(fā)現(xiàn)你intent帶走一個Uri册舞,地址是本地的文件,就會限制的障般,其他的原理普及請搜索其他的簡介
下面貼一下使用的步驟和我的工具類

一调鲸、需要修改當前module的AndroidManifest.xml文件,添加provider標簽挽荡,映射路徑藐石。

<application android:allowBackup="true" android:label="@string/app_name"
        android:supportsRtl="true">
 
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.myFileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/path_file" />
        </provider>
    </application>

文件中的部分是固定的寫法,官網(wǎng)也是這么寫的徐伐,不要亂嘗試贯钩,android:resource標簽的值是main/res/xml/path_file.xml文件,即在res下新建xml文件夾办素,在新建一個xml文件

二、在res/xml下新建一個path_file.xml文件祸穷,文件名隨便去性穿,但是與上步驟一致即可。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <paths>
        <!--
        <files-path/>代表的根目錄: Context.getFilesDir()
        <cache-path/>代表的根目錄: getCacheDir()
        <external-path/>代表的根目錄: Environment.getExternalStorageDirectory()
        <external-files-path/>代表的根目錄: Context.getExternalFilesDir(String) Context.getExternalFilesDir(null).
        <external-cache-path />代表的根目錄: Context.getExternalCacheDir().
        <root-path />代表設(shè)備的根目錄new File("/");
        -->
        <!-- path=""代表根目錄雷滚,也可以指定特定目錄需曾,name="camera_picture"是虛擬目錄camera_picture -->
        <root-path name="root" path="" />
        <files-path name="files" path="" />
        <cache-path name="cache" path="" />
        <external-path name="external" path="" />
        <external-files-path name="external_files" path="" />
        <external-cache-path name="external_cache" path="" />
    </paths>
</resources>

里面共有六個標簽,含義都寫了,根據(jù)你所需要的標簽定義就好了呆万,比如<external-path/>標簽商源,里面的path=""時,這個標簽映射的就是外掛sd卡根目錄了谋减,name屬性沒什么用牡彻,來迷惑第三方應(yīng)用的虛擬目錄,來掩蓋文件的真實路徑出爹。

三庄吼、工具類 FileUriPermissionCompat.java

/**
 * @Author:Smuel
 * @DateTime: 2018-08-30 14:43
 * @Description: android 7.0 uri權(quán)限適配, <br/>
 * (通過intent暴漏uri或file給第三方app時的)私有目錄被禁止訪問 <br/>
 * 已對local path和net path做了適配 <br/>
 */
public class FileUriPermissionCompat {
    private static final String TAG = FileUriPermissionCompat.class.getSimpleName();
 
    // TODO: 此處需要更改為對應(yīng)值
    //此處需要改成AndroidManifest.xml中申請的對應(yīng)的provider的authorities值
    private static final String AUTHORITIES = "com.duke.personalkeeper.myFileProvider";
 
    /**
     * 是否需要適配7.0權(quán)限
     *
     * @return
     */
    public static boolean isNeedAdapt() {
        //24以上版本
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
    }
 
    public static Uri adaptUriAndGrantPermission(Context context, Intent intent, File file) {
        Uri uri = adaptUri(context, file);
        if (uri == null) {
            return null;
        }
        grantUriPermission(context, intent, uri);
        return uri;
    }
 
    public static Uri adaptUri(Context context, File file) {
        if (context == null || file == null) {
            return null;
        }
        //網(wǎng)絡(luò)路徑的特殊處理严就,不需要7.0適配总寻,但必須用parse()方法
        if (file.getPath().startsWith("http")) {
            return Uri.parse(file.getPath());
        }
        Uri uri = null;
        try {
            if (isNeedAdapt()) {
                //需要7.0特殊適配
                //通過系統(tǒng)提供的FileProvider類創(chuàng)建一個content類型的Uri對象
                uri = FileProvider.getUriForFile(context, AUTHORITIES, file);
            } else {
                //不需要適配
                uri = Uri.fromFile(file);
            }
        } catch (Exception e) {
            Log.e(TAG, "authorities value error, so can't convert uri !");
            e.printStackTrace();
        }
        return uri;
    }
 
    /**
     * 對第三方應(yīng)用賦予對uri讀寫的權(quán)限
     *
     * @param context
     * @param intent
     * @param saveUri 適配后的uri
     */
    public static void grantUriPermission(Context context, Intent intent, Uri saveUri) {
        if (!isNeedAdapt()) {
            return;
        }
        if (context == null || intent == null || saveUri == null) {
            return;
        }
        //網(wǎng)絡(luò)路徑的特殊處理,不需要權(quán)限
        if (saveUri.getScheme() != null && saveUri.getScheme().startsWith("http")) {
            //不需要授權(quán)
            return;
        }
        //1梢为、授權(quán)(系統(tǒng)相冊渐行、相機、裁剪時需要)  -- 這種寫法待分析
        List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            if (TextUtils.isEmpty(packageName)) {
                continue;
            }
            context.grantUriPermission(packageName, saveUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        }
        //2铸董、授權(quán)(安裝apk時需要)
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    }
 
    public static void revokeUriPermission(Context context, Intent intent, Uri saveUri) {
        if (!isNeedAdapt()) {
            return;
        }
        if (context == null || intent == null || saveUri == null) {
            return;
        }
        //網(wǎng)絡(luò)路徑的特殊處理殊轴,不需要權(quán)限
        if (saveUri.getScheme() != null && saveUri.getScheme().startsWith("http")) {
            //不需要授權(quán)
            return;
        }
        try {
            context.revokeUriPermission(saveUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 

核心代碼

uri = FileProvider.getUriForFile(context, AUTHORITIES, file);

file就是你想要暴露給其他應(yīng)用的文件地址,比喻你要拍照袒炉,把結(jié)果保存到file文件中旁理。
AUTHORITIES就是上面第一步中,android:authorities="{applicationId}.myFileProvider"的實際值我磁,{applicationId}取得是app/build.gradle中defaultConfig標簽的applicationid值孽文。通過系統(tǒng)提供的FileProvider類的靜態(tài)方法轉(zhuǎn)換file地址為一個以content://開頭的特殊的uri。如果不轉(zhuǎn)換的話夺艰,直接用Uri.fromFile(file)芋哭,你得到的是一個file:///xxxxx這樣的uri。就這差別郁副。

轉(zhuǎn)化完了 還需要授權(quán)

/**
     * 對第三方應(yīng)用賦予對uri讀寫的權(quán)限
     *
     * @param context
     * @param intent
     * @param saveUri 適配后的uri
     */
    public static void grantUriPermission(Context context, Intent intent, Uri saveUri) {
        if (!isNeedAdapt()) {
            return;
        }
        if (context == null || intent == null || saveUri == null) {
            return;
        }
        //網(wǎng)絡(luò)路徑的特殊處理减牺,不需要權(quán)限
        if (saveUri.getScheme() != null && saveUri.getScheme().startsWith("http")) {
            //不需要授權(quán)
            return;
        }
        //1、授權(quán)(系統(tǒng)相冊存谎、相機拔疚、裁剪時需要)  -- 這種寫法待分析
        List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            if (TextUtils.isEmpty(packageName)) {
                continue;
            }
            context.grantUriPermission(packageName, saveUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        }
        //2、授權(quán)(安裝apk時需要)
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    }

里面有兩部分授權(quán)方式既荚,經(jīng)過多輪測試稚失,發(fā)現(xiàn)需要同時使用比較好。
第一種方式恰聘,for循環(huán)句各,是因為有時候你并不確定需要分享的應(yīng)用的包名是哪一個吸占,所以找到所有有可能的第三方應(yīng)用,全部授權(quán)了凿宾。

后來測試發(fā)現(xiàn)矾屯,安裝apk的時候,只有上面的授權(quán)是不行的初厚,還得加上intent.addFlag的方式再次授權(quán)才行件蚕。

最后,需要注意的是:

1惧所、注意7.0的版本判斷骤坐。

2、切記下愈,7.0的權(quán)限有可能需要對sd卡讀寫纽绍,需要6.0的讀寫sd卡權(quán)限。當你測試7.0權(quán)限不成功時势似,考慮下6.0的權(quán)限是否到位了拌夏。

其他的沒的說了。就這些

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末履因,一起剝皮案震驚了整個濱河市障簿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌栅迄,老刑警劉巖站故,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異毅舆,居然都是意外死亡西篓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門憋活,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岂津,“玉大人,你說我怎么就攤上這事悦即∷背桑” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵辜梳,是天一觀的道長粱甫。 經(jīng)常有香客問我,道長冗美,這世上最難降的妖魔是什么魔种? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮粉洼,結(jié)果婚禮上节预,老公的妹妹穿的比我還像新娘。我一直安慰自己属韧,他們只是感情好安拟,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宵喂,像睡著了一般糠赦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锅棕,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天拙泽,我揣著相機與錄音,去河邊找鬼裸燎。 笑死顾瞻,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的德绿。 我是一名探鬼主播荷荤,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼移稳!你這毒婦竟也來了蕴纳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤个粱,失蹤者是張志新(化名)和其女友劉穎古毛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體都许,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡稻薇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了梭稚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颖低。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖弧烤,靈堂內(nèi)的尸體忽然破棺而出忱屑,到底是詐尸還是另有隱情,我是刑警寧澤暇昂,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布莺戒,位于F島的核電站,受9級特大地震影響急波,放射性物質(zhì)發(fā)生泄漏从铲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一澄暮、第九天 我趴在偏房一處隱蔽的房頂上張望名段。 院中可真熱鬧阱扬,春花似錦、人聲如沸伸辟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽信夫。三九已至窃蹋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間静稻,已是汗流浹背警没。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留振湾,地道東北人杀迹。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像恰梢,于是被迫代替她去往敵國和親佛南。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容