首页 > 代码库 > permission 新 运行时权限 6.0 总结

permission 新 运行时权限 6.0 总结

demo地址:https://github.com/baiqiantao/PermissionTest.git

关于运行时权限

在旧的权限管理系统中,权限仅仅在App安装时询问用户一次,用户同意了这些权限App才能被安装(某些深度定制系统另说),App一旦安装后就可以偷偷的做一些不为人知的事情了(同样,某些深度定制系统另说)

从Android6.0开始,App可以直接安装,App在运行时一个一个询问用户是否授予权限,系统会弹出一个对话框让用户选择是否授权某个权限给App(这个Dialog是系统定义的,不能由开发者定制,但必须由开发者手动调用),当App要求用户授予不恰当的权限的时候,用户可以拒绝(然而,很多APP可能会在请求权限失败后直接退出,所以你往往无法拒绝),用户也可以在设置页面对每个App的权限进行管理。

新的权限策略讲权限分为两类,第一类是不涉及用户隐私的,只需要在Manifest中声明即可,比如网络、蓝牙、NFC等;第二类是涉及到用户隐私信息的,需要用户授权后才可使用,比如SD卡读写、联系人、短信读写等。

不需要运行时申请的权限
此类权限都是正常保护的权限,只需要在AndroidManifest.xml中简单声明这些权限即可,安装即授权,不需要每次使用时都检查权限,而且用户不能取消以上授权,除非用户卸载App。

需要运行时申请的权限
所有危险的Android系统权限属于权限组,如果APP运行在Android 6.0 (API level 23)或者更高级别的设备中,并且targetSdkVersion>=23时,系统将会自动采用动态权限管理策略,如果你在涉及到特殊权限操作时没有申请权限而直接调用了相关代码,你的App可能就崩溃了。

综上所述你需要注意:
  • 此类权限也必须在Manifest中申明,否则申请时不提示用户,直接回调开发者权限被拒绝。
  • 同一个权限组的任何一个权限被授权了,这个权限组的其他权限也自动被授权。例如一旦WRITE_CONTACTS被授权了,App也有READ_CONTACTS和GET_ACCOUNTS了。
  • 申请某一个权限的时候系统弹出的Dialog是对整个权限组的说明,而不是单个权限。例如我申请READ_EXTERNAL_STORAGE,系统会提示"允许xxx访问设备上的照片、媒体内容和文件吗?"。

其他情景:
1、targetSdkVersion小于等于22,但是设备系统版本小于等于6.0: 
  • app使用旧的权限管理策略
  • 注册文件列出的权限将会在安装时要求用户授予权限
  • 用户可以在设置列表中编辑相关权限,这对app能否正常运行有很大影响 
2、targetSdkVersion大于等于23,但是设备系统版本小于6.0:
  • app使用旧的权限管理策略
  • 注册文件列出的权限将会在安装时要求用户授予权限

需要授权的权限组

使用adb命令可以查看这些需要授权的权限组:
  1. adb shell pm list permissions -d -g
