说起权限问题,iOS平台处理的非常严谨,几乎所有的权限都需要在运行时由用户来确认是否允许应用使用此权限。android平台稍微好一点,一部分权限只需要在AndroidManifest.xml中申请一下就可以使用,一部分却需要在运行时由用户来确定。今天就来说说Android的运行时权限问题。

简介

运行时权限是在Android6.0才出现的。权限能很好的保护用户的隐私。谷歌将权限分为两类,一类是Normal Permissions,这类权限一般不涉及用户的隐私,不需要用户进行授权,只需要在AndroidManifest.xml中申请就可以。另一类是Dangerous Permission,这类一般都是涉及到用户的隐私的,不仅需要在AndroidManifest.xml中申请,还需要用户进行授权,比如读取SD卡等。

Normal Permissions 如下:

ACCESS_LOCATION_EXTRA_COMMANDS 允许应用程序访问额外的位置提供命令
ACCESS_NETWORK_STATE 允许程序访问有关GSM网络信息
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE 允许程序访问Wi-Fi网络状态信息
BLUETOOTH 允许程序连接到已配对的蓝牙设备
BLUETOOTH_ADMIN 允许程序发现和配对蓝牙设备
BROADCAST_STICKY 允许一个程序广播常用intents
CHANGE_NETWORK_STATE 允许程序改变网络连接状态
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE 允许程序改变Wi-Fi连接状态
DISABLE_KEYGUARD 允许程序禁用键盘锁
EXPAND_STATUS_BAR 允许一个程序扩展收缩在状态栏
GET_PACKAGE_SIZE 允许一个程序获取任何package占用空间容量
INSTALL_SHORTCUT
INTERNET 允许程序打开网络套接字
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS 允许程序修改全局音频设置
NFC
READ_SYNC_SETTINGS 允许程序读取同步设置
READ_SYNC_STATS 允许程序读取同步状态
RECEIVE_BOOT_COMPLETED 允许程序接收到 ACTION_BOOT_COMPLETED 广播在系统完成启动
REORDER_TASKS 允许程序改变Z轴排列任务
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE 允许程序设置时间区域
SET_WALLPAPER 允许程序设置壁纸
SET_WALLPAPER_HINTS 允许程序设置壁纸hits
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE 允许访问振动设备
WAKE_LOCK 允许使用PowerManager的 WakeLocks保持进程在休眠时从屏幕消失
WRITE_SYNC_SETTINGS 允许程序写入同步设置

Dangerous Permissions 如下:

group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS 允许程序写入但不读取用户联系人数据
permission:android.permission.GET_ACCOUNTS 访问一个帐户列表在Accounts Service中
permission:android.permission.READ_CONTACTS 允许程序读取用户联系人数据
group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE 读取电话状态
permission:android.permission.CALL_PHONE 允许一个程序初始化一个电话拨号不需通过拨号用户界面需要用户确认
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP 允许程序使用SIP视频服务
permission:android.permission.PROCESS_OUTGOING_CALLS 允许程序监视、修改有关播出电话
permission:com.android.voicemail.permission.ADD_VOICEMAIL
group:android.permission-group.CALENDAR
permission:android.permission.READ_CALENDAR 允许程序读取用户日历数据
permission:android.permission.WRITE_CALENDAR 允许一个程序写入但不读取用户日历数据
group:android.permission-group.CAMERA
permission:android.permission.CAMERA 请求访问使用照相设备
group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS
group:android.permission-group.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION 允许一个程序访问精良位置(如GPS)
permission:android.permission.ACCESS_COARSE_LOCATION 允许一个程序访问CellID或WiFi热点来获取粗略的位置
group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE 允许程序读取外部存储,如SD卡读取文件
permission:android.permission.WRITE_EXTERNAL_STORAGE 允许程序写入外部存储,如SD卡上写文件
group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO 允许程序录制音频
group:android.permission-group.SMS
permission:android.permission.READ_SMS 允许程序读取短信息
permission:android.permission.RECEIVE_WAP_PUSH 允许程序监控将收到WAP PUSH信息
permission:android.permission.RECEIVE_MMS 允许一个程序监控将收到MMS彩信,记录或处理
permission:android.permission.RECEIVE_SMS 允许程序监控一个将收到短信息,记录或处理
permission:android.permission.SEND_SMS 允许程序发送SMS短信
permission:android.permission.READ_CELL_BROADCASTS

