一. 概述

感谢郭神,自从Android6.0发布以来,在权限上做出了很大的变动,不再是之前的只要在manifest设置就可以任意获取权限,而是更加的注重用户的隐私和体验,不会再强迫用户因拒绝不该拥有的权限而导致的无法安装的事情,也不会再不征求用户授权的情况下,就可以任意的访问用户隐私,而且即使在授权之后也可以及时的更改权限。这就是6.0版本做出的更拥护和注重用户的一大体现。

1.1  Android 6.0 权限

andriod6.0系统把权限分为两个级别:
  一个是Normal Permissions,即普通权限,这类权限不会潜藏有侵害用户隐私和安全的问题,比如,访问网络的权限,访问WIFI的权限等;
  另一类是Dangerous Permissions,即危险权限,这类权限会直接的威胁到用户的安全和隐私问题,比如说访问短信,相册等权限。
  权限组:普通权限是单条的权限,而危险权限是以组展示的。所有危险的权限都属于权限组。也就是说,当你接受一个危险权限时,它所在的这个组里面的其他所有访问权限也将会被自动获取权限。当然,这类危险权限也是需要在manifest中注册的,否则动态申请会失败。
 
1.2 Android 8.0 权限
在 Android 8.0 (targetSdkVersion 26)之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。对于针对 Android 8.0 的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。例如,假设某个应用在其清单中列出 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE。应用请求 READ_EXTERNAL_STORAGE,并且用户授予了该权限。
如果该应用针对的是 API 级别 24 或更低级别,系统还会同时授予 WRITE_EXTERNAL_STORAGE,因为该权限也属于同一 STORAGE 权限组并且也在清单中注册过。
如果该应用针对的是 Android 8.0,则系统此时仅会授予 READ_EXTERNAL_STORAGE;不过,如果该应用后来又请求 WRITE_EXTERNAL_STORAGE,则系统会立即授予该权限,而不会提示用户。如果不申请,会抛异常。
  小结:
  1. 以前,申请一个子权限会自动获取权限组中其他子权限。组内其他子权限可以直接使用。
  2. 现在,申请一个子权限,组内其他子权限不会自动获取。使用组内其他子权限的时候。需要再次申请。(但是这种情况不会弹出系统的权限申请框)如果不申请。会FC。
  3. 同组权限一起申请。当我们申请权限时。申请同组的多个权限时,也只会弹出一次申请框。所以建议一起申请。
  以下原因不会弹框(不仅包括)
  • 6.0以下版本(系统自动申请)
  • 暂时发现vivo、oppo、魅族的6.0以上版本
因为这些厂商修改了6.0系统申请机制,他们修改成系统自动申请权限了。也就是说这些系统会跟以前 6.0 以下的版本一样,需要用到权限的时候系统会自动申请,就算我们主动申请也是没用的。
 
二. 动态申请权限的步骤
第一步: 在清单文件中配置所需要的权限
  1.    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  2. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  3. <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  4. <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

第二步:检测当前权限是否已授权

通过  ContextCompat.checkSelfPermission(context,permission)  方法,方法返回值为  PackageManager.PERMISSION_GRANTED  or   PackageManager.PERMISSION_DENIED 

第三步:请求权限

通过  ActivityCompat.requestPermissions(activity,permissions,requestCode) ,第二个参数是一个String数组,第三个参数是请求码,便于在  onRequestPermissionsResult()  方法中根据requestCode进行判断:

 第四步:处理权限申请的结果 

当请求权限后,界面会显示一个对话框提示用户让用户选择是否同意我们申请的权限,在用户处理完毕后,系统会回调  onRequestPermissionsResult  方法,我们需要在在activity中重写  onRequestPermissionsResult(requestCode,permissions,grantResults)  方法, grantResults 是int类型的数组,每个值为  PackageManager.PERMISSION_GRANTED  or  PackageManager.PERMISSION_DENIED  分别对应 申请的每个permissions 的每个请求(比如申请了6个权限,那这个grantResult数组的值就会有6个,按顺序分别代表每个权限是否已被用户同意)。

