随着安卓的不断升级,手机的权限也逐渐被google收回,在6.0时出现了动态权限的概念。

知识图

在这里插入图片描述

一、由来介绍

安卓6.0之前应用的权限在安装时就可以全部授予(清单文件声明的),然而这可能造成店大欺客的情况,用户为了安装app,必须同意所有的权限。在 Android 6.0 或更高版本对权限进行了分类,对某些涉及到用户隐私的权限可在运行时根据用户的需要动态授予。这样就不需要在安装时被强迫同意某些权限。

二、权限分类

  • 正常权限

manifest文件声明即可使用,安装apk时授予,app运行时不在提示。

  • 危险权限

涉及到用户隐私、用户数据相关的权限。manifest声明,代码中还要动态申请。

三、权限组

安卓系统对所有的危险权限进行了分组,功能相似的权限方放为一组内。
1、android 6.0时,用户只要同意权限组的任意一个权限,用户则会获得此权限组内的所有权限。
2、android 9.0开始,用户申请某个组内的一个权限,系统不会给同组内的其他权限。用户申请哪个,系统给哪个。

1、危险权限和普通权限的区分

除了20多个危险权限,其他都是普通权限(危险权限如下图)

在这里插入图片描述

注意:app申请使用的危险权限,用户都可以在手机-设置-应用程序信息-权限里面手动打开或者关闭。

2、安卓对危险权限组与权限的封装

封装在Manifest类中,可以使我们快速得到权限或者权限组字符串,而不用自己手写双引号引的字符串,避免了出错(源码如下)

public final class Manifest {
    //危险权限组
    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";
    }
    // 危险权限
 public static final class permission {
        public static final String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER";
        public static final String ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION";
        public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
        public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
        public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
        public static final String ACCESS_LOCATION_EXTRA_COMMANDS = "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS";
        public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
        public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
        public static final String ACCESS_NOTIFICATION_POLICY = "android.permission.ACCESS_NOTIFICATION_POLICY";
        public static final String ACCESS_WIFI_STATE = "android.permission.ACCESS_WIFI_STATE";
        public static final String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER";
        public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
        public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
        public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
        public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
        public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
        public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
        public static final String BIND_AUTOFILL_SERVICE = "android.permission.BIND_AUTOFILL_SERVICE";
        public static final String BIND_CALL_REDIRECTION_SERVICE = "android.permission.BIND_CALL_REDIRECTION_SERVICE";
        public static final String BIND_CARRIER_MESSAGING_CLIENT_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE";
        }

四、运行时请求权限

1、权限申请常用步骤

在这里插入图片描述

2、检查权限
ContextCompat 类中的方法:
 /**
     * Determine whether <em>you</em> have been granted a particular permission.
     * 确定是否已经授权了一个特殊的权限
     * 
     * @param permission The name of the permission being checked.
     * 参数:permission ,将要被检测的权限
     * 
     * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if you have the
     * permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} if not.
     *返回值两种:
     *1、PackageManager.PERMISSION_GRANTED 表示用户请求的权限已经授权
     *2、PackageManager.PERMISSION_DENIED 表示用户请求的权限未授权
     *
     * @see android.content.pm.PackageManager#checkPermission(String, String)
     */
public static int checkSelfPermission(@NonNull Context context, @NonNull String permission)

收获:
1、使用ContextCompat 类的checkSelfPermission来检测是否具有某项权限
2、checkSelfPermission的返回值为两种int值,这两个值封装在PackageManager类中

