Android M runtime permissions(An

作者: 5ef947bf2bd1 | 来源:发表于2018-10-28 11:23 被阅读12次

官方介绍:
https://developer.android.com/training/permissions/requesting?hl=zh-cn

Google Sample地址:
https://github.com/googlesamples/android-RuntimePermissionsBasic
https://github.com/googlesamples/android-RuntimePermissions

Android M引入了“运行时权限”,targetSdkVersion 23及以上的应用不但需要在AndroidManifest.xml中声明它们需要的权限,还需要在运行时动态申请这些已声明过的危险权限;targetSdkVersion低于23的应用,在安装时用户已经同意了所有权限,并且在运行时所需权限全部可用。如果不同意这些权限,用户将无法安装此应用。

处理过程包括:

  1. 在应用的清单文件中添加使用具体功能需要的相关权限;

  2. 每次在使用需要危险权限的功能之前,检查应用是否具有相关权限(用户有可能在授权之后手动撤销权限);

  • 如果应用不具有相关权限,申请相关权限;
  • 如果应用已拥有相关权限,开始使用具体功能;
  1. 申请权限之前,帮助用户了解应用为什么需要某项权限;
  • 如果用户曾经拒绝授权,弹窗引导用户进行动态申请权限;
  • 如果用户未授权,申请相关权限;
  • 如果用户曾经拒绝授权且勾选“不再提示”选项(“不再提示”选项仅在用户已拒绝过1次授权此权限后应用再次申请用户授权此权限时显示)而导致无法显示弹窗,提示相关权限申请失败,无法使用具体功能,并告知用户开启相关权限的操作方法(设置->应用->选择应用->权限);
  1. 监听申请权限的结果,如果用户同意应用申请的权限后,开始使用具体功能;如果用户拒绝应用申请的权限,提示相关权限申请失败,无法使用具体功能。
    注:当用户授权应用同一组危险权限中的某一项权限后(如果用户不手动撤销此权限),系统将自动授权应用该组的其他权限。后期版本权限组可能会发生变化。

场景:使用相机预览

  1. 在AndroidMinifest的根节点下声明相机权限
<uses-permissionandroid:name="android.permission.CAMERA"/>
  1. 通过点击按钮,触发“开启相机预览”事件

  2. 进行权限验证

if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)  != PackageManager.PERMISSION_GRANTED) {
   requestCameraPermission();
} else {
   startCamera();// 假装有相机
}
  1. 申请权限
private void requestCameraPermission() {
    // Permission has not been granted and must be requested.
    if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
        //应用之前请求过此权限但用户拒绝了请求
        Snackbar.make(mLayout, R.string.camera_access_required, Snackbar.LENGTH_INDEFINITE)
                .setAction(R.string.ok, new View.OnClickListener() {
            @Override
            public void onClick(View view) {
              // Request the permission
              ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CAMERA);
            }
        }).show();
    } else {
        // 应用未请求过此权限或者用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了“不再提示”选项
        Snackbar.make(mLayout, R.string.camera_unavailable, Snackbar.LENGTH_SHORT).show();
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CAMERA);
    }
}
  1. 监听申请权限结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == PERMISSION_REQUEST_CAMERA) {
        // Request for camera permission.
        if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission has been granted. Start camera preview Activity.
            Snackbar.make(mLayout, R.string.camera_permission_granted, Snackbar.LENGTH_SHORT).show();
            startCamera();
        } else {
            // Permission request was denied.
            Snackbar.make(mLayout, R.string.camera_permission_denied, Snackbar.LENGTH_SHORT).show();
        }
    }
}
  1. Fragment中验证授权和申请授权操作
  • 使用ActivityCompat.requestPermissions(getActivity(), String[], int),会执行Activity的onRequestPermissionsResult()方法;
  • 直接使用 Fragment.requestPermissions(String[], int),会执行Fragment的onRequestPermissionsResult()方法;
  1. 嵌套Fragment中申请权限操作
  • 在子Fragment中使用getParentFragment().requestPermissions()申请权限;
  • 在父Fragment中将onRequestPermissionsResutl()回调透传到子Fragmnet
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    List<Fragment> fragments = getChildFragmentManager().getFragments();
    if (fragments != null) {
        for (Fragmentfragment : fragments) {
            if (fragment != null) {
                fragment.onRequestPermissionsResult(requestCode, permissions,grantResults);
            }
        }
    }
}

应用targetSdkVersion < 23的兼容性处理:

场景:

targetSdkVersion < 23的App安装在Android 6.0及以上设备时,用户已同意App中声明的所有权限,但是在安装完成后,用户手动在设置中关闭了APP的权限。此时调用ContextCompat.checkSelfPermission()检查APP是否拥有指定权限,返回值仍为PackageManager.PERMISSION_GRANTED,如果使用需要此项危险权限的功能,应用将奔溃。

