Android 6.0运行时权限小结

Android 6.0之后需要动态申请权限

Android6.0权限介绍

Google在Android6.0系统之后对权限进行了分类:

  • 正常(Normal Protection)权限:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    ACCESS_LOCATION_EXTRA_COMMANDS
    ACCESS_NETWORK_STATE
    ACCESS_NOTIFICATION_POLICY
    ACCESS_WIFI_STATE
    BLUETOOTH
    BLUETOOTH_ADMIN
    BROADCAST_STICKY
    CHANGE_NETWORK_STATE
    CHANGE_WIFI_MULTICAST_STATE
    CHANGE_WIFI_STATE
    DISABLE_KEYGUARD
    EXPAND_STATUS_BAR
    GET_PACKAGE_SIZE
    INTERNET
    KILL_BACKGROUND_PROCESSES
    MODIFY_AUDIO_SETTINGS
    NFC
    READ_SYNC_SETTINGS
    READ_SYNC_STATS
    RECEIVE_BOOT_COMPLETED
    REORDER_TASKS
    REQUEST_INSTALL_PACKAGES
    SET_TIME_ZONE
    SET_WALLPAPER
    SET_WALLPAPER_HINTS
    TRANSMIT_IR
    USE_FINGERPRINT
    VIBRATE
    WAKE_LOCK
    WRITE_SYNC_SETTINGS
    SET_ALARM
    INSTALL_SHORTCUT
    UNINSTALL_SHORTCUT
  • 危险(Dangerous)权限

    1
    2
    3
    4
    5
    6
    7
    8
    9
    CALENDAR
    CAMERA
    CONTACTS
    LOCATION
    MICROPHONE
    PHONE
    SENSORS
    SMS
    STORAGE

权限分组及其具体权限如下:
此处输入图片的描述

  • 特殊(Particular)权限

    1
    2
    SYSTEM_ALERT_WINDOW,设置悬浮窗
    WRITE_SETTINGS 修改系统设置
  • 其他权限(一般很少用到)

Android6.0在之前AndroidManifest.xml声明权限的基础上对危险权限和特殊权限新增了运行时权限,需要动态获取权限。

运行时权限处理

特殊权限

特殊权限的做法是使用startResultActivity启动授权界面来完成。

请求SYSTEM_ALERT_WINDOW权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static final int REQUEST_CODE = 1;
private void requestAlertWindowPermission() {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE) {
if (Settings.canDrawOverlays(this)) {
Log.i(LOGTAG, "onActivityResult granted");
}
}
}

上述代码需要注意的是

  • 使用Action Settings.ACTION_MANAGE_OVERLAY_PERMISSION启动隐式Intent
  • 使用”package:” + getPackageName()携带App的包名信息
  • 使用Settings.canDrawOverlays方法判断授权结果

    请求WRITE_SETTINGS

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    private static final int REQUEST_CODE_WRITE_SETTINGS = 2;
    private void requestWriteSettings() {
    Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
    intent.setData(Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, REQUEST_CODE_WRITE_SETTINGS );
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_WRITE_SETTINGS) {
    if (Settings.System.canWrite(this)) {
    Log.i(LOGTAG, "onActivityResult write settings granted" );
    }
    }
    }

上述代码需要注意的是

  • 使用Action Settings.ACTION_MANAGE_WRITE_SETTINGS启动隐式Intent
  • 使用”package:” + getPackageName()携带App的包名信息
  • 使用Settings.canDrawOverlays方法判断授权结果

    危险权限

    我们需要使用以下API:

  • int checkSelfPermission(String permission) 用来检测应用是否已经具有权限

  • void requestPermissions(String[] permissions, int requestCode) 进行请求单个或多个权限
  • void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 用户对请求作出响应后的回调
    以请求READ_EXTERNAL_STORAGE权限为例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    private static final int READ_EXTERNAL_STORAGE_REQUEST_CODE = 1;
    @Override
    public void onClick(View v) {
    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, READ_EXTERNAL_STORAGE_REQUEST_CODE);
    }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == READ_EXTERNAL_STORAGE_REQUEST_CODE){
    if (grantResults[0] == PackageManager.PERMISSION_GRANTED){
    Intent intent = new Intent(Intent.ACTION_PICK, null);
    intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_TYPE);
    startActivityForResult(intent, RESULT_IMAGE);
    }else{
    Snackbar.make(recyclerView,"没有权限臣妾做不到呀",Snackbar.LENGTH_INDEFINITE).setAction("再次获取权限", new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, READ_EXTERNAL_STORAGE_REQUEST_CODE);
    }
    });
    }
    }
    }

请求权限后,系统会弹出请求权限的Dialog
此处输入图片的描述
当用户选择允许或拒绝,我们就可以在onRequestPermissionsResult方法中进行响应的处理,如果用户拒绝,应用再次申请权限将会出现不在询问的选项:
此处输入图片的描述
当用户勾选了”不再询问“拒绝后,应用的这个权限就无法使用了。
不过,你还有一丝希望,那就是再出现上述的对话框之前做一些说明信息,比如你使用这个权限的目的(一定要坦白)。

shouldShowRequestPermissionRationale这个API可以帮我们判断接下来的对话框是否包含”不再询问“选择框。因此,一个标准的申请权限流程如下:

1
2
3
4
5
6
7
8
if (!(checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED)) {
if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
Toast.makeText(this, "Please grant the permission this time", Toast.LENGTH_LONG).show();
}
requestReadContactsPermission();
} else {
Log.i(LOGTAG, "onClick granted");
}

批量申请权限

只需要字符串数组放置多个权限即可:

1
2
3
4
5
private static final int REQUEST_CODE = 1;
private void requestMultiplePermissions() {
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_STATE};
requestPermissions(permissions, REQUEST_CODE);
}

此处输入图片的描述

权限很多呢

前面权限介绍已经提到了权限分组,举一个例子,如果你的应用授权了读取联系人的权限,那么你的应用也是被赋予了写入联系人的权限。因为读取联系人和写入联系人这两个权限都属于联系人权限分组,所以一旦组内某个权限被允许,该组的其他权限也是被允许的。

Fragment中运行时权限的特殊处理

  • 在Fragment中申请权限,不要使用ActivityCompat.requestPermissions, 直接使用Fragment的requestPermissions方法,否则会回调到Activity的onRequestPermissionsResult
  • 如果在Fragment中嵌套Fragment,在子Fragment中使用requestPermissions方法,onRequestPermissionsResult不会回调回来,建议使用getParentFragment().requestPermissions方法,
    这个方法会回调到父Fragment中的onRequestPermissionsResult,加入以下代码可以把回调透传到子Fragment
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    List<Fragment> fragments = getChildFragmentManager().getFragments();
    if (fragments != null) {
    for (Fragment fragment : fragments) {
    if (fragment != null) {
    fragment.onRequestPermissionsResult(requestCode,permissions,grantResults);
    }
    }
    }
    }

注意

即使支持了运行时权限,也要在Manifest声明,因为市场应用会根据这个信息和硬件设备进行匹配,决定你的应用是否在该设备上显示。

相关开源项目

蔡小木 wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!