Android 6.0 運(yùn)行時(shí)權(quán)限

Android M 運(yùn)行時(shí)權(quán)限介紹

Android 6.0 已經(jīng)發(fā)布很長(zhǎng)一段時(shí)間了赞厕,做Android開(kāi)發(fā)的同學(xué)都知道Android 6.0 新增了運(yùn)行時(shí)權(quán)限機(jī)制蜈项,見(jiàn)官網(wǎng):Android 6.0 變更。 網(wǎng)上已經(jīng)有很多成熟的解決方案晨雳,在這里記錄一種方案行瑞,為了方便自己快速找回,其次也可以作為公司項(xiàng)目解決該問(wèn)題的規(guī)范餐禁,畢竟幾個(gè)人一起開(kāi)發(fā)每個(gè)人都用自己的方案蘑辑,代碼會(huì)很難維護(hù)。

對(duì)于我們開(kāi)發(fā)者來(lái)說(shuō)坠宴,最需要關(guān)注的是洋魂,Android M(API版本23)把權(quán)限分為 正常權(quán)限和危險(xiǎn)權(quán)限。正常權(quán)限只需在Manifest聲明就可以使用喜鼓,危險(xiǎn)權(quán)限則需要請(qǐng)求用戶授權(quán)副砍,用戶也可以在權(quán)限管理中取消授權(quán),如果程序處理不當(dāng)庄岖,在調(diào)用沒(méi)有權(quán)限的功能時(shí)就有可能會(huì)閃退豁翎。如果我們把 targetSdkVersion 調(diào)到 23 以下,所有權(quán)限在安裝時(shí)都會(huì)被授權(quán)隅忿,但是用戶還是可以手動(dòng)取消授權(quán)心剥,這樣的話也會(huì)導(dǎo)致無(wú)法預(yù)料的后果。所以最好的方法是用戶每次執(zhí)行危險(xiǎn)權(quán)限操作時(shí)背桐,都先檢查授權(quán)优烧,如果沒(méi)有授權(quán),就彈框申請(qǐng)授權(quán)链峭。

危險(xiǎn)權(quán)限有下面這些:

權(quán)限組 權(quán)限
CALENDAR (日歷) READ_CALENDAR
WRITE_CALENDAR
CAMERA(相機(jī)) CAMERA
CONTACTS(通訊錄) READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
LOCATION(定位) ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
MICROPHONE(麥克風(fēng)) RECORD_AUDIO
PHONE(電話) READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
SENSORS(傳感器) BODY_SENSORS
SMS(短信) SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
STORAGE(存儲(chǔ)) READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE

每個(gè)危險(xiǎn)權(quán)限都有自己的權(quán)限組畦娄,如果用戶授權(quán)了某個(gè)權(quán)限,同一個(gè)權(quán)限組里面的其他權(quán)限也會(huì)自動(dòng)授予權(quán)限弊仪。

動(dòng)態(tài)申請(qǐng)權(quán)限的實(shí)現(xiàn)

我們可以使用Android系統(tǒng)提供的方法實(shí)現(xiàn)權(quán)限申請(qǐng)熙卡,包括以下幾個(gè)步驟:

  • 檢查權(quán)限狀態(tài):ContextCompat.checkSelfPermission()
  • 申請(qǐng)授權(quán):ActivityCompat.requestPermissions()
  • 處理回調(diào):onRequestPermissionsResult()

每次都自己處理授權(quán)還是比較麻煩的,所以可以考慮使用相關(guān)開(kāi)源項(xiàng)目励饵,我選擇的是 RxPermissions 驳癌,一個(gè)基于 RxJava 的運(yùn)行時(shí)權(quán)限檢測(cè)框架,因?yàn)轫?xiàng)目中也會(huì)使用到 RxAndroid 和 RxBus 役听,所以就選擇了這個(gè)框架颓鲜。

RxPermissions 使用教程

  • 在 build.gradle 中:
repositories {
    jcenter() // If not already there
}

dependencies {
    compile 'com.tbruyelle.rxpermissions:rxpermissions:0.9.3@aar'
    compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'
}

  • 在 AndroidManifest 中聲明權(quán)限:
<!-- 每個(gè)權(quán)限組選取一個(gè)權(quán)限 -->
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

  • 在 Activity 或者 Fragment 中檢查申請(qǐng)運(yùn)行時(shí)權(quán)限:
RxPermissions rxPermissions = new RxPermissions(this);

//申請(qǐng)單個(gè)權(quán)限
rxPermissions.requestEach(Manifest.permission.CALL_PHONE)
        .subscribe(new Action1<Permission>() {
            @Override
            public void call(Permission permission) {
                if(permission.granted) {
                    //授權(quán)成功

                } else if(permission.shouldShowRequestPermissionRationale == true){
                    //禁止授權(quán)

                } else if(permission.shouldShowRequestPermissionRationale == false){
                    //禁止授權(quán)且不再詢(xún)問(wèn)

                }
            }
        });

