Android权限完全解析

Android6.0以后的权限管理发生了很大的改变,不是直接在manifest中添加,而是让用户使用到的时候去动态的申请,Google把权限分了类,当涉及到一些危险的权限的时候必须使用动态申请。

  • 权限分类
  • 重要方法
  • 使用示例
  • 权限申请封装
  • 其他权限

权限分类

如下是危险权限,使用的时候都是需要动态申请的

Dangerous Permissions:
// 涉及读写联系人,访问账户
group:android.permission-group.CONTACTS
  permission:android.permission.WRITE_CONTACTS
  permission:android.permission.GET_ACCOUNTS
  permission:android.permission.READ_CONTACTS
// 涉及电话操作
group:android.permission-group.PHONE
  permission:android.permission.READ_CALL_LOG
  permission:android.permission.READ_PHONE_STATE
  permission:android.permission.CALL_PHONE
  permission:android.permission.WRITE_CALL_LOG
  permission:android.permission.USE_SIP
  permission:android.permission.PROCESS_OUTGOING_CALLS
  permission:com.android.voicemail.permission.ADD_VOICEMAIL
// 涉及日历信息的操作(用户日程安排)
group:android.permission-group.CALENDAR
  permission:android.permission.READ_CALENDAR
  permission:android.permission.WRITE_CALENDAR
// 涉及相机操作
group:android.permission-group.CAMERA
  permission:android.permission.CAMERA
// 涉及使用手机传感器操作
group:android.permission-group.SENSORS
  permission:android.permission.BODY_SENSORS
// 涉及用户地理位置信息的操作
group:android.permission-group.LOCATION
  permission:android.permission.ACCESS_FINE_LOCATION
  permission:android.permission.ACCESS_COARSE_LOCATION
// 涉及存储卡的读写操作
group:android.permission-group.STORAGE
  permission:android.permission.READ_EXTERNAL_STORAGE
  permission:android.permission.WRITE_EXTERNAL_STORAGE
// 涉及多媒体信息的操作哦
group:android.permission-group.MICROPHONE
  permission:android.permission.RECORD_AUDIO
// 涉及SMS卡的操作
group:android.permission-group.SMS
  permission:android.permission.READ_SMS
  permission:android.permission.RECEIVE_WAP_PUSH
  permission:android.permission.RECEIVE_MMS
  permission:android.permission.RECEIVE_SMS
  permission:android.permission.SEND_SMS
  permission:android.permission.READ_CELL_BROADCASTS

重要方法

如下申请动态权限的时候用到的函数:
(1)ContextCompat.checkSelfPermission
(2)ActivityCompat.requestPermissions
(3)ActivityCompat.shouldShowRequestPermissionRationale
(4)onRequestPermissionsResult

1. ContextCompat.checkSelfPermission

检查是否具有某个权限,如果应用具有此权限,方法将返回PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。如果应用不具有此权限,方法将返回PackageManager.PERMISSION_DENIED,且应用必须明确向用户要求权限。

2. ActivityCompat.requestPermissions

应用可以通过这个方法动态申请权限,调用后会弹出一个对话框提示用户授权所申请的权限。

3. ActivityCompat.shouldShowRequestPermissionRationale

如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了 Don't ask again 选项,此方法将返回 false。如果设备规范禁止应用具有该权限,此方法也会返回 false。

4. onRequestPermissionsResult

当应用请求权限时,系统将向用户显示一个对话框。当用户响应时,系统将调用应用的 onRequestPermissionsResult() 方法,这个是执行requestPermissions后的回调。

使用示例

1、ContextCompat.checkSelfPermission使用

private void checkPermission() {
    //动态请求的权限数组
    String[] permissions =new String[]{
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.CAMERA,
            Manifest.permission.READ_SMS,
            Manifest.permission.INSTALL_SHORTCUT,
            Manifest.permission.MODIFY_AUDIO_SETTINGS};
    //测试
    Log.i("checkSelfPermission", "--------------------------------"+permissions.length);
    for (String permission : permissions) {
        int isGranted = ContextCompat.checkSelfPermission(this, permission);
        if (isGranted == PackageManager.PERMISSION_GRANTED) {
            //已授权
            Log.i("checkSelfPermission", permission.toString()+ ":   已授权");
        }else if(isGranted == PackageManager.PERMISSION_DENIED){
            //未授权的
            Log.i("checkSelfPermission", permission.toString()+ ":   未授权");
        }
    }
    Log.i("checkSelfPermission", "--------------------------------"+permissions.length);
}

2、ActivityCompat.requestPermissions使用,这里是先检查是否获得了这两个权限,只要有一个没有获得就去申请权限