这里存在几种情况需要注意:

1)用户同意了我们的申请

2)用户拒绝了我们的申请

这个时候为了良好的用户体验,我们可以向用户阐明我们为什么需要这个权限,提醒用户这个权限的必要性。  ActivityCompat.shouldShowRequestPermissionRationale(activity,permission)   这个方法是在用户拒绝权限后返回true。什么意思呢,也就是说:用户第一次点击一个需要权限的地方,该方法返回false(因为用户没拒绝~),当用户拒绝掉该权限,下次点击此权限处,该方法会返回true。可在里面进行对该权限的说明,然后弹出权限让用户选择,并且对话框有don't ask again选项:

 3) 用户选择don't ask again 后

我们已经向用户阐明我们为什么需要这个权限,但是用户依然置之不理,并且还选择了don't ask again 复选框,那这个时候 ActivityCompat.shouldShowRequestPermissionRationale(activity,permission)  会一直返回false, 并且  ActivityCompat.requestPermissions  不会弹出对话框,系统直接deny,并回调  onRequestPermissionsResult  方法:

<用户拒绝时>

<用户接受时>

三. 代码演示

比如我要申请以下4种权限:

  1. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  2. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  3. <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  4. <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

为了最大程度给予用户自由,我在首界面,也就是SplashActivity去申请这些权限,我们约定只有定位权限是必须的,只要用户同意定位权限,我们就允许用户进入主界面。实际的代码如下:

  1. package com.yongdaimi.android.androidapitest;
  2.  
  3. import android.Manifest;
  4. import android.content.DialogInterface;
  5. import android.content.Intent;
  6. import android.content.pm.PackageManager;
  7. import android.net.Uri;
  8. import android.os.Build;
  9. import android.os.Bundle;
  10. import android.os.Handler;
  11. import android.util.Log;
  12.  
  13. import androidx.annotation.NonNull;
  14. import androidx.appcompat.app.AlertDialog;
  15. import androidx.appcompat.app.AppCompatActivity;
  16. import androidx.core.app.ActivityCompat;
  17. import androidx.core.content.ContextCompat;
  18.  
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.List;
  22.  
  23. public class SplashActivity extends AppCompatActivity {
  24.  
  25. private String[] mNeedGrantedPermissionArr = null;
  26.  
  27. private static final int REQUEST_CODE_GRANT_PERMISSION = 10001;
  28.  
  29. public static final String TAG = "xp.chen";
  30.  
  31. @Override
  32. protected void onCreate(Bundle savedInstanceState) {
  33. super.onCreate(savedInstanceState);
  34. setContentView(R.layout.activity_splash);
  35. initNeedGrantedPermissionArr();
  36. }
  37.  
  38. private void initNeedGrantedPermissionArr() {
  39. mNeedGrantedPermissionArr = new String[]{
  40. Manifest.permission.READ_EXTERNAL_STORAGE,
  41. Manifest.permission.WRITE_EXTERNAL_STORAGE,
  42. Manifest.permission.ACCESS_FINE_LOCATION,
  43. Manifest.permission.ACCESS_COARSE_LOCATION,
  44. };
  45. }
  46.  
  47. @Override
  48. protected void onStart() {
  49. super.onStart();
  50. checkRuntimePermissions();
  51. }
  52.  
  53. private void checkRuntimePermissions() {
  54. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  55. List<String> needApplyPermissionList = new ArrayList<String>();
  56. for (String s : mNeedGrantedPermissionArr) {
  57. if (ContextCompat.checkSelfPermission(getApplicationContext(), s) != PackageManager.PERMISSION_GRANTED) {
  58. needApplyPermissionList.add(s);
  59. }
  60. }
  61.  
  62. if (needApplyPermissionList.size() > 0) {
  63. ActivityCompat.requestPermissions(this, needApplyPermissionList.toArray(new String[0]), REQUEST_CODE_GRANT_PERMISSION);
  64. } else {
  65. skip2MainActivity();
  66. Log.i(TAG, "checkRuntimePermissions: needApplyPermissionList num is 0");
  67. }
  68. } else {
  69. skip2MainActivity();
  70. }
  71. }
  72.  
  73. @Override
  74. public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull int[] grantResults) {
  75. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  76. if (requestCode == REQUEST_CODE_GRANT_PERMISSION) {
  77.  
  78. for (String permission: permissions) {
  79. Log.i(TAG, "onRequestPermissionsResult: permission: "+permission);
  80. }
  81. for (int grantRet : grantResults) {
  82. Log.i(TAG, "onRequestPermissionsResult: grantRet: "+ grantRet);
  83. }
  84.  
  85. List<String> permissionList = Arrays.asList(permissions);
  86. if (permissionList.contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
  87. int pos = permissionList.indexOf(Manifest.permission.ACCESS_FINE_LOCATION);
  88. if (grantResults[pos] != PackageManager.PERMISSION_GRANTED) {
  89. mNeedGrantedPermissionArr = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
  90. if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
  91. AlertDialog dialog = new AlertDialog.Builder(this)
  92. .setTitle("Tips")
  93. .setMessage("I really need Location Permission to work")
  94. .setCancelable(false)
  95. .setNegativeButton("OK", new DialogInterface.OnClickListener() {
  96. @Override
  97. public void onClick(DialogInterface dialog, int which) {
  98. checkRuntimePermissions();
  99. }
  100. })
  101. .create();
  102. dialog.show();
  103. } else {
  104. AlertDialog dialog = new AlertDialog.Builder(this)
  105. .setTitle("Tips")
  106. .setMessage("The Location permission is not yet enabled, It is necessary, you can open it in the settings ui")
  107. .setCancelable(false)
  108. .setNegativeButton("Close App", new DialogInterface.OnClickListener() {
  109. @Override
  110. public void onClick(DialogInterface dialog, int which) {
  111. finish();
  112. }
  113. })
  114. .setPositiveButton("Go Open", new DialogInterface.OnClickListener() {
  115. @Override
  116. public void onClick(DialogInterface dialog, int which) {
  117. Intent intent = new Intent();
  118. intent.setAction(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
  119. intent.setData(Uri.parse("package:" + getPackageName()));
  120. startActivity(intent);
  121. }
  122. })
  123. .create();
  124. dialog.show();
  125. }
  126. } else {
  127. skip2MainActivity();
  128. Log.i(TAG, "checkRuntimePermissions: grantResults[pos] != PackageManager.PERMISSION_GRANTED");
  129. }
  130. } else {
  131. skip2MainActivity();
  132. Log.i(TAG, "checkRuntimePermissions: not necessary");
  133. }
  134.  
  135. }
  136. }
  137.  
  138. private void skip2MainActivity() {
  139. new Handler().postDelayed(new Runnable() {
  140. @Override
  141. public void run() {
  142. Intent intent = new Intent(getApplicationContext(), BLEBluetoothApiUseDemoActivity.class);
  143. startActivity(intent);
  144. finish();
  145. }
  146. }, 1000);
  147. }
  148.  
  149. }