以下为华为手机上的数据(Android 6.0.1)
(注意:不同系统可能稍有差异,特别是某些定制系统会添加一些额外的权限,但是以下标红部分是Android系统定义的最基本的危险权限,基本上定制系统也都会有)
  1. C:\Users\Administrator>adb shell pm list permissions -d -g
  2. * daemon not running. starting it now at tcp:5037 *
  3. * daemon started successfully *
  4. Dangerous Permissions:
  5. group:com.google.android.gms.permission.CAR_INFORMATION    汽车资料
  6. permission:com.google.android.gms.permission.CAR_VENDOR_EXTENSION    供应商
  7. permission:com.google.android.gms.permission.CAR_MILEAGE    里程
  8. permission:com.google.android.gms.permission.CAR_FUEL    燃料
  9. group:android.permission-group.CONTACTS 联系人
  10. permission:android.permission.WRITE_CONTACTS
  11. permission:android.permission.GET_ACCOUNTS
  12. permission:android.permission.READ_CONTACTS
  13. group:android.permission-group.PHONE 手机
  14. permission:android.permission.READ_CALL_LOG
  15. permission:android.permission.READ_PHONE_STATE
  16. permission:android.permission.CALL_PHONE
  17. permission:android.permission.WRITE_CALL_LOG
  18. permission:android.permission.USE_SIP
  19. permission:android.permission.PROCESS_OUTGOING_CALLS
  20. permission:com.android.voicemail.permission.ADD_VOICEMAIL
  21. group:com.kingroot.kinguser.permission-group.SUPERUSER    超级管理员
  22. group:android.permission-group.CALENDAR 日历
  23. permission:android.permission.READ_CALENDAR
  24. permission:android.permission.WRITE_CALENDAR
  25. group:android.permission-group.CAMERA 相机
  26. permission:android.permission.CAMERA
  27. group:android.permission-group.SENSORS 传感器
  28. permission:android.permission.BODY_SENSORS
  29. group:android.permission-group.LOCATION 位置
  30. permission:android.permission.ACCESS_FINE_LOCATION
  31. permission:com.google.android.gms.permission.CAR_SPEED
  32. permission:android.permission.ACCESS_COARSE_LOCATION
  33. group:android.permission-group.STORAGE 存储卡
  34. permission:android.permission.READ_EXTERNAL_STORAGE
  35. permission:android.permission.WRITE_EXTERNAL_STORAGE
  36. group:com.sina.weibo.permission-group
  37. permission:com.sina.weibo.permission.USER
  38. group:android.permission-group.MICROPHONE 麦克风
  39. permission:android.permission.RECORD_AUDIO
  40. group:android.permission-group.SMS 短信
  41. permission:android.permission.READ_SMS
  42. permission:android.permission.RECEIVE_WAP_PUSH
  43. permission:android.permission.RECEIVE_MMS
  44. permission:android.permission.RECEIVE_SMS
  45. permission:android.permission.SEND_SMS
  46. permission:android.permission.READ_CELL_BROADCASTS
  47. ungrouped:未分组的
  48. permission:com.huawei.pushagent.permission.RICHMEDIA_PROVIDER
  49. permission:com.huawei.contacts.permission.HW_NUMBER_MARK
  50. permission:com.huawei.motion.permission.MOTION_EX
  51. permission:com.huawei.contacts.permission.CHOOSE_SUBSCRIPTION

