Android Permission 权限总结

 是时候系统的总结一遍Android权限:版本区别、权限级别、权限检测、权限申请、自定义权限等方面。权限相关源码分析详见Android权限检测申请源码流程分析

1、权限 Permission

        权限是Android的一种安全机制,主要用来保护用户、应用的数据安全。

1.1、版本差异

        Android权限机制有一个分水岭,就是Android 6.0 API 23,对于危险权限的申请有所不同。Android开发头疼的一点就是适配兼容,说不定以后所有的权限都要动态申请!

        targetSdkVersion < 23 & API < 6.0 :安装后就有权限,无需授权,无法取消授权

        targetSdkVersion < 23 & API  ≥ 6.0 :安装获得权限,但是可以被取消授权。

        targetSdkVersion ≥ 23 & API < 6.0 :安装后就有权限,无需授权,无法取消授权

        targetSdkVersion ≥ 23 & API  ≥ 6.0 :这种是趋势。需要动态申请,也可取消授权

1.2、框架预设权限

        Android框架预先定义了很多权限,它们在框架的Manifest中。开发者不需要都记住,只需要知道常用的,比如我们熟知了日历读写、联系人读写、网络访问权限等等。手机厂商定制的ROM系统还会有很多新增的权限。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

    public final class Manifest {

        //权限组,API 30中一共有11组

        public static final class permission_group {

            public static final String CALENDAR = "android.permission-group.CALENDAR";

            public static final String CALL_LOG = "android.permission-group.CALL_LOG";

            ...

        }

         

        //所有权限,API 30中一共有158个

        public static final class permission {

            public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";

            public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";

            public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";

            ...

        }

    }

1.3、adb命令查看权限

        通过adb命令可以查看手机中的所有权限:

1

    adb shell pm list permissions

        这其中是所有手机框架、系统、三方应用定义的权限,在安装的时候都会被系统记录保存:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

    All Permissions:

     

    permission:android.permission.REAL_GET_TASKS

    permission:com.huawei.permission.ONEHOP

    permission:com.huawei.intelligent.RECEIVE_FEEDBACK

    permission:android.permission.ACCESS_CACHE_FILESYSTEM

    permission:android.permission.REMOTE_AUDIO_PLAYBACK

    permission:com.huawei.decision.permission.ACCESS_PERMISSION

    permission:com.huawei.browser.OfflineFileProvider.READ

    permission:com.huawei.hidisk.permission.BackupNotificationActivity

    permission:com.huawei.hms.permission.signature

    permission:com.huawei.openplugin.permission.ABILITY_FOR_OPEN_PLUGIN

    permission:com.huawei.alarm.provider.writePermission

    permission:android.permission.MANAGE_APPOPS

    ...

        安装app并且对所有列在manifest的所有permission给予授权

1

    adb install -g apk

        授权某个app指定的permission

1

    adb pm grant <package_name> <permission_name>

        撤销上面的授权

1

    adb pm revoke <package_name> <permission_name>

2、权限级别

        Android系统将权限分为三个保护级别:普通、危险和签名权限。

2.1、普通权限

        普通权限就是在AndroidManifest.xml中注册即可,不需要动态申请。在应用安装后就会授予,用户无法撤销应用的这些权限。(禁止应用访问网络,可不是禁止了应用的网络权限)

1

2

3

    <!--普通权限-->

    <uses-permission android:name="android.permission.INTERNET" />

    ...

2.2、危险权限

        危险权限也得在AndroidManifest.xml中注册,不过用到的时候需要动态申请:运行时检测有没有该权限,没有就提示用户授权。

1

2

    <!--危险权限-->

    <uses-permission android:name="android.permission.READ_CONTACTS" />

        adb命令 -d 查看手机中的所有危险权限:

1

    adb shell pm list permissions -d -g

2.3、签名权限

        在尝试使用某权限的应用签名证书,和定义该权限的应用证书一致时才会授予。一般自定义权限会采用这种签名的机制,防止其它三方应用也申请自定义的权限。比如腾讯系、阿里系的应用有相同的证书,保证内部自定义的权限不会被“外族”使用。

3、权限分组

        Android系统将权限根据功能特性进行分组。adb命令 -g 查看手机中的命令分组情况:

1

    adb shell pm list permissions -g

3.1、权限组

        在框架的Manifest中目前定义了如下几个permission_group权限组(API 30源码):

1

2

3

4

5

6

7

8

9

10

11

12