可以看到,危险权限都是一组一组的,那么分组的意义何在呢?

如果你申请某个危险的权限,假设APP早已经被用户授权了同一组的某个危险权限,那么系统会立即授权,而不需要用户去点击授权,比如当APP对READ_CONTACTS已经授权了,当你申请WRITE_CONTACTS时,系统会直接授权通过。另外,对于申请时弹出的dialog上面的文本说明也是对整个权限组的说明,而不是单个权限。不过需要注意的是,不要对权限组依赖过多,尽可能对每个危险权限都进行正常流程的申请,因为权限组可能会在后期的版本中有变化。

请求权限

请求权限的步骤分为如下几步:

1 . 在AndroidManifest.xml文件中添加需要申请的权限。如果不添加,可能会崩溃。

2 . 检查权限

if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
//请求权限
}else {
//业务
}
}else {
//6.0以下不需要运行时权限 业务
}

这里判断某权限是否被用户授权的方法是:checkSelfPermission,方法接收一个参数,为权限名称。此方法为ContextWrapper的实例方法,而我们的Activity一般都间接继承于ContextWrapper,所以,可以在Activity中直接调用此方法。方法返回两种结果:PackageManager.PERMISSION_DENIEDPackageManager.PERMISSION_GRANTED,当返回DENIED就需要申请授权了。

3 . 申请授权

requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},10086);

此方法定义在Activity中,接收;两个参数,第一个为需要申请的权限的字符串数组,第二个为请求码。主要用于回调的时候检测,系统允许我们一次性申请多个权限,系统会逐一询问用户。注意,此方法为异步。

4 . 处理权限申请回调

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 10086) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 表明用户同意权限 业务
}else {
//用户不同意
}
}
}

此回调方法回回传三个参数,第一个为请求码,就是我们在请求权限时候传多去的请求码,第二个是权限数组,第三个是请求结果数组。 这里,首先校验权限码,然后,判断grantResults的长度是否大于0,因为这里我们申请的只有一种权限,所以我直接判断数组中的第一个元素是否为PackageManager.PERMISSION_GRANTED,如果我们一次申请多个权限,需要分别判断。

基本的申请权限步骤已经介绍完毕。

完整的代码如下:

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
//请求权限
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},10086);
}else {
//业务
}
}else {
//6.0以下不需要运行时权限 业务
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 10086) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 表明用户同意权限
}else {
//用户不同意
}
}
}
}

封装

虽然权限处理并不麻烦,但是需要编写很多重复的代码,所以,这里推荐一个库:MPermissions,用起来还是很方便的。

引入

首先,在项目的build.gradle中添加如下代码:

buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
}
}

然后在appbuid.gradle中添加如下代码:

apply plugin: 'com.neenbedankt.android-apt'
dependencies {
apt 'com.zhy:mpermission-compiler:1.0.0'
compile 'com.zhy:mpermission-api:1.0.0'
}
使用
  • 申请权限
MPermissions.requestPermissions(MainActivity.this, 100, Manifest.permission.WRITE_EXTERNAL_STORAGE);
  • 处理权限回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
MPermissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}

完整的代码:

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//申请权限 权限码很重要
MPermissions.requestPermissions(MainActivity.this, 100, Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
//处理权限回调
MPermissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
//会根据权限码 来映射方法 执行方法
@PermissionGrant(100)
public void doSomething(){
Toast.makeText(this, "Contact permission is granted", Toast.LENGTH_SHORT).show();
}
}