zoukankan      html  css  js  c++  java
  • Android 6.0 运行时权限处理完全解析*

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lmj623565791/article/details/50709663

    转载请标明出处: 
    http://blog.csdn.net/lmj623565791/article/details/50709663; 
    本文出自:【张鸿洋的博客】

    一、概述

    随着Android 6.0发布以及普及,我们开发者所要应对的主要就是新版本SDK带来的一些变化,首先关注的就是权限机制的变化。对于6.0的几个主要的变化,查看查看官网的这篇文章http://developer.android.com/intl/zh-cn/about/versions/marshmallow/android-6.0-changes.html,其中当然包含Runtime Permissions

    ok,本篇文章目的之一就是对运行时权限处理的一个介绍,以及对目前权限相关的库的一些了解。

    当然非常推荐阅读官网权限相关文章:

    本文也是在上述文章基础上理解、实验以及封装。

    二、运行时权限的变化及特点

    对于6.0以下的权限及在安装的时候,根据权限声明产生一个权限列表,用户只有在同意之后才能完成app的安装,造成了我们想要使用某个app,就要默默忍受其一些不必要的权限(比如是个app都要访问通讯录、短信等)。而在6.0以后,我们可以直接安装,当app需要我们授予不恰当的权限的时候,我们可以予以拒绝(比如:单机的象棋对战,请求访问任何权限,我都是不同意的)。当然你也可以在设置界面对每个app的权限进行查看,以及对单个权限进行授权或者解除授权。

    新的权限机制更好的保护了用户的隐私,Google将权限分为两类,一类是Normal Permissions,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动、访问网络等;另一类是Dangerous Permission,一般是涉及到用户隐私的,需要用户进行授权,比如读取sdcard、访问通讯录等。

    • Normal Permissions如下
    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
    INSTALL_SHORTCUT
    INTERNET
    KILL_BACKGROUND_PROCESSES
    MODIFY_AUDIO_SETTINGS
    NFC
    READ_SYNC_SETTINGS
    READ_SYNC_STATS
    RECEIVE_BOOT_COMPLETED
    REORDER_TASKS
    REQUEST_INSTALL_PACKAGES
    SET_ALARM
    SET_TIME_ZONE
    SET_WALLPAPER
    SET_WALLPAPER_HINTS
    TRANSMIT_IR
    UNINSTALL_SHORTCUT
    USE_FINGERPRINT
    VIBRATE
    WAKE_LOCK
    WRITE_SYNC_SETTINGS
    • Dangerous Permissions:
    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

    可以通过adb shell pm list permissions -d -g进行查看。

    看到上面的dangerous permissions,会发现一个问题,好像危险权限都是一组一组的,恩,没错,的确是这样的,

    那么有个问题:分组对我们的权限机制有什么影响吗?

    的确是有影响的,如果app运行在Android 6.x的机器上,对于授权机制是这样的。如果你申请某个危险的权限,假设你的app早已被用户授权了同一组的某个危险权限,那么系统会立即授权,而不需要用户去点击授权。比如你的app对READ_CONTACTS已经授权了,当你的app申请WRITE_CONTACTS时,系统会直接授权通过。此外,对于申请时弹出的dialog上面的文本说明也是对整个权限组的说明,而不是单个权限(ps:这个dialog是不能进行定制的)。

    不过需要注意的是,不要对权限组过多的依赖,尽可能对每个危险权限都进行正常流程的申请,因为在后期的版本中这个权限组可能会产生变化。

    三、相关API

    好在运行时相关的API也比较简单,所以适配起来并不会非常痛苦。

    API的讲解就跟着申请权限步骤一起了:

    1. 在AndroidManifest文件中添加需要的权限。

      这个步骤和我们之前的开发并没有什么变化,试图去申请一个没有声明的权限可能会导致程序崩溃。

    2. 检查权限

              if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
                  //todo
              } else {
                  //todo
              }
    3. 这里涉及到一个API,ContextCompat.checkSelfPermission,主要用于检测某个权限是否已经被授予,方法返回值为PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。当返回DENIED就需要进行申请授权了。

    4. 申请授权

      ActivityCompat.requestPermissions(thisActivity, new String[] { Manifest.permission.READ_CONTACTS }, MY_PERMISSIONS_REQUEST_READ_CONTACTS);

      该方法是异步的,第一个参数是Context;第二个参数是需要申请的权限的字符串数组;第三个参数为requestCode,主要用于回调的时候检测。可以从方法名requestPermissions以及第二个参数看出,是支持一次性申请多个权限的,系统会通过对话框逐一询问用户是否授权。

    5. 处理权限申请回调

          @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,分别记录你两个权限的申请结果。如果申请成功,就可以做你的事情了~

    // 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.
                }
            }

    四、简单的例子

    这里写一个简单的例子,针对于运行时权限。相信大家在最开始接触Android的时候,都利用Intent实验了打电话、发短信等功能。

    我们看看直接拨打电话在Android 6.x的设备上权限需要如何处理。

    当然代码非常简单:

    package com.zhy.android160217;
    import android.Manifest;
    import android.content.Intent;
    import android.content.pm.PackageManager;
    import android.net.Uri;
    import android.os.Bundle;
    import android.support.v4.app.ActivityCompat;
    import android.support.v4.content.ContextCompat;
    import android.support.v7.app.AppCompatActivity;
    import android.view.View;
    import android.widget.Toast;
    
    public class MainActivity extends AppCompatActivity {
    
        private static final int MY_PERMISSIONS_REQUEST_CALL_PHONE = 1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        public void testCall(View view) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.CALL_PHONE }, MY_PERMISSIONS_REQUEST_CALL_PHONE);
            } else {
                callPhone();
            }
        }
    
        public void callPhone() {
            Intent intent = new Intent(Intent.ACTION_CALL);
            Uri data = Uri.parse("tel:" + "10086");
            intent.setData(data);
            startActivity(intent);
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
            if (requestCode == MY_PERMISSIONS_REQUEST_CALL_PHONE) {
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    callPhone();
                } else {
                    // Permission Denied
                    Toast.makeText(MainActivity.this, "Permission Denied", Toast.LENGTH_SHORT).show();
                }
                return;
            }
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
     

    在Android 6.x上运行是,点击testCall,即会弹出授权窗口,如何你Allow则直接拨打电话,如果Denied则Toast弹出”Permission Denied”。

    例子很简单,不过需要注意的是,对于Intent这种方式,很多情况下是不需要授权的甚至权限都不需要的,比如:你是到拨号界面而不是直接拨打电话,就不需要去申请权限;打开系统图库去选择照片;调用系统相机app去牌照等。更多请参考Consider Using an Intent 
    .

    当然,上例也说明了并非所有的通过Intent的方式都不需要申请权限。一般情况下,你是通过Intent打开另一个app,让用户通过该app去做一些事情,你只关注结果(onActivityResult),那么权限是不需要你处理的,而是由打开的app去处理。

     

  • 相关阅读:
    GenericServlet和HttpServlet有什么区别?
    什么是RMI?
    【WPF学习】第十八章 多点触控输入
    【WPF学习】第十七章 鼠标输入
    【WPF学习】第十六章 键盘输入
    【WPF学习】第十五章 WPF事件
    【WPF学习】第十四章 事件路由
    【WPF学习】第十三章 理解路由事件
    【WPF学习】第十二章 属性验证
    【WPF学习】第十一章 理解依赖项属性
  • 原文地址:https://www.cnblogs.com/chenxibobo/p/9582459.html
Copyright © 2011-2022 走看看