3、请求权限
ActivityCompat的方法:
    /*
      * @param activity The target activity.
      *  参数:activity
      * @param permissions The requested permissions. Must me non-null and not empty.
      * 参数:要申请的权限字符串数组(非null,非empty)
      * @param requestCode Application specific request code to match with a result
      * 参数:请求码,请求码必须为整数,且大于0
      *    reported to {@link OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])}.
     *    Should be >= 0.
     */
 public static void requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {

收获:
1、此方法为ActivityCompat类的方法
2、requestPermissions执行时会弹系统对话框申请相关的权限。用户可以选择同意授权,或者拒绝授权。对话框关闭之后就走Activity的onRequestPermissionsResult回调了。
3、请求危险权限,之前需要在清单文件声明,否则对话框都不谈,直接就是用户拒绝了这个权限。
4、总是一句话:只要使用权限就必须清单文件先声明,只是危险权限还要动态申请下。

4、请求回调
FragmentActivity的方法:
 /**
     * Callback for the result from requesting permissions. This method
     * is invoked for every call on {@link #requestPermissions(String[], int)}.
     * 请求权限方法回调
     *
     * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}.
     * 参数:请求码(用户申请权限时传递的int值)
     * 
     * @param permissions The requested permissions. Never null.
     * 参数:请求的权限
     * 
     * @param grantResults The grant results for the corresponding permissions
     *     which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED}
     *     or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
     * 参数:授权结果数组
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults)
           

收获:
1、直接在你的activity中重写onRequestPermissionsResult方法即可。因为我们现在一般使用AppCompatActivity,兼容包下的。这个activity是继承FragmentActivity的。

5、简单实战
/**
 * 权限申请步骤:
 * 1、检查有无权限
 *         有权限-> doSomething
 *         无权限->申请权限
 *
 * 2、申请权限(走权限回调)
 *
 *        用户同意->doSomething
 *        用户拒绝->展示跳转设置界面对话框
 *
 * 3、跳转设置对话框
 *
 *        同意跳转->跳转特定的权限打开界面
 *        用户拒绝->Toast提示没权限,功能不能正常使用
 */
public class MainActivity extends AppCompatActivity {
    private static final int REQUEST_CODE = 0x11; // 请求权限请求码
    private String[] permissions;
    private boolean isAllGrant;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 开发是需要的权限
        permissions = new String[]{
                Manifest.permission.READ_CONTACTS,
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
        };
    }

    public void normal(View view) {
        isAllGrant = checkIsAllPermissionGranted(permissions, this);
        if (isAllGrant) {
            // 已有权限 ->做事情
            Toast.makeText(this, "检测到已有权限!", Toast.LENGTH_SHORT).show();
            doWork();

        } else {
            // 没有权限-> 申请权限
            ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE);
        }

    }

    public void frame(View view) {
        startActivity(new Intent(this, FrameActivity.class));
    }


    private void doWork() {
        // todo  有权限时,用户想要做的逻辑
    }

    /**
     * @param permissions 权限数组
     * @return 权限都授权时返回true,否则false
     * @function 检查是否所有的权限都授权
     */
    private boolean checkIsAllPermissionGranted(String[] permissions, Context context) {
        // 遍历检测权限
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    /**
     * 权限回调处理,此时用户还拒绝,则提示他去手机的设置-权限管理界面打开权限
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE) {
            isAllGrant = true;
            for (int grant : grantResults) {
                if (grant == PackageManager.PERMISSION_DENIED) {
                    isAllGrant = false;
                    break;
                }
            }
        }
        if (isAllGrant) {// 同意授权->做事情
            Toast.makeText(MainActivity.this, "授权成功", Toast.LENGTH_SHORT).show();
            doWork();
        } else {
            // 拒绝授权->开弹窗跳询问是否跳设置-权限管理界面
            AlertDialog.Builder builder = new AlertDialog.Builder(this)
                    .setMessage("应用需要您的通讯录和存储权限,请到设置-权限管理中授权。")
                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Intent intent = new Intent();
                            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                            intent.addCategory(Intent.CATEGORY_DEFAULT);
                            intent.setData(Uri.parse("package:" + getPackageName()));
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
                            intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                            startActivity(intent);
                        }
                    }).setCancelable(false)
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Toast.makeText(MainActivity.this, "您没有获得权限,此功能不能正常使用", Toast.LENGTH_SHORT).show();
                        }
                    });
            builder.create().show();
        }
    }
}

五、动态权限框架

这里主要推荐下几个star高的权限请求框架嘿嘿。。。

六 shouldShowRequestPermissionRationale 用途

1、测试代码
 <uses-permission android:name="android.permission.CAMERA"/>
/**
 * Create by SunnyDay /11/01 11:58:56
 */
object CameraPermissionHelper {
    private const val CAMERA_PERMISSION_CODE = 0
    private const val CAMERA_PERMISSION = Manifest.permission.CAMERA

    fun hasCameraPermission(activity: Activity) = ContextCompat.checkSelfPermission(
        activity,
        CAMERA_PERMISSION
    ) == PackageManager.PERMISSION_GRANTED

    fun requestCameraPermission(activity: Activity) {
        ActivityCompat.requestPermissions(
            activity,
            arrayOf(CAMERA_PERMISSION),
            CAMERA_PERMISSION_CODE
        )
    }

    fun shouldShowRequestPermissionRationale(activity: Activity): Boolean =
        ActivityCompat.shouldShowRequestPermissionRationale(
            activity,
            CAMERA_PERMISSION
        )

    fun launchPermissionSetting(activity: Activity) {
        activity.startActivity(Intent().apply {
            action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
            data = Uri.fromParts("package", activity.packageName, null)
        })
    }
}
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 检测权限 无权限时申请。
        Log.d("MainActivity", "onCreate#shouldShowRequestPermissionRationale:${CameraPermissionHelper.shouldShowRequestPermissionRationale(this)}")
        if (!CameraPermissionHelper.hasCameraPermission(this)) {
            CameraPermissionHelper.requestCameraPermission(this)
        }
    }


    /**
     * 权限回调
     * */
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        Log.d("MainActivity", "onRequestPermissionsResult#shouldShowRequestPermissionRationale:${CameraPermissionHelper.shouldShowRequestPermissionRationale(this)}")
        if (!CameraPermissionHelper.hasCameraPermission(this)) {
            Log.d(
                "MainActivity",
                "onRequestPermissionsResult: Camera permission is needed to run this application"
            )
            if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) {
                CameraPermissionHelper.launchPermissionSetting(this)
            }
            finish()
        } else {
            Log.d("MainActivity", "onRequestPermissionsResult: has Permission!")
        }
    }
}
2、系统默认的权限弹窗

