android 6.0权限全面详细分析和解决方案

原文:

http://www.2cto.com/kf/201512/455888.html

http://blog.csdn.net/yangqingqo/article/details/48371123

http://inthecheesefactory.com/blog/things-you-need-to-know-about-Android-m-permission-developer-edition/en

一、Marshmallow版本权限简介

android的权限系统一直是首要的安全概念,因为这些权限只在安装的时候被询问一次。一旦安装了,app可以在用户毫不知晓的情况下访问权限内的所有东西,而且一般用户安装的时候很少会去仔细看权限列表,更不会去深入了解这些权限可能带来的相关危害。所以在android 6.0 Marshmallow版本之后,系统不会在软件安装的时候就赋予该app所有其申请的权限,对于一些危险级别的权限,app需要在运行时一个一个询问用户授予权限。

二、旧版本app兼容问题

  那么问题来了,是不是所有以前发布的app都会出现问题呢?答案是不会,只有那些targetSdkVersion 设置为23和23以上的应用才会出现异常,在使用危险权限的时候系统必须要获得用户的同意才能使用,要不然应用就会崩溃,出现类似
java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider

的崩溃日志。所以targetSdkVersion如果没有设置为23版本或者以上,系统还是会使用旧规则:在安装的时候赋予该app所申请的所有权限。所以app当然可以和以前一样正常使用了,但是还有一点需要注意的是6.0的系统里面,用户可以手动将该app的权限关闭,如下图

  
  那么问题又来了,如果以前的老应用申请的权限被用户手动关闭了怎么办,应用会崩溃么?我们来试一试
  这里写图片描述
  好吧,可以庆幸了一下了,不会抛出异常,不会崩溃,只不过调用那些被用户禁止权限的api接口返回值都为null或者0,所以我们只需要做一下判空操作就可以了,不判空当然还是会崩溃的喽。

三、普通权限和危险权限列表

  现在对于新版本的权限变更应该有了基本的认识,那么,是不是所有权限都需要去进行特殊处理呢?当然不是,只有那些危险级别的权限才需要。

PROTECTION_NORMAL类权限

当用户安装或更新应用时,系统将授予应用所请求的属于 PROTECTION_NORMAL 的所有权限(安装时授权的一类基本权限)。这类权限包括:

android.permission.ACCESS LOCATIONEXTRA_COMMANDS 
android.permission.ACCESS NETWORKSTATE 
android.permission.ACCESS NOTIFICATIONPOLICY 
android.permission.ACCESS WIFISTATE 
android.permission.ACCESS WIMAXSTATE 
android.permission.BLUETOOTH 
android.permission.BLUETOOTH_ADMIN 
android.permission.BROADCAST_STICKY 
android.permission.CHANGE NETWORKSTATE 
android.permission.CHANGE WIFIMULTICAST_STATE 
android.permission.CHANGE WIFISTATE 
android.permission.CHANGE WIMAXSTATE 
android.permission.DISABLE_KEYGUARD 
android.permission.EXPAND STATUSBAR 
android.permission.FLASHLIGHT 
android.permission.GET_ACCOUNTS 
android.permission.GET PACKAGESIZE 
android.permission.INTERNET 
android.permission.KILL BACKGROUNDPROCESSES 
android.permission.MODIFY AUDIOSETTINGS 
android.permission.NFC 
android.permission.READ SYNCSETTINGS 
android.permission.READ SYNCSTATS 
android.permission.RECEIVE BOOTCOMPLETED 
android.permission.REORDER_TASKS 
android.permission.REQUEST INSTALLPACKAGES 
android.permission.SET TIMEZONE 
android.permission.SET_WALLPAPER 
android.permission.SET WALLPAPERHINTS 
android.permission.SUBSCRIBED FEEDSREAD 
android.permission.TRANSMIT_IR 
android.permission.USE_FINGERPRINT 
android.permission.VIBRATE 
android.permission.WAKE_LOCK 
android.permission.WRITE SYNCSETTINGS 
com.android.alarm.permission.SET_ALARM 
com.android.launcher.permission.INSTALL_SHORTCUT 
com.android.launcher.permission.UNINSTALL_SHORTCUT

这类权限只需要在AndroidManifest.xml中简单声明这些权限就好,安装时就授权。不需要每次使用时都检查权限,而且用户不能取消以上授权。

危险权限

Permission Group Permissions
android.permission-group.CALENDAR
  • android.permission.READ_CALENDAR
  • android.permission.WRITE_CALENDAR
