关于Android动态权限的一些疑问

Android 6.0开始,对敏感权限需要动态申请,首先我们需要引入android.support.v4兼容包(或appcompat兼容包),然后使用support包中ActivityCompat这个类来处理权限。其实Activity自身也可以处理动态权限,不需要额外引入ActivityCompat。

使用ActivityCompat处理权限需要用到的方法:

ActivityCompat.checkSelfPermission(...)
ActivityCompat.requestPermissions(...)
ActivityCompat.shouldShowRequestPermissionRationale(...)

第一个是检查某个权限是否授权
第二个是申请权限
第三个是判断是否需要合理显示授权对话框。具体含义和用法文末会详解。

Activity自身也可以处理动态权限,也有这些方法,为什么要使用ActivityCompat来处理动态权限?

这也就是为什么要引入兼容包的原因。

第一,如果应用的minSdkVersion小于23(我们一般会设置成15或16左右),直接使用Activity来处理动态权限,那么IDE会提示你这些方法在6.0以下不存在,编译会不通过。

第二,ActivityCompat做了一些兼容性判断,比如requestPermissions的实现里加了判断api 23,在23以下的机器方法不做处理,不弹出对话框。onRequestPermissionsResult回调在23以下是没有的,除非Activity实现了OnRequestPermissionsResultCallback接口。

看看ActivityCompat.requestPermissions函数的源码就明白了:

  public static void requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
        if (sDelegate != null
                && sDelegate.requestPermissions(activity, permissions, requestCode)) {
            // Delegate has handled the permission request.
            return;
        }

        if (Build.VERSION.SDK_INT >= 23) {
            if (activity instanceof RequestPermissionsRequestCodeValidator) {
                ((RequestPermissionsRequestCodeValidator) activity)
                        .validateRequestPermissionsRequestCode(requestCode);
            }
            activity.requestPermissions(permissions, requestCode);
        } else if (activity instanceof OnRequestPermissionsResultCallback) {
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    final int[] grantResults = new int[permissions.length];

                    PackageManager packageManager = activity.getPackageManager();
                    String packageName = activity.getPackageName();

                    final int permissionCount = permissions.length;
                    for (int i = 0; i < permissionCount; i++) {
                        grantResults[i] = packageManager.checkPermission(
                                permissions[i], packageName);
                    }

                    ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(
                            requestCode, permissions, grantResults);
                }
            });
        }
    }

可以看到,如果系统是23以下就调用activity的申请权限方法,如果系统是23以下的,并且activity又没有实现OnRequestPermissionsResultCallback接口,那么这个requestPermissions方法就什么都没做。

所谓兼容包,其实就是帮我们判断了系统版本,针对性地进行处理,防止高版本的api在低版本上不存在而出错。

ActivityCompat.checkSelfPermission函数源码:

  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());
    }

可以看到这个方法其实调用的是Context的方法。需要注意的是,Context的checkPermission方法一直都是存在的,并不是23以后才加入的。

ActivityCompat.shouldShowRequestPermissionRationale函数源码:

  public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
            @NonNull String permission) {
        if (Build.VERSION.SDK_INT >= 23) {
            return activity.shouldShowRequestPermissionRationale(permission);
        }
        return false;
    }
    

shouldShowRequestPermissionRationale其实也是调用了Context的方法。

Android 23以下如何检查权限呢?

上面已经说了,检查权限方法并不是23以后出现的新方法。一种检查方法是使用ActivityCompat,调用checkSelfPermission方法。
另一种方法直接使用Activity提供的方法:checkPermission(String permission, int pid, int uid),需要传入当前进程id和uid。

checkSelfPermission方法其实也是调用checkPermission(String permission, int pid, int uid)。

动态权限处理代码示例

package com.devnn.testdemo;

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.widget.Toast;

public class MainActivity extends Activity {
    String permission = Manifest.permission.READ_EXTERNAL_STORAGE;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{permission}, 100);
        }else{
            Toast.makeText(this, "已有读SDK卡权限", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode==100){
            if(grantResults[0]==PackageManager.PERMISSION_GRANTED){
                Toast.makeText(this, "授权成功", Toast.LENGTH_SHORT).show();
            }else if(ActivityCompat.shouldShowRequestPermissionRationale(this,permission)==false){
                Toast.makeText(this, "拒绝并勾选了不再提醒,不会弹了,需要引导用户去设置页", Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(this, "拒绝了授权,下次还会再弹", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

记得先要在AndroidManifest.xml里声明权限。

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

在弹出系统的权限对话框前,弹出自已的对话框,用来说明权限用途,会更加友好一点。直接使用Materia风格的Dialog。

    public void showPermissionDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this, android.R.style.Theme_DeviceDefault_Light_Dialog_NoActionBar_MinWidth);
        builder.setMessage("需要读SD卡权限,用来获取照片\n需要获取手机状态权限,用来获取设备号\n需要获取联系人权限,用来...");
        builder.setTitle("权限说明");
        builder.setCancelable(true);
        builder.setPositiveButton("申请", new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{permission}, 100);
            }
        });
        builder.show();
    }

对话框效果:
在这里插入图片描述

只有危险权限(或叫敏感权限)才需要申请,其它权限不需要申请。

“危险(敏感)权限组中的某一个权限授权了,组内其它权限也被同时授权。”这句话是错误的,经过对读写SD卡权限的测试,发现仅申请读SD卡权限后,并没有写权限。当申请了写权限后,读权限自动被授予。

危险权限组:
在这里插入图片描述

关于Context.shouldShowRequestPermissionRationale(...)方法含义

ActivityCompat.shouldShowRequestPermissionRationale(…)实质也是调用Context的这个方法,参见上文源码。

从字面意思理解,即是否需要合理显示申请权限对话框(注意是系统的不是自定义的)。那么什么时候显示才是合理的呢?我们发现以下规律。

在第一次申请权限前,它返回了false
用户选择接受权限后,它返回false
用户选择拒绝但是没有勾选不再提醒,它返回true
而用户选择拒绝并勾选不再提醒(永久拒绝),它返回了false

因此这个方法并不能用来判断是否需要显示自已的权限说明对话框。
它只能在权限回调里用来判断是否要引导用户到设置页打开权限。
此方法正确使用方式参考上文示例代码。

原创文章 56 获赞 44 访问量 9万+

猜你喜欢

转载自blog.csdn.net/devnn/article/details/100104612