Android 权限

参考:

系统权限https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous


在开发 Android 应用时,如果想要获取系统某些特定功能,需要在应用清单上进行权限声明。按照不同的功能需求设置不同的权限;针对不同权限所产生的影响,将权限分为不同级别;Android 系统版本从 Android 6.0(API 级别 23) 开始,需要在运行时动态申请权限


主要内容

  1. 权限浅析
  2. 权限声明与授予
  3. 运行时请求权限
  4. 请求 STORAGE 权限
  5. 权限操作小结

权限浅析

Android 系统将每个应用运行在单独的进程沙盒中,即默认各个应用之间并不产生交互。应用在进程沙盒中可实现的功能不需要权限声明,当应用需要系统提供额外的功能,比如访问网络,读写存储等操作时,需要进行权限声明,Andriod 系统才能授予权限声明的功能。

正常权限和危险权限

Android 系统权限可分为几个级别,主要的两个级别是 正常权限危险权限

  • 正常权限:指需要访问沙盒外部数据或资源,但对用户隐私或者对其他应用操作风险很小的功能。当应用声明了这一类权限,系统将自动授予该权限

  • 危险权限:指需要涉及用户隐私信息的数据或资源,或者对用户存储的数据或其他应用的操作产生影响的功能。对于这一类权限,应用声明后,还需要用户明确向应用授予该权限

正常权限

正常权限包括网络访问的权限,设置时区的权限等等

危险权限

Android 系统将所有的危险权限进行分类,每个危险权限都属于特定的权限组,同一个权限组可包含多个危险权限

Note:权限组中可能同时包含正常权限和危险权限,不过仅有危险权限需要用户在运行时授予,所以可忽略正常权限

具体的危险权限和权限组如下所示:

这里写图片描述


权限声明与授予

需要在应用清单(AndroidManifest.xml)上进行权限声明,使用属性 <uses-permission> 进行标记,示例如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zj.permissiondemo">

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

    <application
        ...
    </application>

</manifest>

上面的代码实现了对网络访问的权限声明

  • 当声明正常权限时,系统会自动授予该权限
  • 当声明危险权限时,系统需要用户显式确认该权限

危险权限授予

Android 系统授予危险权限的方式有两种:

  • 如果系统版本运行在 Android 5.1 (API 级别 22) 或者更低时,系统会在 应用安装时 向用户请求权限。如果用户 不授予 该权限,则应用无法安装;一旦应用授予该权限,撤销权限 的唯一方式是卸载该应用;如果 应用更新时 增加了新的危险权限,同样需要在更新应用时请求权限。
  • 如果系统版本是 Android 6.0(API 级别 23) 或者更高时,应用需要在运行时向用户请求权限,并且用户可通过系统设置对应用权限进行授予或者取消

前面讲到危险权限均有其所属的权限组,同一权限组内可能包含多个危险权限。当应用向用户请求授予危险权限时,并不会描述应用要访问的具体权限,而是描述其所属权限组。而如果同一权限组内的某个危险权限已被授权,那么同组内的其它权限在请求时会自动授予,不再和用户交互。


运行时请求权限

Android 6.0(API 级别 23) 开始,应用需要在运行中请求权限

检查权限

在执行危险权限系统功能时,需要检查是否已授予该权限,使用函数 ContextCompat.checkSelfPermission :

int phonePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE);
Log.e(TAG, "onClick: phonePermission = " + phonePermission);
int locationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
Log.e(TAG, "onClick: locationPermission = " + locationPermission);

上面进行了电话危险权限和定位危险权限的查询,如果返回值为 PackageManager.PERMISSION_GRANTED,表示权限已授予,如果返回为 PackageManager.PERMISSION_DENIED,需要向用户请求权限

请求权限

如果权限已授予,则继续下面的操作即可;如果没有授予,那么调用函数 ActivityCompat.requestPermissions 请求授予权限:

void requestPermissions (Activity activity, 
                String[] permissions, 
                int requestCode)

可以一次性请求多个危险权限,应用会逐个弹出对话框向用户请求

请求权限是一个异步操作,用户授予情况将通过函数 onRequestPermissionsResult 返回:

void onRequestPermissionsResult (int requestCode, 
                String[] permissions, 
                int[] grantResults)

当用户没有授予某项权限,应用将无法访问该权限所给予的系统功能。有两种处理方法

第一种就是应用禁止该权限功能的实现

第二种就是在这里弹出一个对话框,向用户解释为什么需要该权限,然后再次申请该权限

辅助请求权限手段

请求说明

在请求权限之前,可以先向用户说明为什么本应用需要该权限,调用函数 ActivityCompat.shouldShowRequestPermissionRationale 判断是否需要进行说明

boolean shouldShowRequestPermissionRationale (Activity activity, 
                String permission)
  • 如果用户之前已经拒绝过该权限请求,将返回 true

  • 如果是第一次请求,将返回 false

  • 如果在之前的权限请求中,用户拒绝了请求,并在对话框上点击了 Don't ask again(如果有的话),那么也将返回 falseAndroid 7.0 MIUI 8.5.2.0 MI5 手机上进行权限请求时的对话框没有 Don't ask again 选项,拒绝后再 ononRequestPermissionsResult 上调用 shouldShowRequestPermissionRationale 也返回 false,同时仅能请求两次,之后就直接拒绝请求)

请求每个危险权限