参考链接:

1. Android 权限管理

2.Android 8.0行为变更

android: Android 权限管理小结的更多相关文章

  1. 大约Android 了解权限管理

    如Android应用程序开发人员.为android权限机制一直觉得很奇怪.为什么要这个东西权限?为什么要AndroidManifest里面写的uses-permission 这样的事情?我一直搞不清楚 ...

  2. Android 开发 权限管理

    Android 开发 权限管理 https://sspai.com/post/42779 $ adb shell pm list permissions -d -g https://zhuanlan. ...

  3. [Android Pro] android 4.4 Android原生权限管理:AppOps

    reference : http://m.blog.csdn.net/blog/langzxz/45308199 reference : http://blog.csdn.net/hyhyl1990/ ...

  4. android 自定义权限管理

    在Android6.0后有些权限就需要进行询问,虽然可以将targetSdkVersion设置成小于等于23,但是这样可能有些东西无法使用,所以要进行权限的管理. 实现逻辑:打开页面就询问权限,如果没 ...

  5. android sdcard 权限管理策略研究

    自从android4.4 以来,第三方应用程序是不能再随便的访问sdcard了,从开发者的角度而言,研究一下android系统到底是怎么样来实现这样的控制还是比较有价值的. 首先分析下现状,现在已知, ...

  6. mysql 用户及权限管理 小结

    MySQL 默认有个root用户,但是这个用户权限太大,一般只在管理数据库时候才用.如果在项目中要连接 MySQL 数据库,则建议新建一个权限较小的用户来连接. 在 MySQL 命令行模式下输入如下命 ...

  7. Android应用权限管理总结

      访问登记属性 android.permission.ACCESS_CHECKIN_PROPERTIES ,读取或写入登记check-in数据库属性表的权限 获取错略位置 android.permi ...

  8. android 手机权限管理——PermissionsDispatcher

    Android6.0 之后某些权限需要动态申请,相比于之前版本复杂了许多.不过已经有大神给我们写好了框架(PermissionsDispatcher),我们用起来还是很方便. 1.添加引用 根据 gr ...

  9. 第一篇、Android Supersu 权限管理定制,隐藏过滤权限,指定APP最高权限

    近期有个需求,在预装ROM的时候,须要权限,可是又不同意全部的应用都有权限,仅仅同意自己的应用有最高的权限(当然没有系统签名情况下). 所以.编译了CM 提取了supersu进行了二次定制,让他进行权 ...

