Android60运行时权限处理完全解析(转载)
转载请标明出处:
http://blog.csdn.net/lmj623565791/article/details/50709663;
本文出自:【张鸿洋的博客】
一、概述
随着Android 6.0发布以及普及,我们开发者所要应对的主要就是新版本SDK带来的一些变化,首先关注的就是权限机制的变化。对于6.0的几个主要的变化,查看查看官网的这篇文章http://developer.android.com/intl/zh-cn/about/versions/marshmallow/android-6.0-changes.html,其中当然包含Runtime Permissions。
ok,本篇文章目的之一就是对运行时权限处理的一个介绍,以及对目前权限相关的库的一些了解。
当然非常推荐阅读官网权限相关文章:
- Working with System Permissions
- Permissions Best Practices
本文也是在上述文章基础上理解、实验以及封装。
二、运行时权限的变化及特点
对于6.0以下的权限及在安装的时候,根据权限声明产生一个权限列表,用户只有在同意之后才能完成app的安装,造成了我们想要使用某个app,就要默默忍受其一些不必要的权限(比如是个app都要访问通讯录、短信等)。而在6.0以后,我们可以直接安装,当app需要我们授予不恰当的权限的时候,我们可以予以拒绝(比如:单机的象棋对战,请求访问任何权限,我都是不同意的)。当然你也可以在设置界面对每个app的权限进行查看,以及对单个权限进行授权或者解除授权。
新的权限机制更好的保护了用户的隐私,Google将权限分为两类,一类是Normal Permissions,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动、访问网络等;另一类是Dangerous Permission,一般是涉及到用户隐私的,需要用户进行授权,比如读取sdcard、访问通讯录等。
- 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
- Dangerous Permissions:
group:android.permission-group.CONTACTSpermission:android.permission.WRITE_CONTACTSpermission:android.permission.GET_ACCOUNTSpermission:android.permission.READ_CONTACTSgroup:android.permission-group.PHONEpermission:android.permission.READ_CALL_LOGpermission:android.permission.READ_PHONE_STATEpermission:android.permission.CALL_PHONEpermission:android.permission.WRITE_CALL_LOGpermission:android.permission.USE_SIPpermission:android.permission.PROCESS_OUTGOING_CALLSpermission:com.android.voicemail.permission.ADD_VOICEMAILgroup:android.permission-group.CALENDARpermission:android.permission.READ_CALENDARpermission:android.permission.WRITE_CALENDARgroup:android.permission-group.CAMERApermission:android.permission.CAMERAgroup:android.permission-group.SENSORSpermission:android.permission.BODY_SENSORSgroup:android.permission-group.LOCATIONpermission:android.permission.ACCESS_FINE_LOCATIONpermission:android.permission.ACCESS_COARSE_LOCATIONgroup:android.permission-group.STORAGEpermission:android.permission.READ_EXTERNAL_STORAGEpermission:android.permission.WRITE_EXTERNAL_STORAGEgroup:android.permission-group.MICROPHONEpermission:android.permission.RECORD_AUDIOgroup:android.permission-group.SMSpermission:android.permission.READ_SMSpermission:android.permission.RECEIVE_WAP_PUSHpermission:android.permission.RECEIVE_MMSpermission:android.permission.RECEIVE_SMSpermission:android.permission.SEND_SMSpermission:android.permission.READ_CELL_BROADCASTS
可以通过adb shell pm list permissions -d -g
进行查看。
看到上面的dangerous permissions,会发现一个问题,好像危险权限都是一组一组的,恩,没错,的确是这样的,
那么有个问题:分组对我们的权限机制有什么影响吗?
的确是有影响的,如果app运行在Android 6.x的机器上,对于授权机制是这样的。如果你申请某个危险的权限,假设你的app早已被用户授权了同一组的某个危险权限,那么系统会立即授权,而不需要用户去点击授权。比如你的app对READ_CONTACTS
已经授权了,当你的app申请WRITE_CONTACTS
时,系统会直接授权通过。此外,对于申请时弹出的dialog上面的文本说明也是对整个权限组的说明,而不是单个权限(ps:这个dialog是不能进行定制的)。
不过需要注意的是,不要对权限组过多的依赖,尽可能对每个危险权限都进行正常流程的申请,因为在后期的版本中这个权限组可能会产生变化。
三、相关API
好在运行时相关的API也比较简单,所以适配起来并不会非常痛苦。
API的讲解就跟着申请权限步骤一起了:
在AndroidManifest文件中添加需要的权限。
这个步骤和我们之前的开发并没有什么变化,试图去申请一个没有声明的权限可能会导致程序崩溃。
检查权限
if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED) { }else{// }
这里涉及到一个API,
ContextCompat.checkSelfPermission
,主要用于检测某个权限是否已经被授予,方法返回值为PackageManager.PERMISSION_DENIED
或者PackageManager.PERMISSION_GRANTED
。当返回DENIED就需要进行申请授权了。申请授权
ActivityCompat.requestPermissions(thisActivity,new String[]{Manifest.permission.READ_CONTACTS},MY_PERMISSIONS_REQUEST_READ_CONTACTS);
该方法是异步的,第一个参数是Context;第二个参数是需要申请的权限的字符串数组;第三个参数为requestCode,主要用于回调的时候检测。可以从方法名
requestPermissions
以及第二个参数看出,是支持一次性申请多个权限的,系统会通过对话框逐一询问用户是否授权。处理权限申请回调
@Override public void onRequestPermissionsResult(int requestCode,String permissions[], int[] grantResults) {switch (requestCode) {case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {// If request is cancelled, the result arrays are empty.if (grantResults.length > 0&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {// permission was granted, yay! Do the// contacts-related task you need to do.} else {// permission denied, boo! Disable the// functionality that depends on this permission.}return;}} }
ok,对于权限的申请结果,首先验证requestCode定位到你的申请,然后验证grantResults对应于申请的结果,这里的数组对应于申请时的第二个权限字符串数组。如果你同时申请两个权限,那么grantResults的length就为2,分别记录你两个权限的申请结果。如果申请成功,就可以做你的事情了~
当然,到此我们的权限申请的不走,基本介绍就如上述。不过还有个API值得提一下:
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,Manifest.permission.READ_CONTACTS)) // Show an expanation to the user *asynchronously* -- don't block// this thread waiting for the user's response! After the user// sees the explanation, try again to request the permission.}
这个API主要用于给用户一个申请权限的解释,该方法只有在用户在上一次已经拒绝过你的这个权限申请。也就是说,用户已经拒绝一次了,你又弹个授权框,你需要给用户一个解释,为什么要授权,则使用该方法。
那么将上述几个步骤结合到一起就是:
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED) {// Should we show an explanation?if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,Manifest.permission.READ_CONTACTS)) {// Show an expanation to the user *asynchronously* -- don't block// this thread waiting for the user's response! After the user// sees the explanation, try again to request the permission.} else {// No explanation needed, we can request the permission.ActivityCompat.requestPermissions(thisActivity,new String[]{Manifest.permission.READ_CONTACTS},MY_PERMISSIONS_REQUEST_READ_CONTACTS);// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an// app-defined int constant. The callback method gets the// result of the request.}
}
四、简单的例子
这里写一个简单的例子,针对于运行时权限。相信大家在最开始接触Android的时候,都利用Intent实验了打电话、发短信等功能。
我们看看直接拨打电话在Android 6.x的设备上权限需要如何处理。
当然代码非常简单:
package com.zhy.android160217;import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;public class MainActivity extends AppCompatActivity
{private static final int MY_PERMISSIONS_REQUEST_CALL_PHONE = 1;@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void testCall(View view){if (ContextCompat.checkSelfPermission(this,Manifest.permission.CALL_PHONE)!= PackageManager.PERMISSION_GRANTED){ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CALL_PHONE},MY_PERMISSIONS_REQUEST_CALL_PHONE);} else{callPhone();}}public void callPhone(){Intent intent = new Intent(Intent.ACTION_CALL);Uri data = Uri.parse("tel:" + "10086");intent.setData(data);startActivity(intent);}@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults){if (requestCode == MY_PERMISSIONS_REQUEST_CALL_PHONE){if (grantResults[0] == PackageManager.PERMISSION_GRANTED){callPhone();} else{// Permission DeniedToast.makeText(MainActivity.this, "Permission Denied", Toast.LENGTH_SHORT).show();}return;}super.onRequestPermissionsResult(requestCode, permissions, grantResults);}
}
在Android 6.x上运行是,点击testCall,即会弹出授权窗口,如何你Allow则直接拨打电话,如果Denied则Toast弹出”Permission Denied”。
例子很简单,不过需要注意的是,对于Intent这种方式,很多情况下是不需要
授权的甚至权限都不需要的,比如:你是到拨号界面而不是直接拨打电话,就不需要去申请权限;打开系统图库去选择照片;调用系统相机app去牌照等。更多请参考Consider Using an Intent
.
当然,上例也说明了并非所有的通过Intent的方式都不需要申请权限。一般情况下,你是通过Intent打开另一个app,让用户通过该app去做一些事情,你只关注结果(onActivityResult),那么权限是不需要你处理的,而是由打开的app去处理。
五、封装
(1)申请权限
虽然权限处理并不复杂,但是需要编写很多重复的代码,所以目前也有很多库对用法进行了封装,大家可以在github首页搜索:android permission
,对比了几个库的使用方式,发现https://github.com/lovedise/PermissionGen这个库据我所见相比较而言使用算是比较简单的,那么就以这个库的源码为基础来讲解,大家有兴趣可以多看几个库的源码。
封装的代码很简单,正如大家的对上面的权限代码所见,没有特别复杂的地方。
对于申请权限的代码,原本的编写为:
if (ContextCompat.checkSelfPermission(this,Manifest.permission.CALL_PHONE)!= PackageManager.PERMISSION_GRANTED)
{ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CALL_PHONE},MY_PERMISSIONS_REQUEST_CALL_PHONE);
} else
{callPhone();
}
大家可以看到,对于其他的权限,其实申请的逻辑是类似的;唯一不同的肯定就是参数,那么看上面的代码,我们需要3个参数:Activity|Fragment
、权限字符串数组
、int型申请码
。
也就是说,我们只需要写个方法,接受这几个参数即可,然后逻辑是统一的。
public static void needPermission(Fragment fragment, int requestCode, String[] permissions)
{requestPermissions(fragment, requestCode, permissions);
}@TargetApi(value = Build.VERSION_CODES.M)
private static void requestPermissions(Object object, int requestCode, String[] permissions)
{if (!Utils.isOverMarshmallow()){doExecuteSuccess(object, requestCode);return;}List<String> deniedPermissions = Utils.findDeniedPermissions(getActivity(object), permissions);if (deniedPermissions.size() > 0){if (object instanceof Activity){((Activity) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);} else if (object instanceof Fragment){((Fragment) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);} else{throw new IllegalArgumentException(object.getClass().getName() + " is not supported");}} else{doExecuteSuccess(object, requestCode);}
}
Utils.findDeniedPermissions
其实就是check没有授权的权限。
#Utils
@TargetApi(value = Build.VERSION_CODES.M)
public static List<String> findDeniedPermissions(Activity activity, String... permission)
{List<String> denyPermissions = new ArrayList<>();for (String value : permission){if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED){denyPermissions.add(value);}}return denyPermissions;
}
那么上述的逻辑就很清晰了,需要的3种参数传入,先去除已经申请的权限,然后开始申请权限。
ok,我相信上面代码,大家扫一眼就可以了解了。
(2)处理权限回调
对于回调,因为要根据是否授权去执行不同的事情,所以很多框架也需要些一连串的代码,或者和前面的申请代码耦合。不过这个框架还是比较方便的,也是我选择它来讲解的原因。
回调主要做的事情:
- 了解是否授权成功。
- 根据授权情况进行回调。
对于第一条逻辑都一样;对于第二条,因为涉及到两个分支,每个分支执行不同的方法。
对于第二条,很多框架选择将两个分支的方法在申请权限的时候进行注册,然后在回调中根据requestCode进行匹配执行,不过这样需要在针对每次申请进行对象管理。
不过这个框架采取了一种很有意思的做法,它利用注解去确定需要执行的方法,存在两个注解:
@PermissionSuccess(requestCode = 100)
@PermissionFail(requestCode = 100)
利用反射根据授权情况+requestCode即可找到注解标注的方法,然后直接执行即可。
大致的代码为:
@Override public void onRequestPermissionsResult(int requestCode, String[] permissions,int[] grantResults) {PermissionGen.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}private static void requestResult(Object obj, int requestCode, String[] permissions,int[] grantResults)
{List<String> deniedPermissions = new ArrayList<>();for (int i = 0; i < grantResults.length; i++){if (grantResults[i] != PackageManager.PERMISSION_GRANTED){deniedPermissions.add(permissions[i]);}}if (deniedPermissions.size() > 0){doExecuteFail(obj, requestCode);} else{doExecuteSuccess(obj, requestCode);}
}
首先根据grantResults进行判断成功还是失败,对于成功则:
private static void doExecuteSuccess(Object activity, int requestCode)
{Method executeMethod = Utils.findMethodWithRequestCode(activity.getClass(),PermissionSuccess.class, requestCode);executeMethod(activity, executeMethod);
}#Utils
public static <A extends Annotation> Method findMethodWithRequestCode(Class clazz, Class<A> annotation, int requestCode)
{for (Method method : clazz.getDeclaredMethods()){if (method.isAnnotationPresent(annotation)){if (isEqualRequestCodeFromAnntation(method, annotation, requestCode)){return method;}}}return null;
}
根据注解和requestCode找到方法,然后反射执行即可。失败的逻辑类似,不贴代码了。
ok,到此我们的运行时权限相对于早起版本的变化、特点、以及如何处理和封装都介绍完了。
不过对于上面讲解的库,肯定有人会说:运行时反射会影响效率,没错,不过我已经在上述代码的基础上将运行时注解改成Annotation Processor的方式了,即编译时注解,这样的话,就不存在反射损失效率的问题了。本来准备fork修改,然后PR,结果写完,改动太大,估计PR是不可能通过了,所以另起项目了,也方便后面的做一些扩展和维护。
详见库:https://github.com/hongyangAndroid/MPermissions.
六、MPermissions用法
对外的接口和PermissionGen基本一致,因为申请只需要三个参数,抛弃了使用原本类库的单例的方式,直接一个几个静态方法,简单整洁暴力。
贴一个用法:
public class MainActivity extends AppCompatActivity
{private Button mBtnSdcard;private static final int REQUECT_CODE_SDCARD = 2;@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mBtnSdcard = (Button) findViewById(R.id.id_btn_sdcard);mBtnSdcard.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View v){MPermissions.requestPermissions(MainActivity.this, REQUECT_CODE_SDCARD, Manifest.permission.WRITE_EXTERNAL_STORAGE);}});}@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults){MPermissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);super.onRequestPermissionsResult(requestCode, permissions, grantResults);}@PermissionGrant(REQUECT_CODE_SDCARD)public void requestSdcardSuccess(){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();}
}
是不是简单明了~~对于onRequestPermissionsResult所有的Activity都是一致的,所以可以放到BaseActivity中去。此外,在Fragment中使用的方式一致,详见demo。
详见库:https://github.com/hongyangAndroid/MPermissions.
至于为什么不直接介绍MPermission的源码,因为主要涉及到Annotation Processor,所以以这种方式引出,后面考虑单篇博文介绍下我目前所会的编译时注解的相关做法以及API的使用。
最后感谢PermissionGen
库,祝大家元宵节快乐~!
欢迎关注我的微博:
http://weibo.com/u/3165018720
讨论群:284327959,欢迎入群
微信公众号:hongyangAndroid
(欢迎关注,第一时间推送博文信息)
转载于:https://www.cnblogs.com/qingminahai/p/5593449.html
Android60运行时权限处理完全解析(转载)相关推荐
- Android 6.0 运行时权限处理完全解析
一.概述 随着Android 6.0发布以及普及,我们开发者所要应对的主要就是新版本SDK带来的一些变化,首先关注的就是权限机制的变化.对于6.0的几个主要的变化,查看查看官网的这篇文章http:// ...
- Android8.0运行时权限策略变化和适配方案
版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com Android8.0也就是Android O即将要发布了,有很多新特性,目前我们可以通过Android ...
- Android 6.0 运行时权限处理
2019独角兽企业重金招聘Python工程师标准>>> 运行时权限介绍 Android 6.0在我们原有的AndroidManifest.xml声明权限的基础上, 又新增了运行时权限 ...
- (调用系统电话薄)运行时权限的基本使用
GitHub项目地址: https://github.com/Skymqq/RuntimePermissionTest.git 运行时权限是Android6.0系统引入的新特性,那么为何要引入这种运行 ...
- 安卓从业者应该关注:Android 6.0的运行时权限
Android 6.0,代号棉花糖,自发布伊始,其主要的特征运行时权限就很受关注.因为这一特征不仅改善了用户对于应用的使用体验,还使得应用开发者在实践开发中需要做出改变. 没有深入了解运行时权限的开发 ...
- Android PermissionUtils:运行时权限工具类及申请权限的正确姿势
Android PermissionUtils:运行时权限工具类及申请权限的正确姿势 ifadai 关注 2017.06.16 16:22* 字数 318 阅读 3637评论 1喜欢 6 Permis ...
- 如何优雅地申请Android运行时权限
转载本文需注明出处:微信公众号EAWorld,违者必究. 前言: Android 是一个权限分隔的操作系统,其中每个应用都有其独特的系统标识.在默认情况下任何应用都没有权限执行对其他应用.操作系统或用 ...
- 优雅地处理运行时权限请求
前言 从android 6.0(API 级别 23)开始,android引入了运行时权限,用户开始在应用运行时向其授予权限,而不是在应用安装时向其授予权限,如果应用的某项功能需要使用到受运行时权限保护 ...
- Android运行时权限 1
Android 运行时权限 一.Android 权限发展史 二.Android6.0 权限简介 1.Normal Permissions (普通权限) 2.Dangerous Permissions ...
最新文章
- byte数组添加数据_数据类型
- Android性能优化之渲染篇(一)
- yolov5 ncnn
- jquery animate
- HTML中Select的使用详解
- Android在全球的市场份额跃居全球第一
- opensource项目_Opensource.com 2015社区奖的获奖者
- it'll be much more productive to read pdf files via a touch screen
- 难道你不好奇?Thread.sleep(0):线程休眠0秒有什么意义!
- 双系统bios如何设置默认启动系统_BIOS(主板)常用功能:设置启动磁盘顺序,迁移系统必备...
- 【数学逻辑思维】A 好玩的数独游戏——001
- Linux:一位猫奴的意外逆袭
- 二胎政策来袭 职场妈妈生还是升
- 排序算法之快速排序实现及算法思想
- 防疫主题的微信公众号图文排版有哪些技巧?
- Protobuf3 google protobuf
- 我的理想200字计算机工程师,我的理想:工程师
- 网络并发知识第一天网络通信基本原理
- 一文读懂高速互联的阻抗及反射
- 用华为手机扫一扫功能检测食物热量方法
热门文章
- 经验歧视 学历歧视 英语歧视及其它
- Android 12系统源码_SystemUI(二)系统状态栏StatusBar的创建流程
- 智能电话会议系统(11)---Android 6.0 Phone 多方通话流程
- 清华计算机系行政面试,清华大学复试面试过程及成功经验分享
- linux分区的基本方案
- Column ‘XXX‘ cannot be null问题 后端接收不到前端表单提交的数据
- 2021年安全员-A证最新解析及安全员-A证考试试卷
- Java使用QQ邮箱发送纯文本格式信息
- Java操作Word文档添加水印(文字/图片)
- 黑作坊灯光大师完美破解版