即使同一权限组中的其它危险权限已被授予,也需要明确向用户请求权限,虽然系统会立即授予该权限,这是为了预防将来的 Android 系统版本中权限组的变化

手动设置权限

参考:Android中跳转到系统设置界面大全

当用户在弹出的对话框中拒绝了此权限,并提示系统不在提供该权限,比如,点击 Don't ask again(如果有的话)。这种情况下,之后的权限请求将自动拒绝,直接跳转到 onRequestPermissionsResult 并返回 PERMISSION_DENIED,不再与用户进行直接交互

Android 6.0(API 级别 23) 或更高系统需要在运行时授予权限,同时也给予了用户可以手动设置权限请求,打开手机系统设置界面,找到该应用并设置权限

Uri packageURI = Uri.parse("package:" + getPackageName());
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
startActivityForResult(intent, ACTION_TO_SETTING_PAGE);

上面这段代码将从应用跳转到系统设置中的该 app 的详细信息页面,返回时在 onActivityResult 方法中再次判断权限


请求 STORAGE 权限

下面演示如何查询,请求和处理权限操作

读取存储文件是常用的操作,权限组 STORAGE 有两个权限:READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE

参考:Android 文件读写以及assets操作

读取 内部存储 不需要 读写 权限,读取 外部存储 分别需要读写权限,其中,从 Android 4.4(API 级别 19) 开始,读取使用函数 getExternalFileDirgetExternalCacheDir 得到的外部存储路径下的文件不需要读写权限

实现功能:从外部存储中读入一张图片并将该图片重新写入外部存储

声明权限

在应用清单(AndroidManifest.xml)中写入:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zj.permissiondemo">

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

    <application
        ...
    </application>

</manifest>

权限操作

首先检查权限是否已授予:

private boolean checkPermission(String permission) {
    int check = PackageManager.PERMISSION_GRANTED;
    if (Build.VERSION.SDK_INT >= 23) {
        check = checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
    }

    return check == PackageManager.PERMISSION_GRANTED;
}

如果已授予,继续下面操作;否则,需要请求授予权限

请求权限前判断是否需要向用户解释:

private boolean shouldRequestPermission(String permission) {
    boolean flag = false;
    if (Build.VERSION.SDK_INT >= 23) {
        flag = shouldShowRequestPermissionRationale(permission);
    }

    return flag;
}

如果需要解释,自定义一个对话框,向用户进行说明:

private void showPermissionDialog(final String... permissions) {
    AlertDialog.Builder builder = new AlertDialog.Builder(this)
            .setTitle("权限请求")
            .setMessage("请确认权限,以保证应用相应功能正常使用")
            .setPositiveButton("同意", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    onShowPermissionResult(true, permissions);
                }
            })
            .setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    onShowPermissionResult(false, permissions);
                }
            });

    builder.show();
}

针对用户点击 同意 或者 决绝 按钮,自定义函数 onShowPermissionResult 进行操作

private void onShowPermissionResult(boolean flag, String... permissions) {
    if (flag) {
        if (Build.VERSION.SDK_INT >= 23) {
            requestPermissions(permissions, REQUEST_PERMISSION);
        }
    } else {
        finish();
    }
}

请求结果可在回调函数 onRequestPermissionsResult 中获取,如果权限已授予,则继续下面操作;否则,判断是否需要解释请求权限

如果用户已经点击 Don't ask again 选项,那么,请求权限操作无法再与用户交互,需要用户手动开启权限:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    switch (requestCode) {
        case REQUEST_PERMISSION:
            for (int i = 0; i < permissions.length; i++) {
                String permission = permissions[i];
                if (permission.equals(readPermission)) {
                    if (grantResults.length >= i && grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                        showImage();
                    }
                } else {
                    if (shouldRequestPermission(readPermission)) {
                        if (Build.VERSION.SDK_INT >= 23) {
                            requestPermissions(new String[]{readPermission}, REQUEST_PERMISSION);
                        }
                    } else {
                        showSettingDialog();
                    }
                }
            }
            break;
        default:
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            break;
    }
}

showSettingDialog 方法中弹出一个对话框,告诉用户需要手动进行设置:

private void showSettingDialog(final String... permissions) {
    AlertDialog.Builder bulilder = new AlertDialog.Builder(this)
            .setTitle("权限请求")
            .setMessage("请手动开启权限,已保证应用相应功能正常使用")
            .setPositiveButton("确认", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    Uri packageURI = Uri.parse("package:" + getPackageName());
                    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
                    startActivityForResult(intent, ACTION_TO_SETTING_PAGE);
                }
            })
            .setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    onShowPermissionResult(false, permissions);
                }
            });

    bulilder.show();
}

点击确认,将跳转到系统设置该 apk 的详细信息页面,再返回时再通过回调函数 onActivityResult 进行权限操作

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case ACTION_TO_SETTING_PAGE:

            break;
        default:
            super.onActivityResult(requestCode, resultCode, data);
            break;
    }
}

权限操作小结

权限操作其实还是一个比较麻烦的事情,尤其是遇上请求多个权限的问题,经过上面的实践,有一些小小的总结:

  • 可以需要的危险权限分类,将本应用必须使用的危险权限归为一类,在启动应用时就统一申请这些权限,这些权限如果没有完全授予就结束应用
  • 其它辅助类型的危险权限,可在需要时进行授予,如果没有授予就不实现相应功能,并不完全结束应用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值