如下是android 12 red mi K30 pro 手机测试现象。

todo 插入第一张图片

(1) 点击"本次运行允许":app获得权限,只要不清除数据、不手动关闭权限则app后续一直具有权限。
(2)点击"仅在使用中允许":app获得权限,只要不清除数据、不手动关闭权限则app后续一直具有权限。
(3) 点击"拒绝":app未获取到权限,下次再打开app申请权限时会展示带"不再提示"标语的弹框。

在这里插入图片描述

(1) 点击"本次运行允许":app获得权限,只要不清除数据、不手动关闭权限则app后续一直具有权限。
(2)点击"仅在使用中允许":app获得权限,只要不清除数据、不手动关闭权限则app后续一直具有权限。
(3)点击"拒绝切不再询问":app未获取到权限,且后续触发权限申请也不会弹窗(除非手动清除数据、手动去设置里面更改权限)
代码内部表现是权限回调方法直接返回"无权限"结果

3、log观测&结论

发现只有点击"拒绝" 这一流程获取到的shouldShowRequestPermissionRationale值为true,其他情况都为false。大家也可自行验证下。

结论:单纯的使用shouldShowRequestPermissionRationale去做什么判断,是没用的,因为其值受用户选择影响,其值可能为true,可能为false。建议在权限回调后使用。

当用户拒绝了权限&shouldShowRequestPermissionRationale返回了false 这时用户点击了"拒绝切不再询问",此时直接跳设置。

这篇博客的评论中看到过一位哥们评论的挺nice的:

1,没有申请过权限,申请就是了,所以返回false;
2,申请了用户拒绝了,那你就要提示用户了,所以返回true;
3,用户选择了拒绝并且不再提示,那你也不要申请了,也不要提示用户了,所以返回false;
4,已经允许了,不需要申请也不需要提示,所以返回false;

code 总结

1、基础权限请求练习

2、使用easypermissions练习

参考文献:

官方文档 Permissions overview

https://blog.csdn.net/totond/article/details/73648103

https://www.cnblogs.com/ldq2016/p/7090872.html

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