解决:

  • 方案一:升级targetSdkVersion到23或23以上版本;
  • 方案二:
    1. 获取设备Android版本,如果(Build,VERSION.SDK_INT >= Build.VERSION_CODES.M)条件成立,表示应用运行在Android 6.0及更高版本的设备上;
    2. 获取应用targetSdkVersion
try {
    PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
    int targetSdkVersion = info.applicationInfo.targetSdkVersion;
} catch (PackageManager.NameNotFoundExceptione) {
    e.printStackTrace();
}

a) 如果targetSdkVersion 低于23,使用PermissionChecker兼容库检查APP拥有的权限;
b) 如果targetSdkVersion 不低于23,使用之前的方式检查APP是否拥有指定权限;

  1. 调用PermissionChecker.checkSelfPermission()检查APP是否拥有指定权限,返回值:

a) PERMISSION_GRANTED:已授权
b) PERMISSION_DENIED:用户拒绝授权
c) PERMISSION_DENIED_APP_OP:用户拒绝授权且targetSdkVersion小于23

runtime permissions处理封装库:

鸿洋MPermissions:https://github.com/hongyangAndroid/MPermissions

  1. 引入
  • project’s build.gradle
buildscript {
    dependencies {
        classpath'com.neenbedankt.gradle.plugins:android-apt:1.4'
    }
}
  • module’s build.gradle
apply plugin: 'com.neenbedankt.android-apt'

dependencies {
    apt 'com.zhy:mpermission-compiler:1.0.0'
    compile 'com.zhy:mpermission-api:1.0.0'
}
  1. 定义权限请求码
    final int REQUECT_CODE_SDCARD = 1;
  2. 判断是否需要弹出解释
if(!MPermissions.shouldShowRequestPermissionRationale(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE, REQUECT_CODE_SDCARD)) {
    MPermissions.requestPermissions(MainActivity.this, REQUECT_CODE_SDCARD, Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
  1. 申请读写SD卡权限
MPermissions.requestPermissions(MainActivity.this, REQUECT_CODE_SDCARD, Manifest.permission.WRITE_EXTERNAL_STORAGE);
  1. 处理申请权限回调
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    MPermissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

@PermissionGrant(REQUECT_CODE_SDCARD)
public voidrequestSdcardSuccess() {
    Toast.makeText(this, "GRANT ACCESS SDCARD!", Toast.LENGTH_SHORT).show();
}

@PermissionDenied(REQUECT_CODE_SDCARD)
public void requestSdcardFailed() {
    Toast.makeText(this, "DENY ACCESS SDCARD!",Toast.LENGTH_SHORT).show();
}
  1. 混淆
-dontwarn com.zhy.m.**
-keep class com.zhy.m.** {*;}
-keep interface com.zhy.m.** { *; }
-keep class **$$PermissionProxy { *; }

PermissionsDispatcher:https://github.com/hotchemi/PermissionsDispatcher

  1. 引入:
  • module’s build.gradle
dependencies {
   implementation 'com.github.hotchemi:permissionsdispatcher:${latest.version}'
   annotationProcessor 'com.github,hotchemi:permissionsdispatcher-processor:${latest.version}'
}
  • 安装PermissionsDispatcher插件
    File->Settings->Plugins,搜索PermissionsDispatcher,点击install安装,重启AS。


    image.png
  • 生成申请权限操作代码
    ⅰ. Code->Generate->GenerateRuntime Permissions


    image.png

    ⅱ. 勾选要申请的权限以及要覆盖的方法,点击Generate生成代码

@NeedsPermission 用户同意授权应用相关权限后的回调;
@OnShowRationale 用户已拒绝过1次授权此权限后,应用再次请求用户授权此权限时的回调。在此方法中解释应用为什么需要此权限。之后调用PermissionRequest的proceed()方法继续申请权限;
@OnPermissionDenied 用户拒绝授权应用相关权限后的回调;
@OnNeverAskAgain 当用户拒绝授权并勾选“不再提示”后的回调

  • 申请权限
xxActivityPermissionsDispatcher.**WithPermissionCheck(this);//  xx为自动生成的辅助类,**为被@NeedsPermission注解的方法名

常规权限列表:

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

危险权限列表:

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

导入Google Sample—RuntimePermissions时,build失败解决方案:

  • 原因:settings.gradle中引入的模块为Application,但是Application缺少build.gradle。
  • 解决:File->Open->RuntimePermissions项目->kotlinApp,build后(AS可能需要下载kotlin插件,允许后等待下载完成)可以正常运行项目。

这里是《我的Android世界》系列目录

相关文章

网友评论

    本文标题:Android M runtime permissions(An

    本文链接:https://www.haomeiwen.com/subject/xljptqtx.html