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

Android6.0权限介绍

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

  • 正常(Normal Protection)权限:

    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)权限

    CALENDAR
    CAMERA
    CONTACTS
    LOCATION
    MICROPHONE
    PHONE
    SENSORS
    SMS
    STORAGE

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

  • 特殊(Particular)权限

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

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

运行时权限处理

特殊权限

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

请求SYSTEM_ALERT_WINDOW权限

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

    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权限为例:

    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可以帮我们判断接下来的对话框是否包含”不再询问“选择框。因此,一个标准的申请权限流程如下:

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

批量申请权限

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

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
    @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声明,因为市场应用会根据这个信息和硬件设备进行匹配,决定你的应用是否在该设备上显示。

相关开源项目