使用系统API完整演示

  1. public class MainActivity extends ListActivity {
  2. private static final int REQUESTCODE = 20094;
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. String[] array = {"在没有申请权限的情况下在SD卡创建文件会失败",
  6. "完整的授权过程演示",
  7. "演示PermissionsDispatcher",};
  8. setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<>(Arrays.asList(array))));
  9. }
  10. @Override
  11. protected void onListItemClick(ListView l, View v, int position, long id) {
  12. switch (position) {
  13. case 0:
  14. createFileWithoutRequestPermission(this);
  15. break;
  16. case 1:
  17. requestPermissionBeforeCreateFile();
  18. break;
  19. case 2:
  20. startActivity(new Intent(this, Activity1.class));
  21. break;
  22. }
  23. }
  24. /**
  25. * 如果将targetSdkVersion改为22或以下,可以成功创建文件
  26. * 相反,如果改为23或以上,则失败
  27. */
  28. public static void createFileWithoutRequestPermission(Context context) {
  29. String fileName = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss", Locale.getDefault()).format(new Date());
  30. File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), fileName + ".txt");
  31. boolean createFile = false;
  32. try {
  33. createFile = file.createNewFile();//没有申请权限时就在SD卡创建文件会失败(当然,如果文件已经存在,返回值也是false)
  34. } catch (IOException e) {
  35. e.printStackTrace();
  36. } finally {
  37. Toast.makeText(context, "创建文件结果:" + createFile, Toast.LENGTH_SHORT).show();
  38. }
  39. }
  40. private void requestPermissionBeforeCreateFile() {
  41. //检查权限。结果:PERMISSION_GRANTED=0,有权限;PERMISSION_DENIED=-1,没有权限
  42. int state = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
  43. Toast.makeText(this, "写SD卡权限状态:" + state, Toast.LENGTH_SHORT).show();
  44. if (state != PackageManager.PERMISSION_GRANTED) {// 没有权限,申请权限
  45. //是否应该显示请求权限的说明。
  46. //加这个提醒的好处在于:第一次申请权限时不需要麻烦用户,但如果用户拒绝过一次权限后我们再次申请时
  47. //可以提醒用户授予该权限的必要性,免得再次申请时用户勾选"不再提醒"并拒绝,导致下次申请权限直接失败
  48. //返回值的特点:第一次请求权限之前调用返回false(不应该提醒);如果用户拒绝了,则下次调用返回true(应该提醒)
  49. //如果之后再次请求权限时,用户拒绝了并选择了"不再提醒",则下次调用返回false(不应该提醒,且不弹授权对话框)
  50. boolean state2 = ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
  51. if (state2) {
  52. // 一般是通过弹一个自定义的对话框告诉用户,我们为什么需要这个权限
  53. new AlertDialog.Builder(this)
  54. .setTitle("请求读写SD卡权限").setMessage("请求SD卡权限,作用是给你保存妹子图片")
  55. .setPositiveButton("知道了", (dialog, which) -> ActivityCompat.requestPermissions(MainActivity.this,
  56. new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE))
  57. .create().show();
  58. } else {
  59. //请求用户授权几个权限,调用后系统会显示一个请求用户授权的提示对话框,开发者不能修改这个对话框
  60. ActivityCompat.requestPermissions(MainActivity.this,//api 23以后可以调用Activity的requestPermissions方法
  61. new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},//需要申请的权限数组
  62. REQUESTCODE);//请求码,会在回调onRequestPermissionsResult()时返回
  63. }
  64. } else {// 有权限了,去放肆吧。
  65. createFileWithoutRequestPermission(this);
  66. }
  67. }
  68. @Override
  69. //当用户处理完授权操作时,会回调Activity或Fragment的此方法,参数为:权限数组、授权结果数组
  70. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  71. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  72. if (grantResults.length > 0) {
  73. switch (requestCode) {
  74. case REQUESTCODE: {
  75. if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  76. createFileWithoutRequestPermission(this);
  77. } else {
  78. Toast.makeText(this, "你拒绝了权限,我们已经没法愉快的玩耍了,我将在3秒后关闭!", Toast.LENGTH_SHORT).show();
  79. new Handler().postDelayed(this::finish, 3 * 1000);
  80. }
  81. }
  82. }
  83. }
  84. }
  85. }

PermissionsDispatcher 简介

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

Simple annotation-based API to handle runtime permissions.
PermissionsDispatcher provides a simple annotation-based API to handle runtime permissions in Android Marshmallow.
This library lifts the burden that comes with writing a bunch of check statements whether a permission has been granted or not from you, in order to keep your code clean and safe.

使用简单的基于注解的API来处理运行时权限。
PermissionsDispatcher 提供了一个简单的基于注解的API处理Android M 系统运行时权限。
这个库解除了编写一系列检查语句时的负担,不管您是否已经被授予权限,以保持代码的整洁和安全。

使用过程
1、添加依赖
app
  1. compile ‘com.github.hotchemi:permissionsdispatcher:2.4.0
  2. annotationProcessor ‘com.github.hotchemi:permissionsdispatcher-processor:2.4.0
根目录
  1. allprojects {
  2. repositories {
  3. jcenter()
  4. mavenCentral()//或 maven { url ‘http://oss.jfrog.org/artifactory/oss-snapshot-local/‘ }
  5. }
  6. }
注意:要将targetSdkVersion设为23货以上
  1. targetSdkVersion 25

