关于动态权限

2,791 阅读8分钟

首发于我的公众号

关于动态权限

1、在给app升级动态权限之前需要注意搞清楚一下几个概念

1.1、版本号对应的api等级

查阅官网可以看出

1.2、minSdkVersion、targetSdkVersion和 compileSdkVersion

这几个属性通常在gradle defaultConfig中去配置,作用如下

(1)minSdkVersion 指明应用程序运行所需的最小API level,如果系统的API level低于minSdkVersion设定的值,那么android系统会阻止用户安装这个应用,如果不指明的话,默认是1,如果指明这个属性,并在项目中使用了改与这个API level的API的话,那么将会在编译时报错。 可见该属性不仅在程序安装时起作用,而且会在项目构建时起作用

(2)targetSdkVersion

指明应用程序目标API level的一个整数,如果不设置就默认和minSdkVersion相同。这个属性通知系统,已经针对这个目标版本测试过程序,系统不必使用兼容模式来让你的应用程序向前兼容这个目标版本。 如果targetSdkVersion为19(对应为Android4.4),应用运行时,最高只能使用API 19的新特性。即使代码中使用了API 23的新特性,实际运行时,也不会使用该新特性; 可见targetSdkVersion这个属性是在程序运行时期起作用的,系统根据这个属性决定要不要以兼容模式运行这个程序 例如,Android6.0系统增加了动态权限机制,如果为了追时髦,盲目把你的targetSDKVersion设置为23(6.0),那么在需要使用权限的地方将会出现异常。为此,在你做好动态权限申请之前,为保障APP正常运行,你需要将目标版本设置低于23。

(3)complieSdkVersion

compileSdkVersion仅仅是告诉Gradle使用哪个版本的SDK编译应用,不会被包含到apk中,完全不影响应用的运行结果;虽然没有影响但是

即 :compileSDKVersion是和编译器打交道的,而minSDKVersion和targetSDKVersion是和系统打交道的。

2、如何升级app的动态权限

首先需要了解动态权限这个概念

2.1 动态权限

这个是在android 6.0提出的,从该版本开始,用户实在运行时而不是应用安装时授予或者撤销应用权限,对应API23,这个版本最重要的变化就是权限动态管理,任何未被授予权限的逻辑都可能引起后续运行的崩溃,可以通过adb 工具从命令行获取权限

按组列出权限和状态:

$ adb shell pm list permissions -d -g

授予或撤销一项或多项权限:

$ adb shell pm [grant|revoke] ...

6.0之前的权限在AndroidMenifest声明之后即可获取所有权限,这样会造成用户会默默忍受一些不必要的权限,特别是通讯录,位置短信之类的比较敏感的权限,在6.0之后我们只有在需要权限的时候才会向用户请求,用户可以选择拒绝,新的机制较好的保护的用户的隐私。 谷歌将权限分为两大类 Normal Permissions 这类权限一般不涉及用户隐私是不需要用户进行授权的,比如网络访问、蓝牙(详细权限见附录) Dangenerous Permission 这类涉及到用户隐私,需要用户进行授权,比如读取sdcard、开启照相机、访问通讯录等,其中危险权限是分组的,用户之前如果已经选择通过该组某个危险权限,主要有以下几组(详细权限见附录)

  1. 身体传感器
  2. 日历 摄像头
  3. 通讯录
  4. 地理位置
  5. 麦克风
  6. 电话
  7. 短信
  8. 存储空间

2.2 权限相关API

为了方便开发者实现权限管理,谷歌给出下面4个API

一般权限检查需要以下三个步骤配合

1、检查权限是否授予

//Activity.java
> public int checkSelfPermission(permission) 

这个是在ContextCompat类中用来判断是否app已经获取到某一个权限的使用权。如果返回android.content.pm.PackageManager#PERMISSION_GRANTED,则说明app被授予权限,如果返回android.content.pm.PackageManager#PERMISSION_DENIED 则说明权限被禁止授予

2、申请权限

//Activity.java
public final void requestPermissions( new String[permission1,permission2,...], requestCode)

这个是在ActivityCompat类以及FragmentCompat类中用来申请权限的方法

3、权限回调 用户在系统弹窗里面选择以后结果会通过Activity的onRequestPermissionsResult 方法回调

public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
    //继续执行逻辑或者提示权限获取失败
}

这些方法可以封装在BaseActvity中

4、是否显示权限对话框 这个是作为辅助类

public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity, @NonNull String permission) 这个是在ActivityCompat类以及FragmentCompat类中用来判断是否显示权限询问对话框。在权限申请的过程中,如果用户选择允许权限使用或者在拒绝的同时又勾选了不再询问对话框,这2种情况下该api就会返回false,表示不再弹出权限询问对话框,其中授权弹窗是不支持自定义的