android.permission-group.CAMERA
  • android.permission.CAMERA
android.permission-group.CONTACTS
  • android.permission.READ_CONTACTS
  • android.permission.WRITE_CONTACTS
  • android.permission.GET_ACCOUNTS
android.permission-group.LOCATION
  • android.permission.ACCESS_FINE_LOCATION
  • android.permission.ACCESS_COARSE_LOCATION
android.permission-group.MICROPHONE
  • android.permission.RECORD_AUDIO
android.permission-group.PHONE
  • android.permission.READ_PHONE_STATE
  • android.permission.CALL_PHONE
  • android.permission.READ_CALL_LOG
  • android.permission.WRITE_CALL_LOG
  • com.android.voicemail.permission.ADD_VOICEMAIL
  • android.permission.USE_SIP
  • android.permission.PROCESS_OUTGOING_CALLS
android.permission-group.SENSORS
  • android.permission.BODY_SENSORS
android.permission-group.SMS
  • android.permission.SEND_SMS
  • android.permission.RECEIVE_SMS
  • android.permission.READ_SMS
  • android.permission.RECEIVE_WAP_PUSH
  • android.permission.RECEIVE_MMS
  • android.permission.READ_CELL_BROADCASTS
android.permission-group.STORAGE
  • android.permission.READ_EXTERNAL_STORAGE
  • android.permission.WRITE_EXTERNAL_STORAGE

  android开发者官网也有相关描述:
  http://developer.android.com/training/permissions/requesting.html
  http://developer.android.com/guide/topics/security/permissions.html

  所以仔细去看看自己的app,对照列表,如果有需要申请其中的一个权限,就需要进行特殊操作。还有一个比较人性的地方就是如果同一组的任何一个权限被授权了,其他权限也自动被授权。例如,一旦WRITE_EXTERNAL_STORAGE被授权了,app也有READ_EXTERNAL_STORAGE权限了。

四、支持Marshmallow新版本权限机制

关于权限控制主要使用到

PermissionChecker类的checkSelfPermission();

ActivityCompat类的

public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
            @NonNull String permission)

Fragment类的

public boolean shouldShowRequestPermissionRationale(@NonNull String permission)

ActivityCompat类的

public static void requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final int requestCode)

Fragment类的

public final void requestPermissions(@NonNull String[] permissions, int requestCode)

终于要开始支持android 6.0版本了,最先一步当然就是修改build.gradle文件中的tragetSdkVersion和compileSdkVersion成23版本,同时使用compile ‘com.android.support:appcompat-v7:23.1.1’最新v7包。

android {
    compileSdkVersion 23
    ...
 
    defaultConfig {
        ...
        targetSdkVersion 23
        ...
    }
}
...
dependencies {
...
compile ‘com.android.support:appcompat-v7:23.1.1‘
  修改完后,感兴趣的朋友可以直接打包在手机上测试一下,看看是不是会出现类似于上面我说的那些崩溃日志。
  接着下一步当然就是要修改代码了,最原始代码,无任何处理:

private void startGetImageThread(){
....
    Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    ContentResolver contentResolver = getContentResolver();
    //获取jpeg和png格式的文件,并且按照时间进行倒序
    Cursor cursor = contentResolver.query(uri, null, MediaStore.Images.Media.MIME_TYPE + "=\"image/jpeg\" or " +
    MediaStore.Images.Media.MIME_TYPE + "=\"image/png\"", null, MediaStore.Images.Media.DATE_MODIFIED+" desc");
    ....
}
  这段代码需要访问外部存储(相册图片),属于危险级别的权限,直接使用会造成应用崩溃,所以在这段代码执行之前我们需要进行特殊处理:

int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);

if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {

Activity activty=this;

ActivityCompat.requestPermissions(activty,new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
                CODE_FOR_WRITE_PERMISSION);
    return;
}
  写完这段代码之后,就会出现如下系统dialog:
  
  紧接着就需要去处理DENY和ALLOW的回调了,重写 Activity activity的ActivityCompat.OnRequestPermissionsResultCallback函数:

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == CODE_FOR_WRITE_PERMISSION){
        if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            &&grantResults[0] == PackageManager.PERMISSION_GRANTED){
            //用户同意使用write
            startGetImageThread();
        }else{
            //用户不同意,自行处理即可
            finish();
        }
    }
}
  好了,这样就算是简单初步适配完成了。