2、Activity、Fragment、方法添加注解
<link id="Main-File" rel="Main-File"><link rel="File-List"><style>table { } .font5 { color: black; font-size: 11.0pt; font-weight: 400; font-style: normal; text-decoration: none; font-family: 微软雅黑, sans-serif } .font6 { color: black; font-size: 11.0pt; font-weight: 700; font-style: italic; text-decoration: none; font-family: 微软雅黑, sans-serif } tr { } col { } br { } td { padding-top: 1px; padding-right: 1px; padding-left: 1px; color: black; font-size: 11.0pt; font-weight: 400; font-style: normal; text-decoration: none; font-family: 宋体; text-align: general; vertical-align: bottom; border: none; white-space: nowrap } .xl63 { text-align: center } .xl64 { color: black; font-weight: 700; font-family: 微软雅黑, sans-serif; text-align: center; vertical-align: middle; border: 1px solid windowtext; white-space: normal } .xl65 { color: black; font-size: 10.0pt; font-family: 微软雅黑, sans-serif; vertical-align: middle; border: 1px solid windowtext; white-space: normal } .xl66 { color: black; font-family: 微软雅黑, sans-serif; vertical-align: middle; border: 1px solid windowtext; white-space: normal } .xl67 { color: black; font-family: 微软雅黑, sans-serif; text-align: center; vertical-align: middle; border: 1px solid windowtext; white-space: normal } ruby { } rt { color: windowtext; font-size: 9.0pt; font-weight: 400; font-style: normal; text-decoration: none; font-family: 宋体; display: none }</style>
AnnotationRequiredDescription
@RuntimePermissions?注解在其内部需要使用运行时权限的Activity或Fragment
@NeedsPermission?注解在需要调用运行时权限的方法上,当用户给予权限时会执行该方法
@OnShowRationale 注解在用于向用户解释为什么需要调用该权限的方法上:第一次请求权限之前调用返回false;如果用户拒绝了,则下次调用返回true;如果之后再次请求权限时,用户拒绝了并选择了"不再提醒",则下次调用返回false。
@OnPermissionDenied 注解在当用户拒绝了权限请求时需要调用的方法上
@OnNeverAskAgain 注解在当用户选中了授权窗口中的不再询问复选框后并拒绝了权限请求时需要调用的方法,一般可以向用户解释为何申请此权限,并根据实际需求决定是否再次弹出权限请求对话框
只有 @RuntimePermissions 和 @NeedsPermission 是必须的,其余注解均为可选。
注意:被注解的方法不能是私有方法。

3、当使用了@RuntimePermissions 和 @NeedsPermission 之后,点击"菜单栏→BuildMake Project"重新编译整个项目。
编译完成后,会在 app\build\intermediates\classes\debug 目录下生成一个与被注解的Activity同一个包下的辅助类,名称为"被注解的Activity名称+PermissionsDispatcher.class"。

4、调用辅助类里面的方法
在需要调用权限的位置调用辅助类里面的"xxxWithCheck"静态方法,xxx是被 @NeedsPermission 注解的方法名。如:
  1. Activity1PermissionsDispatcher.createFileWithCheck(Activity1.this)
另外,还需要重写该Activity的onRequestPermissionsResult()方法,其方法内调用辅助类的onRequestPermissionsResult()方法,如:
  1. Activity1PermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);

