Android 6.0&运行时权限 笔记

  • 原生 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

然后是最后的效果图:


Gif 演示

因为系统原生的 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)
}

这应该是最笨的方法了,如果有更好的方法还请告知。

你可能感兴趣的:(Android 6.0&运行时权限 笔记)