五、处理不再提醒

  如果用户拒绝某授权。下一次弹框,用户会有一个“不再提醒”的选项的来防止app以后继续请求授权。

  

  如果这个选项在拒绝授权前被用户勾选了。下次为这个权限请求requestPermissions时,对话框就不弹出来了,系统会直接回调onRequestPermissionsResult函数,回调结果为最后一次用户的选择。所以为了应对这种情况,系统提供了一个shouldShowRequestPermissionRationale()函数,这个函数的作用是帮助开发者找到需要向用户额外解释权限的情况,这个函数:
应用安装后第一次访问,直接返回false;第一次请求权限时,用户拒绝了,下一次shouldShowRequestPermissionRationale()返回 true,这时候可以显示一些为什么需要这个权限的说明;第二次请求权限时,用户拒绝了,并选择了“不再提醒”的选项时:shouldShowRequestPermissionRationale()返回 false;设备的系统设置中禁止当前应用获取这个权限的授权,shouldShowRequestPermissionRationale()返回false;  注意:第二次请求权限时,才会有“不再提醒”的选项,如果用户一直拒绝,并没有选择“不再提醒”的选项,下次请求权限时,会继续有“不再提醒”的选项,并且shouldShowRequestPermissionRationale()也会一直返回true。
  所以利用这个函数我们可以进行相应的优化,针对shouldShowRequestPermissionRationale函数返回false的处理有两种方案。第一种方案:如果应用是第一次请求该权限,则直接调用requestPermissions函数去请求权限;如果不是则代表用户勾选了’不再提醒’,弹出dialog,告诉用户为什么你需要该权限,让用户自己手动开启该权限。链接:http://stackoverflow.com/questions/32347532/android-m-permissions-confused-on-the-usage-of-shouldshowrequestpermissionrati 。第二种方案:在onRequestPermissionsResult函数中进行检测,如果返回PERMISSION_DENIED,则去调用shouldShowRequestPermissionRationale函数,如果返回false代表用户已经禁止该权限(上面的3和4两种情况),弹出dialog告诉用户你需要该权限的理由,让用户手动打开。链接:http://stackoverflow.com/questions/30719047/android-m-check-runtime-permission-how-to-determine-if-the-user-checked-nev  处理方法已经有了,修改一下代码,我这里就以第二种方案来处理了:
 
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == CODE_FOR_WRITE_PERMISSION){
        if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            &&grantResults[0] == PackageManager.PERMISSION_GRANTED){
            //用户同意使用write
            startGetImageThread();
        }else{
            //用户不同意,向用户展示该权限作用
            if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                AlertDialog dialog = new AlertDialog.Builder(this)
                        .setMessage("该相册需要赋予访问存储的权限,不开启将无法正常工作!")
                        .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                finish();
                            }
                        })
                        .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                finish();
                            }
                        }).create();
                dialog.show();
                return;
            }
            finish();
        }
    }
}
  当勾选不再提醒,并且拒绝之后,弹出dialog,提醒用户该权限的重要性:
  
  

六、使用兼容库

  以上的代码在6.0版本上使用没有问题,但是在之前就有问题了,最简单粗暴的解决方法可能就是利用Build.VERSION.SDK_INT >= 23这个判断语句来判断了,方便的是SDK 23的v4包加入了专门类进行相关的处理:

ContextCompat.checkSelfPermission()被授权函数返回PERMISSION_GRANTED,否则返回PERMISSION_DENIED ,在所有版本都是如此。ActivityCompat.requestPermissions()这个方法在6.0之前版本调用,OnRequestPermissionsResultCallback 直接被调用,带着正确的 PERMISSION_GRANTED或者PERMISSION_DENIED。ActivityCompat.shouldShowRequestPermissionRationale()在6.0之前版本调用,永远返回false。  用v4包的这三方法,完美兼容所有版本!下面是代码:
//使用兼容库就无需判断系统版本
int hasWriteContactsPermission = ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (hasWriteContactsPermission == PackageManager.PERMISSION_GRANTED) {
    startGetImageThread();
}
//需要弹出dialog让用户手动赋予权限
else{
    ActivityCompat.requestPermissions(PickOrTakeImageActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE_FOR_WRITE_PERMISSION);

}

  onRequestPermissionsResult函数不变。后两个方法,我们也可以在Fragment中使用,用v13兼容包:FragmentCompat.requestPermissions() and FragmentCompat.shouldShowRequestPermissionRationale()和activity效果一样。

七、一次请求多个权限

  当然了有时候需要多个权限,可以用上面方法一次请求多个权限。当然最重要的是不要忘了为每个权限检查“不再提醒”的设置。