PermissionsDispatcher 完整演示

  1. @RuntimePermissions
  2. public class Activity1 extends Activity {
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. TextView tv = new TextView(this);
  6. setContentView(tv);
  7. tv.setBackgroundColor(Color.YELLOW);
  8. tv.setOnClickListener(v -> Activity1PermissionsDispatcher.createFileWithCheck(Activity1.this));
  9. }
  10. @NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
  11. void createFile() {
  12. Toast.makeText(this, "【NeedsPermission,用户允许了该权限】", Toast.LENGTH_SHORT).show();
  13. MainActivity.createFileWithoutRequestPermission(Activity1.this);
  14. }
  15. @OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
  16. void doOnPermissionDenied() {
  17. Toast.makeText(this, "【OnPermissionDenied,用户拒绝了该权限】", Toast.LENGTH_SHORT).show();
  18. }
  19. @OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
  20. void doOnShowRationale(final PermissionRequest request) {
  21. Toast.makeText(this, "【OnShowRationale,此时应该显示请求权限的说明】", Toast.LENGTH_SHORT).show();
  22. // 一般是通过弹一个自定义的对话框告诉用户,我们为什么需要这个权限
  23. new AlertDialog.Builder(this)
  24. .setTitle("请求读写SD卡权限").setMessage("请求SD卡权限,这样我才能给你保存妹子图片哦")
  25. .setPositiveButton("我知道了", (dialog, which) -> request.proceed())
  26. .setNegativeButton("我不需要", (dialog, which) -> request.cancel())
  27. .create().show();
  28. }
  29. @OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
  30. void doOnNeverAskAgain() {
  31. Toast.makeText(this, "你拒绝了读写SD卡权限并选择了\"不再提醒\"," +
  32. "\n已经没法愉快的玩耍了,我将在3秒后关闭!", Toast.LENGTH_SHORT).show();
  33. new Handler().postDelayed(this::finish, 3 * 1000);
  34. }
  35. @Override
  36. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  37. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  38. Activity1PermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
  39. }
  40. }
生成的辅助类(注意并没有生成".java"源码)
  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by Fernflower decompiler)
  4. //
  5. package permission.bqt.com.permissiontest;
  6. import android.support.v4.app.ActivityCompat;
  7. import java.lang.ref.WeakReference;
  8. import permission.bqt.com.permissiontest.Activity1;
  9. import permissions.dispatcher.PermissionRequest;
  10. import permissions.dispatcher.PermissionUtils;
  11. final class Activity1PermissionsDispatcher {
  12. private static final int REQUEST_CREATEFILE = 0;
  13. private static final String[] PERMISSION_CREATEFILE = new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"};
  14. private Activity1PermissionsDispatcher() {
  15. }
  16. static void createFileWithCheck(Activity1 target) {
  17. if(PermissionUtils.hasSelfPermissions(target, PERMISSION_CREATEFILE)) {
  18. target.createFile();
  19. } else if(PermissionUtils.shouldShowRequestPermissionRationale(target, PERMISSION_CREATEFILE)) {
  20. target.doOnShowRationale(new Activity1PermissionsDispatcher.CreateFilePermissionRequest(target));
  21. } else {
  22. ActivityCompat.requestPermissions(target, PERMISSION_CREATEFILE, 0);
  23. }
  24. }
  25. static void onRequestPermissionsResult(Activity1 target, int requestCode, int[] grantResults) {
  26. switch(requestCode) {
  27. case 0:
  28. if(PermissionUtils.verifyPermissions(grantResults)) {
  29. target.createFile();
  30. } else if(!PermissionUtils.shouldShowRequestPermissionRationale(target, PERMISSION_CREATEFILE)) {
  31. target.doOnNeverAskAgain();
  32. } else {
  33. target.doOnPermissionDenied();
  34. }
  35. default:
  36. }
  37. }
  38. private static final class CreateFilePermissionRequest implements PermissionRequest {
  39. private final WeakReference<Activity1> weakTarget;
  40. private CreateFilePermissionRequest(Activity1 target) {
  41. this.weakTarget = new WeakReference(target);
  42. }
  43. public void proceed() {
  44. Activity1 target = (Activity1)this.weakTarget.get();
  45. if(target != null) {
  46. ActivityCompat.requestPermissions(target, Activity1PermissionsDispatcher.PERMISSION_CREATEFILE, 0);
  47. }
  48. }
  49. public void cancel() {
  50. Activity1 target = (Activity1)this.weakTarget.get();
  51. if(target != null) {
  52. target.doOnPermissionDenied();
  53. }
  54. }
  55. }
  56. }
2017-7-4

permission 新 运行时权限 6.0 总结