Android动态获取权限

前几天在网上找了找Android动态获取权限的文章和视频,自己整理了一下。几天看一位大神说真正的程序员是有着分享精神的,我这个刚刚入行的小菜鸟,也想把自己整理的东西分享给大家。

本文参考了A_siPermission——郭霖认为最优的运行时权限方案郭霖大神的CSDN视屏

在这之前,我们需要知道什么是权限?

权限是一种安全机制。Android权限主要用于限制应用程序内部某些具有限制性特性的功能使用以及应用程序之间的组件访问。
比如网络权限 <user-permission android:name="android.permission.INTERNET"/>当我们的程序需要访问网络的时候,必须添加这个权限,不添加则无法访问网络。

这里还需要注意的是:在Android6.0之前,我们只需要在AndroidManifest.xml文件中直接添加权限即可,但是在Android6.0之后,我们只在AndroidManifest.xml文件中配置是不够的,还需要在Java代码中进行动态获取权限。当然这里不是所有的权限都需要动态获取,只需要获取危险权限就可以了。(毕竟Android权限100多种,要是每个都需要获取,那不累死开发人员啦)

Android中的危险权限分为9组24种

1454742-2c2196fd438a4ed3.png

我们只需要在用这些权限的时候,才需要去动态申请,除这些之外的权限,我们只需要在AndroidManifest.xml文件种写上即可,动态申请的权限同样需要在AndroidManifest.xml文件中写。而且每一组种的权限只要有一个授权了,其他的也会自动授权

下面这张图是我们的build.gradle文件中的版本信息


Image.png

1、targetSdkVersion版本号如果<= 22时,不需要动态处理权限 。但是有时候我们的应用程序会打不开。
2、如果是之前的老版本升级上来的,系统会默认开启之前所有的权限,比如之前的targetSdkVersion是21,后来升级之后变成了25,这个时候系统会自动授权。一定要记住是升级变成的25,否则还是需要授权。

Tips: 为什么SD卡读写权限是危险权限,能不能不申请权限但又能使用SD卡呢?
(1)把SD权限设置为危险权限,是为了防止应用随便在用户的手机上些东西,如果不加防范,用户的SD卡就会有很多乱七八糟的东西。
(2)用户可以不申请权限而使用SD卡,但是只能使用系统提供的默认的路径。
我们可以使用Android系统提供的SDK目录。路径是Android\data\程序包名。这个目录是程序可以不需要经过
用户授权就可以直接使用,甚至不需要添加WRITE_EXTERNAL_STORAGE这个权限。 这个目录会在程序删除时也会直接删除。

如何访问这个目录呢?
File file = getExternalCacheDir();
String s = file.getPath; 这个方法得到的是cache的路径,但是一般的垃圾清理软件都可以把这里面的数据清理掉
File[] files = getExternalCacheDirs(); 这个得到的是一个文件数组,要求Api最小19

如果我们不想垃圾清理软件把我们缓存的数据清理掉,则可以放在file文件下
getExternalFilesDir(""); 这个表示所有的操作都是放在file目录下进行的
getExternalFilesDir("mm"); 这个表示会在file目录下在见一个mm目录,然后所有的操作都会放在这个目录下。

下面我们来看如何动态获取权限!!!

先看最简单的,动态获取一个权限

比如现在要动态获取一个打电话的权限。。
首先需要在AndroidManifest.xml文件中写上这个权限
<user-permission android:name="android.premission.CALL_PHONE"/>
然后在Java代码进行动态获取
请求授权的代码

 /**
     * 请求授权
     */
    private void requestPermission(){

        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){ //表示未授权时
            //进行授权
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CALL_PHONE},1);
        }else{
            //调用打电话的方法
            makeCall();
        }
    }

请求之后,我们需要重写onRequestPermissionsResult这个方法

/**
 * 权限申请返回结果
 * @param requestCode 请求码
 * @param permissions 权限数组
 * @param grantResults  申请结果数组,里面都是int类型的数
 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case 1:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ //同意权限申请
                makeCall();
            }else { //拒绝权限申请
                Toast.makeText(this,"权限被拒绝了",Toast.LENGTH_SHORT).show();
            }
            break;
        default:
            break;
    }
}
/**
 * 打电话方法
 */
private void makeCall(){
    try {
        Intent intent = new Intent(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel://123456789"));
        startActivity(intent);
    }catch (SecurityException e){
        e.printStackTrace();
    }
}

上面只是对一个权限进行授权,如果我们需要对多个权限进行授权呢?难道还是这样一个一个写吗?答案是否定的,我们看一下授权申请的方法

ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CALL_PHONE},1);