List<string> permissionsNeeded = new ArrayList<string>();
permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
permissionsNeeded.add(Manifest.permission.READ_CONTACTS);
permissionsNeeded.add(Manifest.permission.WRITE_CONTACTS);
requestPermissions(permissionsNeeded.toArray(new String[permissionsList.size()]), CODE_FOR_MULTIPLE_PERMISSION);</string></string>
  最后在onRequestPermissionsResult函数中一个个处理返回结果即可。

八、第三方库简化代码

  当然早就有第三方库来帮忙做这些事情了:
  Github上的开源项目 PermissionHelper和hotchemi’s PermissionsDispatcher

九、APP处于运行状态下,被撤销权限

  如果APP正在运行中,用户进入设置-应用程序页面去手动撤销该APP权限,会出现什么情况呢?哈哈,系统又会接着弹出权限请求对话框,挺好挺好:
  
  这样就没有问题了吧O(∩_∩)O~
  上面的测试环境为genymotion6.0模拟器,有朋友跟我反映在6.0nexus 6p真机上会直接退出应用,所以这个应该还和测试环境有关。

使用兼容库support-v4中的方法

ContextCompat.checkSelfPermission()

ActivityCompat.requestPermissions()

ActivityCompat.OnRequestPermissionsResultCallback

ActivityCompat.shouldShowRequestPermissionRationale()

The v4 support library also contains the PermissionChecker class, which provides several static utility methods for apps that use IPC to provide services for other apps. For example,PermissionChecker.checkCallingPermission() checks whether an IPC made by a particular package has a specified permission.

FragmentCompat.requestPermissions()

FragmentCompat.shouldShowRequestPermissionRationale()

requestPermissions() 的一些说明:

Note: When your app calls the framework‘s requestPermissions() method, the system shows a standard dialog box to the user.

Your app cannot configure or alter that dialog box. If you need to provide any information or explanation to the user,

you should do that before you call requestPermissions(), as described in Explain why the app needs permissions.

当调用 requestPermissions() 时,系统会显示一个获取权限的提示对话框,当前应用不能配置和修改这个对话框,

如果需要提示用户一些这个权限相关的信息或说明,需要在调用 requestPermissions() 之前处理。

shouldShowRequestPermissionRationale() 的一些说明:

To help find the situations where you need to provide extra explanation, the system provides theshouldShowRequestPermissionRationale() method.

This method returns true if the app has requested this permission previously and the user denied the request.

That indicates that you should probably explain to the user why you need the permission.

If the user turned down the permission request in the past and chose the Don‘t ask again option in the permission request system dialog, this method returns false.

The method also returns false if the device policy prohibits the app from having that permission.

1. 第一次请求权限时,用户拒绝了,下一次:shouldShowRequestPermissionRationale()  返回 true,应该显示一些为什么需要这个权限的说明

2.第二次请求权限时,用户拒绝了,并选择了“不在提醒”的选项时:shouldShowRequestPermissionRationale()  返回 false

3. 设备的策略禁止当前应用获取这个权限的授权:shouldShowRequestPermissionRationale()  返回 false

注意:上面的:第二次请求权限时,才会有“不在提醒”的选项,如果用户一直拒绝,并没有选择“不在提醒”的选项,下次请求权限时,会继续有“不在提醒”的选项

十、shouldShowRequestPermissionRationale() 的方法说明:

Gets whether you should show UI with rationale for requesting a permission.

You should do this only if you do not have the permission and the context in which the permission is requested does not clearly communicate to the user what would be the benefit from granting this permission.

For example, if you write a camera app, requesting the camera permission would be expected by the user and no rationale for why it is requested is needed. If however, the app needs location for tagging photos then a non-tech savvy user may wonder how location is related to taking photos. In this case you may choose to show UI with rationale of requesting this permission.

根据方法说明:
显示权限说明:是根据你的应用中使用的权限分类来的:
1.用户容易知道应用需要获取的权限:如一个拍照应用,需要摄像头的权限,是很正常,不用提示。
2.一些用户感觉困惑的一些权限:如:分享图片,还需要获取位置的权限,这个需要提示用户:为什么需要这个权限。

时间: 2024-08-01 22:47:28

android 6.0权限全面详细分析和解决方案的相关文章

Android 6.0权限管理的解析与实战

