- 一、前言
- 二、权限分类
- 三、权限申请
- 四、总结
一、前言
安卓平台权限一直有被流氓应用随便利用诟病, android M(SDK 23)的发布彻底解决了这一问题,取而代之的是,app不得不在运行时一个一个询问用户授予权限。
Android 6.0,代号棉花糖,其主要的特征运行时权限就很受关注。因为这一特征不仅改善了用户对于应用的使用体验,还使得应用开发者在实践开发中需要做出改变。Android 6.0(api23)系统中,做了一些限制, 开发者在使用到每条权限时必须自己调用相关代码请求。如果没有获得某项权限,直接使用相关功能,则会导致自己程序crash。
没有深入了解运行时权限的开发者通常会有很多疑问,比如什么是运行时权限,哪些是运行时的权限,我的应用是不是会在6.0系统上各种崩溃呢,如何才能支持运行时权限机制呢。本文讲尝试回答这一些问题,希望读者阅读完成之后,都能找到较为完美的答案。
二、权限分类
Android 6.0以前版本:权限管理总结为一句话是:权限一刀切。
在6.0以前的系统,都是权限一刀切的处理方式,只要用户安装,Manifest申请的权限都会被赋予,并且安装后权限也撤销不了。Android6.0及以后版本:Android M(SDK23) 进行了权限分类,运行时动态申请权限。
尽管Android中有很多权限,但并非所有的权限都是敏感权限,于是6.0系统就对权限进行了分类,一般为下述三类:
正常(Normal)权限
危险(Dangerous)权限
特殊(Particular)权限
-
正常(Normal)权限
一般权限都是一些系统认为比较权限的权限,流氓应用就是拥有这些权限也干不出多大坏事,Normal 权限会在应用安装是直接授权。 正常(Normal)权限的列表:
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指定了这些权限,就会被授予,并且不能撤销。
-
危险(Dangerous)权限
这些权限都是一些敏感性权限,一些广告平台或是流氓应用会用这些权限干一些坏坏的事情,因此系统将这类权限分了几个类别, 应用每次都要检测下是否有权限,没有的化必须弹出对话框申请,只要一个组别中的一个权限得到了授权,整个组的权限都会的到授权。
危险权限实际上才是运行时权限主要处理的对象,这些权限可能引起隐私问题或者影响其他程序运行。Android中的危险权限可以归为以下几个分组:
CALENDAR
CAMERA
CONTACTS
LOCATION
MICROPHONE
PHONE
SENSORS
SMS
STORAGE
各个权限分组与其具体的权限,可以参考下图:
这部分权限也是我们重点在M系统上关注和适配的部分。
6.0的运行时权限,我们最终都是要支持的,通常我们需要使用如下的API:
int checkSelfPermission(String permission) 用来检测应用是否已经具有权限
void requestPermissions(String[] permissions, int requestCode) 进行请求单个或多个权限
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 用户对请求作出响应后的回调
具体如何操作,参考本文:权限申请模块。
-
特殊(Particular)权限
特殊权限,顾名思义,就是一些特别敏感的权限,在Android系统中,主要由两个
SYSTEM_ALERT_WINDOW 设置悬浮窗,进行一些黑科技
WRITE_SETTINGS 修改系统设置
SYSTEM_ALERT_WINDOW and WRITE_SETTINGS, 这两个权限比较特殊,不能通过代码申请方式获取,必须得用户打开软件设置页手动打开,才能授权.
关于上面两个特殊权限的授权,做法是使用startActivityForResult启动授权界面来完成。
官方文档中这样描述:
There are a couple of permissions that don’t behave like normal and dangerous permissions. SYSTEM_ALERT_WINDOW and WRITE_SETTINGS are particularly sensitive, so most apps should not use them. If an app needs one of these permissions, it must declare the permission in the manifest, and send an intent requesting the user’s authorization. The system responds to the intent by showing a detailed management screen to the user.
例如:请求SYSTEM_ALERT_WINDOW,设置悬浮窗
private static final int REQUEST_CODE_OVERLAY_PERMISSION = 110;
private void requestAlertWindowPermission() {
if (!Settings.canDrawOverlays(getBaseContext())) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE);
}
}
protected void onActivityResult1(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_OVERLAY_PERMISSION) {
if (Settings.canDrawOverlays(this)) {
// 已成功授权
}else{
// 未授权
}
}
}
提示
- 使用Action Settings.ACTION_MANAGE_OVERLAY_PERMISSION启动隐式Intent
- 使用"package:" + getPackageName()携带App的包名信息
- 使用Settings.canDrawOverlays()方法来判断授权结果
请求修改设置权限 WRITE_SETTINGS
private static final int REQUEST_CODE_WRITE_SETTINGS = 120;
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.System.canWrite方法检测授权结果
注意:关于这两个特殊权限,除非需要,否则一般不建议应用申请。
三、权限申请
前面已经提到,系统将这类权限分了几个类别, 应用每次都要检测下是否有权限,没有的化必须弹出对话框申请,只要一个组别中的一个权限得到了授权,整个组的权限都会的到授权。
6.0的运行时权限申请时,通常我们需要使用如下的API:
int checkSelfPermission(String permission) 用来检测应用是否已经具有权限,参数就是对应的权限名,如:相机权限是Manifest.permission.CAMERA
void requestPermissions(String[] permissions, int requestCode) 进行请求单个或多个权限
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 用户对请求作出响应后的回调
提示:
开发过程中,我们不是马上就申请权限,而是先判断是否已具有权限,方法是int checkSelfPermission(String permission) 。如果没有权限,才去申请void requestPermissions(String[] permissions, int requestCode) 。然后,拿到回调void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) ,判断用户是否授权了。
下面以以一个请求读取联系人的权限为例进行说明:
API的讲解就跟着申请权限步骤一起了:
- 第一步:在AndroidManifest文件中添加需要的权限。
这个步骤和我们之前的开发并没有什么变化,试图去申请一个没有在AndroidManifest文件中声明的权限可能会导致程序崩溃。
- 第二步:检查权限
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
}else{
//
}
这里涉及到一个API,ContextCompat.checkSelfPermission,主要用于检测某个权限是否已经被授予,方法返回值为PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。当返回DENIED就需要进行申请授权了。
- 第三步:申请授权
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
该方法是异步的,第一个参数是Context;第二个参数是需要申请的权限的字符串数组;第三个参数为requestCode,主要用于回调的时候检测。可以从方法名requestPermissions以及第二个参数看出,是支持一次性申请多个权限的,系统会通过对话框逐一询问用户是否授权。
- 第四步:处理权限申请回调
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
}
}
ok,对于权限的申请结果,首先验证requestCode定位到你的申请,然后验证grantResults对应于申请的结果,这里的数组对应于申请时的第二个权限字符串数组。如果你同时申请两个权限,那么grantResults的length就为2,分别记录你两个权限的申请结果。如果申请成功,就可以做你的事情了~
当然,到此我们的权限申请的步骤,基本介绍就如上述。不过还有个API值得提一下:
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS))
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
}
这个API主要用于给用户一个申请权限的解释,该方法只有在用户在上一次已经拒绝过你的这个权限申请。也就是说,用户已经拒绝一次了,你又弹个授权框,你需要给用户一个解释,为什么要授权,则使用该方法。
- 小结:那么将上述几个步骤结合到一起就是:
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}
四、总结
好在运行时相关的API也比较简单,所以适配起来并不会非常痛苦。对于其他的权限,其实申请的逻辑是类似的;唯一不同的肯定就是参数。
Android6.0版本最大的特性:权限。对于6.0以下的权限及在安装的时候,根据权限声明产生一个权限列表,用户只有在同意之后才能完成app的安装,造成了我们想要使用某个app,就要默默忍受其一些不必要的权限(比如不是每个app都要访问通讯录、短信等)。而在6.0以后,我们可以直接安装,当app需要我们授予不恰当的权限的时候,我们可以予以拒绝。当然你也可以在设置界面对每个app的权限进行查看,以及对单个权限进行授权或者解除授权。
特别提示
在测试过程中,用的锤子·坚果2,发现不弹授权框。换了部乐视2手机,可以弹出。
所以总结:以下原因不会弹框
- 6.0以下版本(系统自动申请)
- 暂时发现锤子手机、vivo、oppo、魅族的6.0以上版本
因为这些厂商修改了6.0系统申请机制,他们修改成系统自动申请权限了。也就是说这些系统会跟以前 6.0 以下的版本一样,需要用到权限的时候系统会自动申请,就算我们主动申请也是没用的。