private void requestPermissions() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, 1);
        }else {
            Toast.makeText(this,"您已经申请了权限!",Toast.LENGTH_SHORT).show();
        }
    }
}

3、ActivityCompat.shouldShowRequestPermissionRationale使用,拒绝且不再提醒、系统默认禁用的权限、用户未点过拒绝的权限均返回false, 只有用户点击拒绝后,且没有勾选不再提醒返回true

private void rationablePermission() {
    String[] permissions =new String[]{
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.CAMERA,
            Manifest.permission.READ_SMS,
            Manifest.permission.INSTALL_SHORTCUT,
            Manifest.permission.MODIFY_AUDIO_SETTINGS};
    //测试
    Log.i("checkSelfPermission", "--------------------------------"+permissions.length);
    for (String permission : permissions) {
        //拒绝且不再提醒、系统默认禁用的权限、用户未点过拒绝的权限均返回false, 只有用户点击拒绝后,且没有勾选不再提醒返回true
        if(!ActivityCompat.shouldShowRequestPermissionRationale(this, permission)){
            Log.i("checkSelfPermission @@", permission.toString()+ ":   false");
        }else{
            Log.i("checkSelfPermission @@", permission.toString()+ ":   true");
        }
    }
    Log.i("checkSelfPermission", "--------------------------------"+permissions.length);
}

4、onRequestPermissionsResult使用,当应用请求权限时候,系统将向用户显示一个对话框。当用户相应的时候,系统将调用onRequstPermissionsResult() 方法。向其传递用户的相应。我们需要在我们自己Activity中复写该方法。并且对用户操作的反馈做处理。

@Override
public void onRequestPermissionsResult(int requestCode,
        String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // permission was granted, yay! Do the
            } else {
                // permission denied, boo! Disable the
            }
            return;
        }
        // other 'case' lines to check for other
    }
}

权限申请封装

首先建立一个权限申请成功和失败的回调接口:

public interface Callback {
    //pendingPermissions需要申请的权限列表。
    void onPendingGrant(Activity activity, List<String> pendingPermissions);

    void onGrantAll();
}

然后建立一个PermissinoHelper类来检查是否有权限,如果没有的话直接申请:

    /**
     * 检查权限(仅仅做检查操作,不会主动申请权限)
     *
     * @param activity    当前活动
     * @param callback    回调:1.onGrantAll 所检查权限已全部授权 2. onPendingGrant 待授权的权限集合
     * @param permissions 待检测的权限
     */
    public static void checkPermissions(Activity activity, Callback callback, String... permissions) {
        checkPermissions(activity, callback, Arrays.asList(permissions));
    }

    public static void checkPermissions(Activity activity, Callback callback, List<String> permissions) {
        if (activity == null || permissions == null || permissions.size() == 0) {
            Log.w(TAG, "Illegal param , Activity or Permissions is NULL");
            return;
        }
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            Log.w(TAG, "The SDK API < Marshmallow");
            return;
        }
        List<String> targetPermissions = permissions;
        //检测 8.0 权限
        if (targetPermissions.contains(Permission.REQUEST_INSTALL_PACKAGES) ||
                targetPermissions.contains(Permission.ANSWER_PHONE_CALLS) ||
                targetPermissions.contains(Permission.READ_PHONE_NUMBERS)) {
            if (activity.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O) {
                Log.d(TAG, "Contain Permission over API 26");
            }
        }
        //检测 6.0 运行时权限
        List<String> noGrantedPermissions = checkPermissionState(activity, targetPermissions);
        if (noGrantedPermissions == null) {
            if (callback != null) {
                callback.onGrantAll();
            }
        } else {
            if (callback != null) {
                callback.onPendingGrant(activity, noGrantedPermissions);
            }
        }
    }
    /**
     * 获取Manifest声明的权限的集合
     *
     * @return
     */
    private static List<String> getManifestPermissions(Activity activity) {
        List<String> permissions = null;
        try {
            permissions = Arrays.asList(activity.getPackageManager().getPackageInfo(
                    activity.getPackageName(), PackageManager.GET_PERMISSIONS).requestedPermissions);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return permissions;
    }

    /**
     * 检测所申请权限的状态,并返回未授权的权限集合
     *
     * @param activity    当前活动
     * @param permissions 需要申请的目标权限的集合
     * @return 未授权的权限集合
     */
    private static List<String> checkPermissionState(Activity activity, List<String> permissions) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return null;
        }
        List<String> results = null;
        for (String permission : permissions) {
            int state = ActivityCompat.checkSelfPermission(activity, permission);
            if (state == PackageManager.PERMISSION_DENIED) {
                if (results == null) {
                    results = new ArrayList<>();
                }
                results.add(permission);
            }
        }
        return results;
    }