13

    public static final class permission_group {

        public static final String ACTIVITY_RECOGNITION = "android.permission-group.ACTIVITY_RECOGNITION";

        public static final String CALENDAR   = "android.permission-group.CALENDAR";

        public static final String CALL_LOG   = "android.permission-group.CALL_LOG";

        public static final String CAMERA     = "android.permission-group.CAMERA";

        public static final String CONTACTS   = "android.permission-group.CONTACTS";

        public static final String LOCATION   = "android.permission-group.LOCATION";

        public static final String MICROPHONE = "android.permission-group.MICROPHONE";

        public static final String PHONE      = "android.permission-group.PHONE";

        public static final String SENSORS    = "android.permission-group.SENSORS";

        public static final String SMS        = "android.permission-group.SMS";

        public static final String STORAGE    = "android.permission-group.STORAGE";

    }

3.2、分组说明

        一组权限要么一个都没有授权,要么只要授权其中一个全部权限自动获取(以后可能会有变化,埋个坑)。

        前面提到的危险权限都有自己的分组,也可以将普通权限放到对应分组里。

        不要假定系统可以自动授予同组权限。以短信权限为例,短信权限组中的权限都和短信相关:

SMS (短信)

SEND_SMS

RECEIVE_SMS

READ_SMS

RECEIVE_WAP_PUSH

RECEIVE_MMS

        以前开发的时候需要短信的权限,图省事,我们只申请READ_CONTACTS权限,同组的其它权限也会自动获取,这时不仅拥有读取联系人的权限,写联系人的权限也有了。但是Google官网给出了警告,这样是不可靠的,因为将来的Android系统可能会修改权限组规则:比如WRITE_CONTACTS如果移出了短信组,那么光申请READ_CONTACTS权限还以为自动拥有了WRITE_CONTACTS权限就会出错。

4、权限检测

        对于targetSdkVersion ≥ 23而言,应用的某些操作需要的权限是动态申请的,在操作前一定要检测是否有该权限。

4.1、检测方法

        通过ContextCompat的checkSelfPermission(Context context,String permission)方法检测传入的permission权限:

1

    ContextCompat.checkSelfPermission(context, permission);

        或者如下调用形式,ActivityCompat继承自ContextCompat。

1

    ActivityCompat.checkSelfPermission(context, permission);

        或者通过AndroidX库中的PermissionChecker检测

1

    PermissionChecker.checkSelfPermission(context, permission);

        最终还是通过Context去检测权限,权限检测流程源码分析详见Android权限检测申请源码流程分析

1

2

3

4

5

6

    public static int checkSelfPermission(@NonNull Context context, @NonNull String permission) {

        if (permission == null) {

            throw new IllegalArgumentException("permission is null");

        }

        return context.checkPermission(permission, android.os.Process.myPid(), Process.myUid());

    }

4.2、返回值

        检测结果以 int 类型返回,PackageManager.PERMISSION_GRANTED有该权限,PackageManager.PERMISSION_DENIED无该权限。

1

2

3

4

5

6

7

8

9

10

11

    /**

     * Permission check result: this is returned by {@link #checkPermission}

     * if the permission has been granted to the given package.

     */

    public static final int PERMISSION_GRANTED = 0;

     

    /**

     * Permission check result: this is returned by {@link #checkPermission}

     * if the permission has not been granted to the given package.

     */

    public static final int PERMISSION_DENIED = -1;

5、权限申请

        前面检测完权限,如果有权限就继续操作。若无相应的权限就需要动态申请。

5.1、shouldShowRequestPermissionRationale()

        在阅读官方指南的时候,发现官方推荐在动态申请权限的时候调用这个方法,是否需要在动态申请权限之前给用户一个友好的提示:告诉用户为什么我的应用需要申请这个权限,而不是不管三七二十一就向用户申请权限。对于一个厨房APP还需要手机联系人读写权限的就很不理解。

1

2

3

4

5

6

    /**

     * Gets whether you should show UI with rationale before requesting a permission.

     */

    public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {

        return getPackageManager().shouldShowRequestPermissionRationale(permission);

    }

5.2、动态申请

        如果检测出来没有权限,使用requestPermissions(String[] permissions, int requestCode)动态申请权限。用法也很简单。

        在Activity、Fragment中都有请求权限的方法,在Activity和Fragment中都可以调用requestPermissions(String[] permissions, int requestCode)方法,虽然请求权限的方法名字相同,但是实现不同。

5.3、权限申请回调

        用户响应系统权限对话框后,系统就会调用应用的onRequestPermissionsResult()方法。系统会传入用户对权限对话框的响应以及开发者自定义的请求代码:

1

2

3

4

    @Override

    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    }

        

6、自定义权限

        应用可以自定义自己的权限。

6.1、permission语法

        定义权限使用<permission/>标签,详见App manifest file permission-element

1

2

3

4

5

6

7

    <permission android:description="string resource"

            android:icon="drawable resource"

            android:label="string resource"

            android:name="com.quibbler.SUBSCRIBE"

            android:permissionGroup="string"

            android:protectionLevel=["normal" | "dangerous" |

                                     "signature" | ...] />

6.2、使用自定义权限

        举个例子:友商的应用要跳转我们的Activity,Activity必须要设置exported为true。但是这样又不安全,这意味着其它任意应用都可以启动暴露的Activity。这时可以在AndroidManifest.xml中给Activity加上权限:

1

2

3

4

5

    <activity

        android:exported="true"

        android:permission="com.quibbler.SUBSCRIBE">

        ...

    </activity>

        将我们自定义的权限给三方,三方应用只需要在自己的AndroidManifest中添加声明即可(遇到特别坑的情况,三方自行修改了权限也不告诉一声那就没办法,所以启动Activity的时候都必须异常捕获)。

1

2

3

4

5

6

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="com.quibbler.jetpack">

    <!--申请自定义权限-->

    <uses-permission android:name="com.quibbler.SUBSCRIBE" />

    ...

</manifest>

        

7、工具方法

        项目上往往将重复的方法抽取出来,成为工具方法。这里的权限检测有很多地方会用到,所以一般也会抽取。简单的示例如下,后面我会单独介绍一个更方便的权限检测库Permission-dispatch

7.1、检查单个权限

        返回true表示已授权,返回false表示未获得权限。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

    // 检查某个权限。返回true表示已启用该权限,返回false表示未启用该权限

    public static boolean checkPermission(Activity act, String permission, int requestCode) {

        Log.d(TAG, "checkPermission: " + permission);

        boolean result = true;

        // 只对Android6.0及以上系统进行校验

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

            // 检查当前App是否开启了名称为permission的权限

            int check = ContextCompat.checkSelfPermission(act, permission);

            if (check != PackageManager.PERMISSION_GRANTED) {

                // 未开启该权限,则请求系统弹窗,好让用户选择是否立即开启权限

                ActivityCompat.requestPermissions(act, new String[]{permission}, requestCode);

                result = false;

            }

        }

        return result;

    }

7.2、检查多个权限

        一次性检查多个权限,有一个没用授权就不通过。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

    // 检查多个权限。返回true表示已完全启用权限,返回false表示未完全启用权限

    public static boolean checkMultiPermission(Activity act, String[] permissions, int requestCode) {

        boolean result = true;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

            int check = PackageManager.PERMISSION_GRANTED;

            // 通过权限数组检查是否都开启了这些权限

            for (String permission : permissions) {

                check = ContextCompat.checkSelfPermission(act, permission);

                if (check != PackageManager.PERMISSION_GRANTED) {

                    break;

                }

            }

            if (check != PackageManager.PERMISSION_GRANTED) {

                // 未开启该权限,则请求系统弹窗,好让用户选择是否立即开启权限

                ActivityCompat.requestPermissions(act, permissions, requestCode);

                result = false;

            }

        }

        return result;

    }

7.3、检查是否允许修改系统设置

        检查是否允许修改系统设置。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

    // 检查是否允许修改系统设置

    public static boolean checkWriteSettings(Activity act, int requestCode) {

        Log.d(TAG, "checkWriteSettings:");

        boolean result = true;

        // 只对Android6.0及Android7.0系统进行校验

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M

                && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {

            // 检查当前App是否允许修改系统设置

            if (!Settings.System.canWrite(act)) {

                Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,

                        Uri.parse("package:" + act.getPackageName()));

                act.startActivityForResult(intent, requestCode);

                Toast.makeText(act, "需要允许设置权限才能调节亮度噢", Toast.LENGTH_SHORT).show();

                result = false;

            }

        }

        return result;

    }

相关资料:

        Android Developers > Guides > permission

        Android Developers > Guides > path-permission

        Android Developers > Guides > Permissions on Android

        Android Developers > Docs > Manifest.permission

        Android 危险权限、权限组列表和所有普通权限

        Android总结篇系列:Android 权限

        Android 8.0权限管理源码分析

        为ACTIVITY设置特定权限才能启动

参考:Android Permission 权限总结-安卓-析物言理的笔记本 

Android权限检测申请源码流程分析-AOSP-析物言理的笔记本

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值