一.引言 随着Android6.0发布,系统增加了一些新的特性和功能.这次的发布介绍了一种新的权限机制.用户可以在运行时直接管理应用程序的权限.这个功能提升了权限控制的可见性和可控性.同时简化了安装和自动升级过程,用户可以单独撤销或者授予应用程序某项权限,对应用拥有更多的控制权. 二.Android 6.0权限机制 当你应用程序target是Android 6.0及以上(API level 23),确保在运行时检查和请求权限.为了确定你的app是否授予某个权限,通过checkSelfPermis

Android 6.0 权限管理

Android 6.0 权限管理 Android permission 运行时权限(Runtime permission) 旧版兼容 6.0权限弹框的两种模式 6.0之后的权限分类 Normal permissions(普通权限) Dangerous permissions(危险权限) 运行时权限请求的基本步骤 权限请求策略 运行时权限(Runtime permission) android的权限系统一直是首要的安全概念,因为这些权限只在安装的时候被询问一次.一旦安装了,app可以在用户毫不知晓

Android 6.0 权限知识学习笔记

最近在项目上因为6.0运行时权限吃了亏,发现之前对运行时权限的理解不足,决定回炉重造,重新学习一下Android Permission. 进入正题: Android权限 在Android系统中,权限分为三种:正常权限.危险权限和特殊权限: 正常权限:不会直接给用户隐私权带来风险.如果您的应用在其清单中列出了正常权限,系统将自动授予该权限. 危险权限:涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域.例如,能够读取用户的联系人属于危险权限.如果应用声

Android开发——Android 6.0权限管理机制详解

0.前言 最近在研究所实习,我负责维护Android手机取证项目的Android客户端,有客户反映我们的APP在Android6.0无响应,经过调试发现SD卡读写权限权限被拒绝.但明明是在AndroidManifest.xml文件中声明过的.查了很多资料才知道Android6.0的很多权限申请机制发生了改变,可以说是Android6.0在安全机制上更进了一步吧,因此写下这篇文章以记录. 注:在运行程序时,对于某些权限向用户询问申请(后面会详细地讲)时因为我们知道客户在我们APP中不会点"拒绝&q

GandCrab5.0.9样本详细分析

?前言:?WannaCry利用永恒之蓝漏洞爆发以后,病毒安全的前沿对抗最频繁种类则是勒索病毒了,17年初或者更早就有人捕获了GandCrab家族的勒索病毒,直到18年已经更新迭代到了5.0版本,18年进入尾声的时候,安全研究人员发现了GandCrab勒索病毒的V5.1最新版变种.?对于勒索个人看法:当第一次听说勒索病毒的时候,就感觉一定是无法抗拒的利益驱动,才会让某一群人去迭代.维护.研发勒索病毒.这道理就像开了一家公司一样,能把病毒走向全世界,已经是很成功了,看似简单,其实背后的关联复杂.除了

项目中处理android 6.0权限管理问题

android 6.0对于权限管理比较收紧,因此在适配android 6.0的时候就很有必要考虑一些权限管理的问题. 如果你没适配6.0的设备并且权限没给的话,就会出现类似如下的问题: java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.CALL dat=tel:xxxxxxxxxxx cmp=com.android.server.telecom/.componen

Android 6.0权限申请

 一.6.0发布权限变化 1.Android需要手动加入权限申请原因: 1)权限分为了两类:一类是Normal Permissions,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动.访问网络等:另一类是Dangerous Permission,一般是涉及到用户隐私的,需要用户进行授权,比如读取sdcard.访问通讯录等. Normal Permissions如下 ACCESS_LOCATION_EXTRA_COMMANDS ACCESS_NETWORK_STATE ACCES

Android 6.0权限问题

Android 6.0 open failed: EACCES (Permission denied) 对于6.0+权限问题,报错如上: 解决方案: Android 6.0 (Marshmallow). 添加新的权限模型,需要在运行时动态获取所需要的权限. 首先判断手机系统版本是否在6.0+然后才是支持运行时动态获取权限: private boolean canMakeSmores(){ return(Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLI

Android 4.0 Launcher源码分析系列(一)

从今天起傻蛋打算做一个系列文章,对最新的Android 4.0 系统中的Launcher,也就是Android 4.0原生的桌面程序,进行一个深入浅出的分析,从而引领Android系统的编程爱好者对 Launcher的设计思想,实现方式来做一个研究,从而能够通过这个实例最掌握到目前世界领先的设计方法,同时在程序中加入我们的一些新的实现.众所周知,对一些优秀源代码的分析,是提高编程水平的一条便捷的方式,希望本系列文章能够给大家带来一定的启发,同时欢迎大家和作者一起讨论,作者的微博是:http://