Android 6.0 运行时权限

Android M 运行时权限介绍

Android 6.0 已经发布很长一段时间了,做Android开发的同学都知道Android 6.0 新增了运行时权限机制,见官网:Android 6.0 变更。 网上已经有很多成熟的解决方案,在这里记录一种方案,为了方便自己快速找回,其次也可以作为公司项目解决该问题的规范,毕竟几个人一起开发每个人都用自己的方案,代码会很难维护。

对于我们开发者来说,最需要关注的是,Android M(API版本23)把权限分为 正常权限和危险权限。正常权限只需在Manifest声明就可以使用,危险权限则需要请求用户授权,用户也可以在权限管理中取消授权,如果程序处理不当,在调用没有权限的功能时就有可能会闪退。如果我们把 targetSdkVersion 调到 23 以下,所有权限在安装时都会被授权,但是用户还是可以手动取消授权,这样的话也会导致无法预料的后果。所以最好的方法是用户每次执行危险权限操作时,都先检查授权,如果没有授权,就弹框申请授权。

危险权限有下面这些:

权限组 权限
CALENDAR (日历) READ_CALENDAR
WRITE_CALENDAR
CAMERA(相机) CAMERA
CONTACTS(通讯录) READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
LOCATION(定位) ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
MICROPHONE(麦克风) 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(存储) READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE

每个危险权限都有自己的权限组,如果用户授权了某个权限,同一个权限组里面的其他权限也会自动授予权限。

动态申请权限的实现

我们可以使用Android系统提供的方法实现权限申请,包括以下几个步骤:

  • 检查权限状态:ContextCompat.checkSelfPermission()
  • 申请授权:ActivityCompat.requestPermissions()
  • 处理回调:onRequestPermissionsResult()

每次都自己处理授权还是比较麻烦的,所以可以考虑使用相关开源项目,我选择的是 RxPermissions ,一个基于 RxJava 的运行时权限检测框架,因为项目中也会使用到 RxAndroid 和 RxBus ,所以就选择了这个框架。

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 中声明权限:
<!-- 每个权限组选取一个权限 -->
<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 中检查申请运行时权限:
RxPermissions rxPermissions = new RxPermissions(this);

//申请单个权限
rxPermissions.requestEach(Manifest.permission.CALL_PHONE)
        .subscribe(new Action1<Permission>() {
            @Override
            public void call(Permission permission) {
                if(permission.granted) {
                    //授权成功

                } else if(permission.shouldShowRequestPermissionRationale == true){
                    //禁止授权

                } else if(permission.shouldShowRequestPermissionRationale == false){
                    //禁止授权且不再询问

                }
            }
        });

//同时申请多个权限
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) {
                    //全部已经授权

                } else {
                    //起码有一个未授权

                }
            }
        });

上面的例子基本上可以解决开发中遇到的大部分运行时权限问题。

自己封装的授权工具类

  • 里面的 T.show() 是我自己封装的 Toast 工具类,可以替换成 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;

/**
 * 授权工具类
 * 作者: JairusTse
 * 日期: 18/5/12 18:44
 */
public class PermissionUtil {

    //权限组,用户授权了某个权限,同一个权限组里面的其他权限也会自动授予权限。
    //这里每组选择一个权限,如果这个规则改变了,就要授权具体的权限。
    public static final String CALENDAR = "android.permission.READ_CALENDAR"; //日历
    public static final String CAMERA = "android.permission.CAMERA"; //相机
    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"; //麦克风
    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"; //存储

    //用户禁止授权的默认提示语
    private static String SHOW_AGAIN_MESSAGE = "您拒绝了授权,无法正常使用";
    //用户禁止授权并且勾选了不再询问的默认提示语
    private static String NOT_SHOW_AGAIN_MESSAGE = "您禁止了授权,请在手机设置里面授权";

    /**
     * 请求危险权限的授权
     * @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);
    }

    /**
     * 请求危险权限的授权
     * @param activity
     * @param showAgainMsg 用户禁止授权的提示语
     * @param notShowAgainMsg 用户禁止授权并且勾选了不再询问的提示语
     * @param listener
     * @param args 权限组
     */
    public static void requestEach(Activity activity, final String showAgainMsg, final String
            notShowAgainMsg, final OnPermissionListener listener, String ... args) {

        if(args.length == 1) {
            //只请求一个权限
            requestSingleEach(activity, showAgainMsg, notShowAgainMsg, listener, args[0]);
        } else if(args.length > 1) {
            //请求多个权限
            requestMultipleEach(activity, showAgainMsg, listener, args);
        }
    }

    /**
     * 请求单个授权
     * @param activity
     * @param showAgainMsg 用户禁止授权的提示语
     * @param notShowAgainMsg 用户禁止授权并且勾选了不再询问的提示语
     * @param listener
     * @param permission 权限
     */
    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) {
                            //授权成功
                            if (listener != null) {
                                listener.onSucceed();
                            }
                        } else {
                            if(permission.shouldShowRequestPermissionRationale == true) {
                                //用户禁止授权提示
                                T.show(!TextUtils.isEmpty(showAgainMsg) ? showAgainMsg : SHOW_AGAIN_MESSAGE);
                            } else if(permission.shouldShowRequestPermissionRationale == false) {
                                //用户禁止授权并且勾选了不再询问提示
                                T.show(!TextUtils.isEmpty(notShowAgainMsg) ? notShowAgainMsg : NOT_SHOW_AGAIN_MESSAGE);
                            }

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


                    }
                });
    }

    /**
     * 同时请求多个授权
     * @param activity
     * @param showMsg 没有全部授权的提示语
     * @param listener
     * @param args 权限组
     */
    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) {
                            //全部已经授权
                            if (listener != null) {
                                listener.onSucceed();
                            }

                        } else {
                            //起码有一个没有授权
                            if (listener != null) {
                                listener.onFailed(true);
                            }
                            //授权失败提示
                            T.show(!TextUtils.isEmpty(showMsg) ? showMsg : SHOW_AGAIN_MESSAGE);
                        }

                    }
                });
    }

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

}

  • 调用方法
/**
 * 获取存储和相机权限
 * @param activity
 */
public void openPhotoPicker(Activity activity) {
    PermissionUtil.requestEach(activity, new
            PermissionUtil.OnPermissionListener() {
                @Override
                public void onSucceed() {
                    //授权成功
                    //do something...
                }

                @Override
                public void onFailed(boolean showAgain) {
                    //授权失败
                }
    }, PermissionUtil.STORAGE, PermissionUtil.CAMERA);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容