我们注意到权限的参数是一个String类型的数组,这就说明我们可以直接把所有要授权的权限放在一个数组中,这样就可以对多个权限进行授权了
以打电话和SD卡权限为例

/**
 * 当有多个权限需要申请的时候
 * 这里以打电话和SD卡读写权限为例
 */
private void requestPermissions(){

    List<String> permissionList = new ArrayList<>();
    if (ContextCompat.checkSelfPermission(this,Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
        permissionList.add(Manifest.permission.CALL_PHONE);
    }

    if (ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
        permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
    }

    if (!permissionList.isEmpty()){  //申请的集合不为空时,表示有需要申请的权限
        ActivityCompat.requestPermissio8ns(this,permissionList.toArray(new String[permissionList.size()]),1);
    }else { //所有的权限都已经授权过了

    }
}

同样需要写onRequestPermissionsResult()方法,这里和单个权限申请的写法有一点区别

/**
 * 权限申请返回结果
 * @param requestCode 请求码
 * @param permissions 权限数组
 * @param grantResults  申请结果数组,里面都是int类型的数
 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case 1:
            if (grantResults.length > 0){ //安全写法,如果小于0,肯定会出错了
                for (int i = 0; i < grantResults.length; i++) {

                    int grantResult = grantResults[i];
                    if (grantResult == PackageManager.PERMISSION_DENIED){ //这个是权限拒绝
                        String s = permissions[i];
                        Toast.makeText(this,s+"权限被拒绝了",Toast.LENGTH_SHORT).show();
                    }else{ //授权成功了
                        //do Something
                    }
                }
            }
            break;
        default:
            break;
    }
}

上面两种是单个和多个的权限申请,可是这还有一个问题,就是我们只能在当前类中使用。这个时候,我们需要想到一个封装,然后让所有的类都可以使用。
如何去封装呢? 我们在开发一个项目的时候,都会打一个框架,这里面可定会有各种各样的基类,这个时候我们就可以把权限的申请放在BaseActivity中,然后各个子Activity去继承BaseActivity。
然后通过接口回调的方式把授权结果传给子Activity。

下面来看代码:

首先是定义的接口

/**
 * Created by 11213 on 2017/1/4.
 * 权限回调接口
 */

public interface PermissionListener {

    //授权成功
    void onGranted();

    //授权部分
    void onGranted(List<String> grantedPermission);

    //拒绝授权
    void onDenied(List<String> deniedPermission);
}

BaseActivity的代码

public class BaseActivity1 extends AppCompatActivity {
   
    private PermissionListener mlistener;

    /**
     * 权限申请
     * @param permissions 待申请的权限集合
     * @param listener  申请结果监听事件
     */
    protected void requestRunTimePermission(String[] permissions,PermissionListener listener){
        this.mlistener = listener;

        //用于存放为授权的权限
        List<String> permissionList = new ArrayList<>();
        //遍历传递过来的权限集合
        for (String permission : permissions) {
            //判断是否已经授权
            if (ContextCompat.checkSelfPermission(this,permission) != PackageManager.PERMISSION_GRANTED){
                //未授权,则加入待授权的权限集合中
                permissionList.add(permission);
            }
        }

        //判断集合
        if (!permissionList.isEmpty()){  //如果集合不为空,则需要去授权
            ActivityCompat.requestPermissions(this,permissionList.toArray(new String[permissionList.size()]),1);
        }else{  //为空,则已经全部授权
            listener.onGranted();
        }
    }

    /**
     * 权限申请结果
     * @param requestCode  请求码
     * @param permissions  所有的权限集合
     * @param grantResults 授权结果集合
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0){
                    //被用户拒绝的权限集合
                    List<String> deniedPermissions = new ArrayList<>();
                    //用户通过的权限集合
                    List<String> grantedPermissions = new ArrayList<>();
                    for (int i = 0; i < grantResults.length; i++) {
                        //获取授权结果,这是一个int类型的值
                        int grantResult = grantResults[i];

                        if (grantResult != PackageManager.PERMISSION_GRANTED){ //用户拒绝授权的权限
                            String permission = permissions[i];
                            deniedPermissions.add(permission);
                        }else{  //用户同意的权限
                            String permission = permissions[i];
                            grantedPermissions.add(permission);
                        }
                    }

                    if (deniedPermissions.isEmpty()){  //用户拒绝权限为空
                        mlistener.onGranted();
                    }else {  //不为空
                        //回调授权成功的接口
                        mlistener.onDenied(deniedPermissions);
                        //回调授权失败的接口
                        mlistener.onGranted(grantedPermissions);
                    }
                }
                break;
            default:
                break;
        }
    }
}

子Activity中进行调用

private void requestPermission(){
    requestRunTimePermission(new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.WRITE_EXTERNAL_STORAGE}
            , new PermissionListener() {
                @Override
                public void onGranted() {  //所有权限授权成功

                }

                @Override
                public void onGranted(List<String> grantedPermission) { //授权失败权限集合

                }

                @Override
                public void onDenied(List<String> deniedPermission) { //授权成功权限集合

                }
            });
}

上面的代码注释很详细,就不再解释了

上面的封装,只能用在Activity中,如果要用在非Activity中(在v4包下的Fragment也可以进行授权),比如工具类中使用时,上面的就不适用了,因为我们看到,申请权限的方法必须传递一个Activity对象,为Activity又不可以new。这个时候,我们要想到,Activity管理栈。

代码如下:

接口还是上面的接口。。。

Activity管理栈

/**
 * Created by 11213 on 2017/1/5.
 * Activity管理类
 */

public class ActivityCollector {

    private static List<Activity> activityList = new ArrayList<>();

    /** 添加一个Activity到集合中*/
    public static void addActivity(Activity activity){
        activityList.add(activity);
    }

    /** 从集合中删除一个Activity*/
    public static void removeActivity(Activity activity){
        activityList.remove(activity);
    }

    /**获取Activity栈中的栈顶的Activity
     * 需要注意的是,栈是先进后出,所以最上面的Activity是集合中的最后一个*/
    public static Activity getTopActivity(){
        if (activityList.isEmpty()){  //Activity栈为空
            return null;
        }else {  //不为空时
            return activityList.get(activityList.size() - 1);
        }
    }
}

BaseActivity的代码

public class BaseActivity extends AppCompatActivity {

    private static PermissionListener mlistener;

    /**
     * 创建Activity时加到管理栈中
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

    /**
     * 销毁时从Activity管理栈中移除
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }

    /**
     * 权限申请
     * @param permissions 待申请的权限集合
     * @param listener  申请结果监听事件
     */
    protected static void requestRunTimePermission(String[] permissions,PermissionListener listener){
        mlistener = listener;

        Activity topActivity = ActivityCollector.getTopActivity();
        if (topActivity == null){
            return;
        }

        //用于存放为授权的权限
        List<String> permissionList = new ArrayList<>();
        //遍历传递过来的权限集合
        for (String permission : permissions) {
            //判断是否已经授权
            if (ContextCompat.checkSelfPermission(topActivity,permission) != PackageManager.PERMISSION_GRANTED){
                //未授权,则加入待授权的权限集合中
                permissionList.add(permission);
            }
        }

        //判断集合
        if (!permissionList.isEmpty()){  //如果集合不为空,则需要去授权
            ActivityCompat.requestPermissions(topActivity,permissionList.toArray(new String[permissionList.size()]),1);
        }else{  //为空,则已经全部授权
            listener.onGranted();
        }
    }

    /**
     * 权限申请结果
     * @param requestCode  请求码
     * @param permissions  所有的权限集合
     * @param grantResults 授权结果集合
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0){
                    //被用户拒绝的权限集合
                    List<String> deniedPermissions = new ArrayList<>();
                    //用户通过的权限集合
                    List<String> grantedPermissions = new ArrayList<>();
                    for (int i = 0; i < grantResults.length; i++) {
                        //获取授权结果,这是一个int类型的值
                        int grantResult = grantResults[i];

                        if (grantResult != PackageManager.PERMISSION_GRANTED){ //用户拒绝授权的权限
                            String permission = permissions[i];
                            deniedPermissions.add(permission);
                        }else{  //用户同意的权限
                            String permission = permissions[i];
                            grantedPermissions.add(permission);
                        }
                    }

                    if (deniedPermissions.isEmpty()){  //用户拒绝权限为空
                        mlistener.onGranted();
                    }else {  //不为空
                        //回调授权成功的接口
                        mlistener.onDenied(deniedPermissions);
                        //回调授权失败的接口
                        mlistener.onGranted(grantedPermissions);
                    }
                }
                break;
            default:
                break;
        }
    }
}

调用方法

public class Util {

    private void requestPermission(){
        BaseActivity.requestRunTimePermission(new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.WRITE_EXTERNAL_STORAGE}
                , new PermissionListener() {
                    @Override
                    public void onGranted() {

                    }

                    @Override
                    public void onGranted(List<String> grantedPermission) {

                    }

                    @Override
                    public void onDenied(List<String> deniedPermission) {

                    }
                });
    }
}

后边的这些代码的注释写的很详细,大家都可以看明白。这些可以直接封装到自己的项目中,另外还有一些其他的封装思路,比如郭神说的使用一个透明的Activity。当然我们也可以使用第三方库。

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

推荐阅读更多精彩内容