//同時(shí)申請(qǐng)多個(gè)權(quán)限
rxPermissions.request(Manifest.permission.READ_CALENDAR,
        Manifest.permission.CAMERA,
        Manifest.permission.READ_CONTACTS,
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.RECORD_AUDIO,
        Manifest.permission.CALL_PHONE,
        Manifest.permission.BODY_SENSORS,
        Manifest.permission.SEND_SMS,
        Manifest.permission.READ_EXTERNAL_STORAGE)
        .subscribe(new Action1<Boolean>() {
            @Override
            public void call(Boolean aBoolean) {
                if(aBoolean) {
                    //全部已經(jīng)授權(quán)

                } else {
                    //起碼有一個(gè)未授權(quán)

                }
            }
        });

上面的例子基本上可以解決開(kāi)發(fā)中遇到的大部分運(yùn)行時(shí)權(quán)限問(wèn)題表窘。

自己封裝的授權(quán)工具類(lèi)

  • 里面的 T.show() 是我自己封裝的 Toast 工具類(lèi),可以替換成 Toast.makeText()
package com.jairus.utils;

import android.app.Activity;
import android.text.TextUtils;

import com.tbruyelle.rxpermissions.Permission;
import com.tbruyelle.rxpermissions.RxPermissions;

import rx.functions.Action1;

/**
 * 授權(quán)工具類(lèi)
 * 作者: JairusTse
 * 日期: 18/5/12 18:44
 */
public class PermissionUtil {

    //權(quán)限組灾杰,用戶授權(quán)了某個(gè)權(quán)限蚊丐,同一個(gè)權(quán)限組里面的其他權(quán)限也會(huì)自動(dòng)授予權(quán)限熙参。
    //這里每組選擇一個(gè)權(quán)限艳吠,如果這個(gè)規(guī)則改變了,就要授權(quán)具體的權(quán)限孽椰。
    public static final String CALENDAR = "android.permission.READ_CALENDAR"; //日歷
    public static final String CAMERA = "android.permission.CAMERA"; //相機(jī)
    public static final String CONTACTS = "android.permission.READ_CONTACTS"; //通訊錄
    public static final String LOCATION = "android.permission.ACCESS_FINE_LOCATION"; //定位
    public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO"; //麥克風(fēng)
    public static final String PHONE = "android.permission.CALL_PHONE"; //電話
    public static final String SENSORS = "android.permission.BODY_SENSORS"; //傳感器
    public static final String SMS = "android.permission.READ_SMS"; //短信
    public static final String STORAGE = "android.permission.WRITE_EXTERNAL_STORAGE"; //存儲(chǔ)

    //用戶禁止授權(quán)的默認(rèn)提示語(yǔ)
    private static String SHOW_AGAIN_MESSAGE = "您拒絕了授權(quán)昭娩,無(wú)法正常使用";
    //用戶禁止授權(quán)并且勾選了不再詢(xún)問(wèn)的默認(rèn)提示語(yǔ)
    private static String NOT_SHOW_AGAIN_MESSAGE = "您禁止了授權(quán),請(qǐng)?jiān)谑謾C(jī)設(shè)置里面授權(quán)";

    /**
     * 請(qǐng)求危險(xiǎn)權(quán)限的授權(quán)
     * @param activity
     * @param listener
     * @param args
     */
    public static void requestEach(Activity activity, final OnPermissionListener listener, String ... args) {
        requestEach(activity, SHOW_AGAIN_MESSAGE, NOT_SHOW_AGAIN_MESSAGE, listener, args);
    }

    /**
     * 請(qǐng)求危險(xiǎn)權(quán)限的授權(quán)
     * @param activity
     * @param showAgainMsg 用戶禁止授權(quán)的提示語(yǔ)
     * @param notShowAgainMsg 用戶禁止授權(quán)并且勾選了不再詢(xún)問(wèn)的提示語(yǔ)
     * @param listener
     * @param args 權(quán)限組
     */
    public static void requestEach(Activity activity, final String showAgainMsg, final String
            notShowAgainMsg, final OnPermissionListener listener, String ... args) {

        if(args.length == 1) {
            //只請(qǐng)求一個(gè)權(quán)限
            requestSingleEach(activity, showAgainMsg, notShowAgainMsg, listener, args[0]);
        } else if(args.length > 1) {
            //請(qǐng)求多個(gè)權(quán)限
            requestMultipleEach(activity, showAgainMsg, listener, args);
        }
    }