这个辅助函数十分重要,当动态权限申请时候如果用户如果用户勾选了“不再询问”,那么在执行requestPermissions( )后,onRequestPermissionsResult( )会永远返回PERMISSION_DENIED,这样应用原本的操作将永远无法执行,这是我们不希望看到的

谷歌还是给我们留了一手,就是 shouldShowRequestPermissionRationale,如果返回true,应用应该弹出dialog说明申请权限的缘由比如下面这种

其中第一次申请权限时候该函数默认返回false,无需告知用户申请该权限的理由;当第一次申请被拒绝,再次申请时该函数会返回true,并且弹出的系统dialog上会有一个选择“不再询问”,要是用户勾选了该选项,则以后该函数一直会返回false。

2.3 如何适配

如果项目的targetSdkVersion < 23 运行在android 6.0及以上的系统上,会默认给与AndroidManifest.xml中申请的权限,是不是这样就万事大吉了呢?显然是想多了,如果用户在应用的权限页面手动回收权限,将会导致应用crash

虽然系统提醒了用户,但是还是要取决用户心情,因此稳妥的适配动态还是有必要的事情。

首先targetSdkVersion小于23的应用默认授予了所申请的所有权限,所以如果你以前的APP设置的targetSdkVersion低于23,也能正常使用。等于或者大于23,则必须 request permission,否则会崩溃闪退。 下面将大致阐述一下升级动态权限步骤

(1)声明目标版本sdk 修改build.gradle中targetSdkVersion为23以上

(2)检查权限申请地方并request权限 在我们需要使用到权限的地方,检查是否已经拥有权限,比如读写外置SD卡权限,我们在写入之前检车是否有权限,没有权限则申请权限

private void requestContactPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS)
                != PackageManager.PERMISSION_GRANTED) {
            //申请 WRITE_CONTACTS 权限
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_CONTACTS},
                    REQUEST_CODE_WRITE_CONTACTS);
        }
    } 

(3)响应用户权限回调

用户可以选择allow或者deny,可能deny。回调onRequestPermissionsResult方法, 该方法类似于onActivityResult。如果是fragment,最好是使用父fragment,但不是使用ActivityCompat。建议使用getParentFragment().requestPermissions方法。

(4)根据requestCode和grandResult(授权结果)做相应处理

private void handleGrantResults(int requestCode, int[] grantResults) {
      if (requestCode == WRITE_EXTERNAL_STORAGE_REQUEST_CODE) {
          if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
              // Permission Granted 获得权限后执行xxx
          } else {
              // Permission Denied 拒绝后xx的操作。
          }
      }
  }

3、后话

3.1、权限申请如果用户拒绝怎么处理

如果用户拒绝了核心权限一次,下次再次申请会出现不再提醒的选项,要是选择不再提醒,核心权限不给获取一些功能就无法使用,你可以选择直接退出应用,或者可以优雅点,我们自己检测一下是否拒绝了两次或者以上,自己给个提示向用户做一个交互

private void handleContactPermission() {
        if (Integer.parseInt(Build.VERSION.SDK)>=23) {
            int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
            if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
                if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {
                    showMessageOKCancel("You need to allow access to Contacts",
                            new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    requestContactPermission();//确定后申请权限。
                                }
                            });
                    return;
                }
                requestContactPermission();//没有权限的话,申请。
            }
        }

或者记录用户勾选了不再询问 Google提供了一个非常好的思路,详见EasyPermissions . EasyPermissions并没有存储上一次shouldShowRequestPermissionRationale( )的返回值,而是在申请权限被拒后调用shouldShowRequestPermissionRationale( )方法,如果此时返回false则说明用户勾选了“不再询问”。

两种方式都可以作为参考方式。

3.2 取消支持Apache http 客户端

Android 6.0 版移除了对 Apache HTTP 客户端的支持。如果您的应用使用该客户端,并以 Android 2.3(API 级别 9)或更高版本为目标平台,请改用HttpURLConnection 类。此 API 效率更高,因为它可以通过透明压缩和响应缓存减少网络使用,并可最大限度降低耗电量。要继续使用 Apache HTTP API,您必须先在 build.gradle 文件中声明以下编译时依赖项: android { useLibrary 'org.apache.http.legacy' }

3.3 升级到7.0 api 24及以上

对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常,常见的是访问升级下载到内部sd卡,访问手机相册等。 网上有很多解决方案

附录

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

Dangenerous Permission

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

###参考链接

公众号小.jpg