然后是一个申请权限的方法,他会首先判断manifest文件中是否声明了该权限

    /**
     * 申请普通的6.0权限(不处理)
     *
     * @param activity           当前活动
     * @param pendingPermissions 待申请的权限集合
     */
    public static void requestPermissions(Activity activity, List<String> pendingPermissions, int requestCode) {
        if (pendingPermissions == null || pendingPermissions.size() == 0) {
            return;
        }
        List<String> manifestPermissions = getManifestPermissions(activity);
        if (manifestPermissions == null) {
            Log.w(TAG, "No permissions declared");
            return;
        } else if (manifestPermissions != null && !manifestPermissions.containsAll(pendingPermissions)) {
            Log.w(TAG, "The manifestPermissions didn't include all of pendingPermissions");
            return;
        } else {
            if (pendingPermissions.contains(Permission.SYSTEM_ALERT_WINDOW)) {
                pendingPermissions.remove(Permission.SYSTEM_ALERT_WINDOW);
                Log.w(TAG, "Call checkAndRequestAlertWindowPermission to request SYSTEM_ALERT_WINDOW permission");
            }
            if (pendingPermissions.contains(Permission.WRITE_SETTINGS)) {
                pendingPermissions.remove(Permission.WRITE_SETTINGS);
                Log.w(TAG, "Call checkAndRequestWriteSettingPermission to request WRITE_SETTINGS permission");
            }
            String[] permissions = new String[pendingPermissions.size()];
            ActivityCompat.requestPermissions(activity, pendingPermissions.toArray(permissions), requestCode);
        }
    }

如下是在Fragment或者Activity中的使用方式,用来申请所需要的权限:

    //申请必须的权限。
    private void checkAndRequestPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            String[] permissions;
            if (getViewModel().getState().callType == AUDIO_TYPE) {
                permissions = new String[]{Manifest.permission.RECORD_AUDIO,
                        Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_PHONE_STATE};
            } else {
                permissions = new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO,
                        Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_PHONE_STATE};
            }
            PermissionHelper.checkPermissions(this, new PermissionHelper.Callback() {
                @Override
                public void onPendingGrant(Activity activity, List<String> pendingPermissions) {
                    PermissionHelper.requestPermissions(activity, pendingPermissions, Constant.PERMISSION_REQUEST_CODE);
                }

                @Override
                public void onGrantAll() {
                    ToastUtils.showToast("All Permission has Granted");
                }
            }, permissions);
        }
    }

最后是在onRequestPermissionsResult处理权限的申请结果

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == Constant.PERMISSION_REQUEST_CODE) {
            ArrayList<String> result = new ArrayList<>();
            for (int i = 0; i < grantResults.length; i++) {
                if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
                    result.add(permissions[i]);
                } else if (TextUtils.equals(permissions[i], Manifest.permission.CAMERA) && initCamera) {
                    cameraEnable();
                }
            }
        }
        ToastUtils.showToast("您尚未开启 : " + result.toString() + "权限");
        //也可以在这里引导用户去设置界面开启权限
    }

其他权限

以上这些内容全部都是来自Google的官方Demo。国内真正的Android开发环境,Rom客制化太多了,Google 原生API被改的很严重。

以小米为例:以看到小米的授权模块中,对权限操作可以分为允许,询问,拒绝。当我们第一次打开应用的时候,默认是询问状态,在该状态下,我们调用requestPermission()方法会弹出系统询问框.在弹出系统授权框后,只要你操作了(拒绝或者允许),你永远也不要想着在以后能看到授权框了,除非你过来设置这边更改为“询问”模式。不然无论你再调用几次requestPermissions(),都是直接走回调OnRequestPermissionResult。总结为一句话,小米客制的授权模块是凌驾于Google的授权模块之上的。

另外小米手机还有特殊的权限,如后台弹出权限和锁屏显示权限,如果没有打开后台弹出权限的话是没有办法从后台正常弹出的,还有锁屏也是。后台弹出权限不同手机的控制机制也是不一样的,小米是直接限制不让弹出,而vivo手机只让弹出后再给他finish掉,360手机还不一样,适配起来还是比较头大的。

再有就是通知权限,这个权限还是比较好控制的,可以直接让用户跳转到设置界面去打开。悬浮窗权限的适配也是比较麻烦的,不同手机也是不太相同。

尊重作者,尊重原创,参考文章:
Demo:https://blog.csdn.net/losingcarryjie/article/details/80889154
封装:https://www.jianshu.com/p/8e37e9cf20a5
https://www.jianshu.com/p/2fe4fb3e8ce0
https://blog.csdn.net/yuguqinglei/article/details/80375702

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

推荐阅读更多精彩内容