    /**
     * 請(qǐng)求單個(gè)授權(quán)
     * @param activity
     * @param showAgainMsg 用戶禁止授權(quán)的提示語(yǔ)
     * @param notShowAgainMsg 用戶禁止授權(quán)并且勾選了不再詢(xún)問(wèn)的提示語(yǔ)
     * @param listener
     * @param permission 權(quán)限
     */
    private static void requestSingleEach(Activity activity, final String showAgainMsg, final String
            notShowAgainMsg, final OnPermissionListener listener, String permission) {
        RxPermissions rxPermissions = new RxPermissions(activity);
        rxPermissions.requestEach(permission)
                .subscribe(new Action1<Permission>() {
                    @Override
                    public void call(Permission permission) {

                        if(permission.granted) {
                            //授權(quán)成功
                            if (listener != null) {
                                listener.onSucceed();
                            }
                        } else {
                            if(permission.shouldShowRequestPermissionRationale == true) {
                                //用戶禁止授權(quán)提示
                                T.show(!TextUtils.isEmpty(showAgainMsg) ? showAgainMsg : SHOW_AGAIN_MESSAGE);
                            } else if(permission.shouldShowRequestPermissionRationale == false) {
                                //用戶禁止授權(quán)并且勾選了不再詢(xún)問(wèn)提示
                                T.show(!TextUtils.isEmpty(notShowAgainMsg) ? notShowAgainMsg : NOT_SHOW_AGAIN_MESSAGE);
                            }

                            if (listener != null) {
                                listener.onFailed(permission.shouldShowRequestPermissionRationale);
                            }
                        }


                    }
                });
    }

    /**
     * 同時(shí)請(qǐng)求多個(gè)授權(quán)
     * @param activity
     * @param showMsg 沒(méi)有全部授權(quán)的提示語(yǔ)
     * @param listener
     * @param args 權(quán)限組
     */
    private static void requestMultipleEach(Activity activity, final String showMsg, final OnPermissionListener listener, String ... args) {

        RxPermissions rxPermissions = new RxPermissions(activity);
        rxPermissions.request(args)
                .subscribe(new Action1<Boolean>() {
                    @Override
                    public void call(Boolean aBoolean) {

                        if(aBoolean) {
                            //全部已經(jīng)授權(quán)
                            if (listener != null) {
                                listener.onSucceed();
                            }

                        } else {
                            //起碼有一個(gè)沒(méi)有授權(quán)
                            if (listener != null) {
                                listener.onFailed(true);
                            }
                            //授權(quán)失敗提示
                            T.show(!TextUtils.isEmpty(showMsg) ? showMsg : SHOW_AGAIN_MESSAGE);
                        }

                    }
                });
    }

    public interface OnPermissionListener {
        void onSucceed();
        void onFailed(boolean showAgain);
    }

}

  • 調(diào)用方法
/**
 * 獲取存儲(chǔ)和相機(jī)權(quán)限
 * @param activity
 */
public void openPhotoPicker(Activity activity) {
    PermissionUtil.requestEach(activity, new
            PermissionUtil.OnPermissionListener() {
                @Override
                public void onSucceed() {
                    //授權(quán)成功
                    //do something...
                }

                @Override
                public void onFailed(boolean showAgain) {
                    //授權(quán)失敗
                }
    }, PermissionUtil.STORAGE, PermissionUtil.CAMERA);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末黍匾,一起剝皮案震驚了整個(gè)濱河市栏渺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锐涯,老刑警劉巖磕诊,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異纹腌,居然都是意外死亡霎终,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)升薯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)莱褒,“玉大人,你說(shuō)我怎么就攤上這事涎劈」阃梗” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵蛛枚,是天一觀的道長(zhǎng)谅海。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蹦浦,這世上最難降的妖魔是什么胁赢? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮白筹,結(jié)果婚禮上智末,老公的妹妹穿的比我還像新娘。我一直安慰自己徒河,他們只是感情好系馆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著顽照,像睡著了一般由蘑。 火紅的嫁衣襯著肌膚如雪闽寡。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天尼酿,我揣著相機(jī)與錄音爷狈,去河邊找鬼。 笑死裳擎,一個(gè)胖子當(dāng)著我的面吹牛涎永,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鹿响,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼羡微,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了惶我?” 一聲冷哼從身側(cè)響起妈倔,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绸贡,沒(méi)想到半個(gè)月后盯蝴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡听怕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年捧挺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叉跛。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡松忍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出筷厘,到底是詐尸還是另有隱情鸣峭,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布酥艳,位于F島的核電站摊溶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏充石。R本人自食惡果不足惜莫换,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骤铃。 院中可真熱鬧拉岁,春花似錦、人聲如沸惰爬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)撕瞧。三九已至陵叽,卻和暖如春狞尔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背巩掺。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工偏序, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留央勒,地道東北人吩案。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像遣耍,于是被迫代替她去往敵國(guó)和親刊殉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子殉摔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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