随机推荐

  1. 从零开始配置一个简单的webpack打包

    一.基础配置 1.创建一个名为demo-webpack的文件夹(名称随意) 2.初始化一个package.json文件 //在cmd窗口中使用以下命令快速创建 npm init -y 3.安装webp ...

  2. XML文件解析之DOM4J解析

    1.DOM4J介绍 dom4j的官网是http://www.dom4j.org/dom4j-1.6.1/,最新的版本是1.6.1,根据官网介绍可知.dom4j是一个易用的.开源的库,应用于Java平台 ...

  3. SpringDataJPA第三天讲义

    第1章     Specifications动态查询 有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecif ...

  4. 去“BAT”这样面试,拿到offer的几率是80%

    一.概述 面试,难还是不难?取决于面试者的底蕴(气场+技能).心态和认知及沟通技巧.面试其实可以理解为一场聊天和谈判,在这过程中有心理.思想上的碰撞和博弈.其实你只需要搞清楚一个逻辑:“面试官为什么会 ...

  5. 怎么读取properties文件和ini文件?

    一.读取properties文件: properties中的内容: server.ip = 127.0.0.1 server.port = 22 //原生java即可读取public static v ...

  6. bash基础——终端

    前言 自学Linux的时候,我们用的显示器+键盘 是物理终端.Linux开机后,会在物理终端(显示器)之上,以软件的方式虚拟出多个终端,CentOS是6个.Ctrl+Alt+F1~6切换 默认情况下, ...

  7. OSI七层与TCP/IP五层

    OSI七层与TCP/IP五层网络架构详解 OSI和TCP/IP是很基础但又非常重要的网络基础知识,理解得透彻对运维工程师来说非常有帮助.今天偶又复习了一下: (1)OSI七层模型 OSI中的层 功能 ...

  8. HRNet网络结构

    最近正在阅读CVPR2019的论文Deep High-Resolution Representation Learning for Human Pose Estimation. 无奈看论文中的Netw ...

  9. 根据IP获取经纬度 js

    <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> &l ...

  10. nginx中ngx_http_ssl_module模块

    此模块为HTTPS提供必要的⽀支持worker_processes auto;http {...server {listen 443 ssl;keepalive_timeout 70;ssl_prot ...