Android 6.0,代号棉花糖,自发布,其主要的特征运行时权限就很受关注。这一特征不仅改善了用户对于应用的使用体验,还使得应用开发者在实践开发中需要做出改变。

6.0 之前权限策略

在6.0 之前,只要用户安装,Manifest申请的权限都会被赋予,并且安装后权限也撤销不了。这种情况下,当安装应用时,会弹出对话框。接收所列出的权限。这种情况下,用户只有两种选择。

  • 接收,即使有dangerous权限
  • 拒绝,不信任应用

6.0 运行时权限

从Android 6.0开始 ,Android引入了新的全新策略。即运行时权限,这里以拍照作为示例。当运行时权限生效,camera权限不是安装之后授予,而是在应用运行时进行请求权限,弹出对话框。是否授权。

权限分组

Android 有各种各样的权限,但是并非所有的权限都是敏感权限,所以权限策略进行了以下分类。

  • 正常(Normal) 权限
  • 危险(Dangerous) 权限
  • 特殊(Particular)权限
  • 其它

正常权限

具有以下特点

  • 对用户隐私没有影响或者没有带来安全问题
  • 安装后就授予了这些权限,不需要提醒用户,用户也不能取消这些权限

正常权限列表

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

如上所列权限基本涉及的是关于网络 蓝牙 时区 快捷方式等,只要在Manifest制订了这些权限,就会被授予,并且不能被撤销。

特殊权限

顾名思义,就是一些特别敏感的权限,在Android中,主要有两个

  • SYSTEM_ALERT_WINDOW ,设置悬浮框,进行一些黑科技
  • WRITE_SETTING 修改系统设置

关于上面两个特殊的权限,做法是使用startActivityForResult 启动授权界面来完成

请求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 Setting.ACTION_MANAGE_OVERLAY_PERMISSION 启动隐式Intent
  • 使用“package" + getPackageName() 携带App的报名信息
  • 使用Setting.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 Setting.ACTION_MANAGE_WRITE_SETTING 启动隐式Intent
  • 使用"package:" + getPackageName()携带App的包名信息
  • 使用Setting.System.canWrite 方法检测授权结果

ps : 关于这两个特殊权限,一般不建议应用申请

危险权限

危险权限实际上才是运行时权限的主要处理对象,这些权限可能会引起隐私问题,或者影响其它程序运行,Android危险权限可以归类为以下几组

  • CALENDAR
  • CAMERA
  • CONTACTS
  • LOCATION
  • MICROPHONE
  • PHONE
  • SENSORS
  • SMS
  • STORAGE

权限分组与具体权限,如下图

6a195423jw1ezwpc11cs0j20hr0majwm.jpg

是否必须要支持运行时权限

目前应用是可以不需要支持运行时权限的,但最终还是需要支持的,只是事件问题。

不支持运行时权限会崩溃吗

可能会,但不是一上来就崩溃那种。
如果你的应用将targetSdkVersion 设置低于23,那么在6.0 的系统上不会为这个应用开启运行时权限机制。

应用运行时权限

支持运行时权限,通常需要使用到如下api

  • int checkSelfPermission(String permission)用来检测应用是否已经具有权限
  • void reuestPermissions(String[] permissions, int requestCode) 进行单个或多个权限
  • void onRequestPermissionResult(int requestCode ,String[] permissions,int[] grantResults) 用户对请求做出响应后的回调

以Camera权限为例

@Override
public void onClick(View v) {
    if (!(checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)) {
        requestCameraPermission();
    }
}

private static final int REQUEST_PERMISSION_CAMERA_CODE = 1;
private void requestCameraPermission() {
    requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CAMERA_CODE);
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_PERMISSION_CAMERA_CODE) {
        int grantResult = grantResults[0];
        boolean granted = grantResult == PackageManager.PERMISSION_GRANTED;
        Log.i(LOGTAG, "onRequestPermissionsResult granted=" + granted);
    }
}

通常会弹出对话框,当用户选择允许,就可以在onRequestPermissionResult 方法中进行想用的处理,比如打开摄像头,当用户拒绝,你的应用可能就开始危险了

shouleShowRequestPermissionRationale 这个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);
}

ps : 间隔较短的多个权限申请建议设置成单次多个申请的形式,避免弹出多个对话框,照成不太好的视觉效果

申请这么多权限是否麻烦

事实上不是每个权限都需要显式申请,举一个例子,如果你的应用授予了读取联系人权限,那么你的应用也是被富裕了写入联系人权限。因为读取联系人和写入联系人都属于联系人权限分组,所以一旦组类某个权限被允许,该组的其它权限也是被允许的。

注意事项

由于checkSelfPermission 和 requestPermissions 是从API 23才假如,低于23版本。需要在运行时判断或者使用support Library v4 中提供的方法

  • ContextCompat.checkSelfPermission
  • ActivityCompat.requestPermissions
  • ActivityCompat.shouldShowPermissionRationale

多系统问题

当我们支持了6.0 必须也需要支持 4.4 5.0之类。所以需要很多情况下,需要两套处理,比如camera

if (isMarshmallow()) {
    requestPermission();//然后在回调中处理
} else {
    useCamera();//低于6.0直接使用Camera
}

两个权限

运行时权限对于应用影响比较大的权限有两个,分别是

  • READ_PHONE_STATE
  • WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE

其中READA_PHONE_STATE用来获取deviceID, 即IMEI号码。这是很多统计依赖计算设备唯一ID的参考,如果新的权限导致读取不到,避免导致统计的异常,建议在完全支持运行时权限之前,将对应的值写入app本地数据中,对于辛安庄的,可以采取其他策略减少对统计的影响

WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE 这两个权限和外置存储有关,对于下载相关的应用这一点还是比较重要的吗我们英高经可能的说明和引导用户授予该权限。

建议

  • 不要使用多余的权限,新增权限时要慎重
  • 使用Intent来替代某些权限,如拨打电话
  • 对于使用权限获取的某些只,如 deviceId 尽量本地存储,下次访问时直接使用本地的数据值
  • 注意,由于用户可以撤销某些权限,所以不要使用应用本地的标志位记录是否获取到这些权限

注意

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

是否支持

个人觉得 6.0 的运行时权限对用户来说非常好。