Android 6.0之后的权限适配处理
一个基本的android程序是没有任何权限的。也就是说,无论是从用户体验上和设备数据上都没有什么危害。 在产品需求下,为了能够使用设备的受保护特性,你必须在AndroidManifest.xml 里声明至少一种所需要的权限。
例如,一个程序需要管理收到的短信,需要指定:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app.myapp" >
<uses-permission android:name="android.permission.RECEIVE_SMS" />
...
</manifest>
在安装应用程序时,安装者要基于应用程序的签名在交互时对应用程序所需的权限进行授权。应用程序运行时不会进行权限检查:它要么在安装后被给予一个特殊的权限,并且可以使用它期望的权限,要么就不被授予权限,任何使用这些权限的操作都会在没有用户提示的情况下自动失败。
通常如果请求权限失败应用程序会抛出一个SecurityException异常,但是也有特例。例如,sendBroadcast(Intent)函数在所有数据被投递到接收者时检查权限,当函数返回后,不会对数据的权限进行检查,也不能接收到任何权限异常。约大多数情况下,权限异常会记录在日志中。
在Android6.0(M)之后,对权限进行了分类,大致有这三种:
- 普通权限
- 危险权限
- 特殊权限
普通权限
也就是正常权限,是对手机的一些正常操作,对用户的隐私没有太大影响的权限,比如手机的震动,网络访问,蓝牙等权限,这些权限会在应用被安装的时候默认授予,用户不能拒绝,也不能取消。
普通权限列表:
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
对于上面这些权限,需要和Android6.0(M)之前的系统,在AndroidManifest.xml声明即可。
危险权限
其实就是运行中需要处理的权限,也是我们最需要注意的权限,这些权限会关系到用户的隐私或影响到其他应用的运行,这些危险权限,谷歌还做了一个权限组,以分组的形式来呈现:
例如手机淘宝的权限适配:
在代码中进行权限适配
只需要记住下面几个API方法就可以:(API23之后提供)
int checkSelfPermission(String permission) 用来检测应用是否已经具有权限
void requestPermissions(String[] permissions, int requestCode) 进行请求单个或多个权限
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 请求权限结果回调
下面来段代码示例(为了向下兼容,这里我采用了v4包下的ContextCompat和ActivityCompat):
btnGetPermissions.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//判断当前系统是否高于或等于6.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//当前系统大于等于6.0
if (ContextCompat.checkSelfPermission(MineInforActivity.this,Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
//具有拍照权限,直接调用相机
//具体调用代码
} else {
//不具有拍照权限,需要进行权限申请
ActivityCompat.requestPermissions(MineInforActivity.this,new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CAMERA_CODE);
}
} else {
//当前系统小于6.0,直接调用拍照
}
}
});
- 1.这里PERMISSION_GRANTED表示具有权限,PERMISSION_DENIED表示无权限
- 2.在判断应用没有相关权限的后,我们通过requestPermissions进行权限申请,这里的 String[] permissions是个字符串数组,可以对多个权限进行申请
- 3.REQUEST_PERMISSION_CAMERA_CODE是个标识码,类似Intent跳转的REQUEST_CODE的,然后我们就可以在onRequestPermissionsResult进行
权限申请的回调处理:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_PERMISSION_CAMERA_CODE) {
if (grantResults.length >= 1) {
int cameraResult = grantResults[0];//相机权限
boolean cameraGranted = cameraResult == PackageManager.PERMISSION_GRANTED;//拍照权限
if (cameraGranted) {
//具有拍照权限,调用相机
} else {
//不具有相关权限,给予用户提醒,比如Toast或者对话框,让用户去系统设置-应用管理里把相关权限开启
}
}
}
}
上面的那张图,有的朋友应该已经留意到了有个不再提醒的勾选框,如果用户勾选了不再提醒,然后把你拒绝了,那你的应用就GG了,其实这里还有一个API方法:
if(!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)){
//如果用户勾选了不再提醒,则返回false
//给予用户提醒,比如Toast或者对话框,让用户去系统设置-应用管理里把相关权限打开
}
当用户勾选了不再提醒的框并把你拒之门外了,这个方法的返回值是true,它可以帮助你再一次的提醒用户需要这个权限。
但是在实际开发中,不得不说的一个坑,由于国内第三方ROM对系统改造的太严重,比如小米,亲测有些机型的这个方法是不起作用的,永远的是返回false,这个时候该怎么办,就要另外想解决方案了。
还有需要注意的一点是上面的权限分组,比如读写文件权限:
WRITE_EXTERNAL_STORAGE
READ_EXTERNAL_STORAGE
它们是属于同一个权限组的,你如果拿到了他们其中的一个权限,那么也同时会有另一个权限,同理,如果你拿到读取通讯录的权限,那么你同事也会拥有写入通讯录的权限,这样就避免了我们在申请相关权限的时候需要些老长老长的权限代码了。
特殊权限
特殊权限,比如:
系统级别对话框:
SYSTEM_ALERT_WINDOW
修改系统设置:
WRITE_SETTINGS
这2个特殊权限,我们需要在startActivityForResult里调用即可,这2个权限一般是不会用到,会用到的地方要么是黑科技或者是反用户体验的场景,这里就不再做过多描述,有兴趣的朋友自己探索吧。
这里需要另外提到的一个权限:
READ_PHONE_STATE
我们可以通过这个权限来获取机器的唯一标识码,很多第三方统计是基于这个标识码来完成统计的,但是在我们应用一开始运行的时候,这个运行权限我们是没有的,在Application里我们也不能对权限进行获取,所以这点也需要我们去注意。
最后
对于一些比较特别的权限,比如文件的读写权限,一般在我们第一次开启APP的时候就要去获取了,假设我们一开始没有获取到这个权限,那么如果我的首页有轮播广告图,这个广告图是网络获取的,做了三级缓存,这样就会到导致磁盘缓存无法写入。这边提供一个解决方法,就是在你引导APP启动的时候,就引导用户去获取权限,当用户拒绝的时候,应该给出弹出框并跳转对应的应用权限管理界面(需要对不同机型进行设置)。
可以参考微信的做法:
启动app,在闪屏页的时候向用户提出权限的申请:
1.存储空间权限,关闭微信
2.电话权限,关闭微信
3.位置权限,关闭微信
进入app:
1.发照片时,申请照片权限
2.发语音时,申请麦克风权限
3.用户每次点击拒绝,都弹出自定义对话框,提示用户设置权限
关于在AndroidManifest.xml中声明强制权限
进入系统或应用程序的组件的高级别权限可以在AndroidManifest.xml中实现。所有这些都可以通过在相应的组件中包含 android:permission 属性,以使其被用以控制进入的权限。
Activity权限(应用于activity标签)用于限制谁才可以启动相关的Activity。该权限在运行Context.startActivity()和Activity.startActivityForResult()期间被检查;如果调用方不具有相应必需的权限,那么将会从此次调用中抛出SecurityException 异常。
Service权限(应用于service标签)用于限制谁才可以启动或绑定该service。在运行Context.startService() , Context.stopService()和Context.bindService()调用的时候会进行权限检查。如果调用方没有所需的权限,则会抛出一个SecurityException异常。
BroadcastReceiver许可(应用于receiver标签)用于限制谁可以向相关的接收器发送广播。权限检查会在Context.sendBroadcast()返回后当系统去发送已经提交的广播给相应的Receiver时进行。最终,一个permission failure不会再返回给调用方一个异常,只是不会去实现该Intent而已。同样地,Context.registerReceiver()也可以通过自己的permission用于限制谁可以向一个在程序中注册的receiver发送广播。另一种方式是,一个permission也可以提供给Context.sendBroadcast() 用以限制哪一个BroadcastReceiver才可以接收该广播。(见下文)
ContentProvider许可(应用于provider标签)用于限制谁才可以访问ContentProvider提供的数据。(Content providers有一套额外的安全机制叫做URI permissions,这些在稍后讨论。)不像其他组件,它有两个单独的权限属性,你可以设置: android:readPermission用于限制谁能够读,android:writePermission用于限制谁能够写。需要注意的是如果provider同时需要读写许可,只有写许可的情况下并不能读取provider中的数据。当你第一次检索内容提供者和当完成相关操作时会进行权限检查。(假如没有任何权限则会抛出SecurityException异常。)使用ContentResolver.query()需要持有读权限;使用ContentResolver.insert(),ContentResolver.update(),ContentResolver.delete()需要写权限。在所有这些情况下,没有所需的权限将会导致抛出SecurityException异常。