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)限有下面這些:
每個(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);
}