- 原生 API 的使用
- 实践中问题
首先介绍一下,Android 6.0 的权限机制的变化大家都知道,从原来安装时一次性全部授权,变为在运行时才向用户申请授权。
当然在 6.0 也不是全部的权限都需要运行时授权,现在的权限分为两类:
Normal Permissions,一般不涉及用户隐私,比如手机震动、访问网络等。
Dangerous Permission,涉及到用户隐私的,比如访问通讯录等,这一类的权限就需要运行时授权。
还有比较特别的一点是,这些危险权限是分组的,比如第一组的权限和联系人有关,当我们获取到用户授予的permission:android.permission.WRITE_CONTACTS
权限时,同组下的权限就默认一起获得,不需要再次申请。
下面是危险权限一览,不在这里的权限的申请和 6.0 以前是一样的。
//联系人
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
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
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
permission:android.permission.ACCESS_COARSE_LOCATION
//存储
group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
//麦克风
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
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS
然后是最后的效果图:
因为系统原生的 API 中,如果用户选择了「不再提示」则不会再弹出申请权限的对话框。
但是假设我的 APP 依靠蓝牙向设备发送信息,但用户未授予蓝牙权限,则这个 APP 就无作为了,所以要捕获到这种情况并且弹出对话框。
还有一点比较重要的,下面一部分代码在魅族、小米等系统上是无效的,猜测是因为它们有自己的权限机制,关于这些情况的处理后面再说。
原生 API 的实践
参考官网中文教程:在运行时请求权限
- 检查权限
if(ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
}
checkSelfPermission()
方法传入 context 和需要申请的权限,返回值:
public static final int PERMISSION_GRANTED = 0;
public static final int PERMISSION_DENIED = -1;
当为 PERMISSION_GRANTED
时则已经获取到权限。
- 申请权限
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 10)
requestPermissions()
方法传入 context,申请的权限的字符串数组,回调返回的参数。
所以该方法可以一次申请多个权限,并且申请结果会回调到 Activity 的 onRequestPermissionsResult()
方法中。
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
//requstCode 是 requestPermissions 传入的第三个参数
//permissions[] 是申请的权限的数组
//grantResults[] 是申请的结果
}
- 特殊的方法:
ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_COARSE_LOCATION))
方法传入 context 和 权限名,如果用户在申请权限对话框中拒绝了申请,则该方法返回 true。
也就是当用户拒绝了一次申请后,APP 再次申请的时候应该解释为什么需要申请该权限。
还有就是如果用户选择了「不再提示」,该方法返回 false。
- 完整代码
这里用上了 Kotlin 和 Anko,如果看不懂就该补习了。
fun getPermission(){
if (ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_COARSE_LOCATION)){
//此时用户已经拒绝了一次申请,所以弹出对话框解释申请的原因
alert("申请开启定位权限,如果不打开定位权限,将不能运行该 APP","申请权限"){
positiveButton("同意",{
ActivityCompat.requestPermissions(act,
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 10)
})
}.show()
}else {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 10)
}
}else{
toast("已拥有权限")
}
}
//申请方法的回调
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 10){
//requestCode 为 10 时,才是对于申请方法的回调。
if (grantResults[0] == PackageManager.PERMISSION_GRANTED){
toast("申请权限成功")
}else{
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_COARSE_LOCATION)) {
//在回调中如果发现用户拒绝了申请,则再一次弹出对话框
//即使用户选择了「不再提示」这个对话框也会弹出
alert("如果不打开定位权限,将不能使用蓝牙,是否跳转到应用详情页面", "申请权限") {
positiveButton("确定", {
//将会跳转到应用的详情页面
val localIntent = Intent()
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
localIntent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
localIntent.data = Uri.fromParts("package", packageName, null)
startActivity(localIntent)
})
negativeButton("取消", { dialog -> dialog.dismiss() })
}.show()
}
}
}
}
遇到的问题
就如上面说的,我在魅族和小米的手机上都试了一下,这里的代码也不能说完全无用,但是会出很多问题。
比如说在魅族的手机上(魅蓝 note 5,Flyme 6.0.2.1A),无论授权与否,checkSelfPermission()
方法都只会返回 0,也就是已授权的返回值 。
这种情况下,比如我需要打开蓝牙,那就直接执行打开蓝牙的方法,假如报错、或者隔了一段时间后还是没有打开,就弹出对话框提示用户手动打开。
if (mBluetoothAdapter == null){
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val bluetoothAdapter = bluetoothManager.adapter
}
var isEnable = bluetoothAdapter?.isEnabled ?: false
if(!isEnable){
//尝试申请权限
bluetoothAdapter?.enable()
}
...
//延时五秒后再一次判断
if (mBluetoothAdapter?.isEnabled == false){
//跳转到应用详情页面
val localIntent = Intent()
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
localIntent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
localIntent.data = Uri.fromParts("package", packageName, null)
startActivity(localIntent)
}
这应该是最笨的方法了,如果有更好的方法还请告知。