Android M 权限相关教程:
本篇内容:
检查权限(Check For Permissions)
弹窗解释权限使用原因(Explain why the app needs permissions)
请求权限(Request the permissions you need)
处理响应的请求权限(Handle the permissions request response)
实战案例:全局通用的(包含权限处理的)电话功能案例
部分源码分析
权限描述
从android 6.0(api 23)开始,用户对运用程序的授权是在运行运用时,而不是在安装运用程序。这简化了运用程序安装过程,因为用户在安装或者更新运用时不需要授权权限。它还给用户更多的控制运用程序的功能:例如:用户给予相机运用访问相机的权限而不是设备定位权限。 用户可以撤销权限在任何时候,通过跳转到设置界面。
系统权限分类:正常和危险.
正常权限 : 不会直接危及用户的隐私。若是运用程序在manifest中申请了正常权限,系统会自动授予权限。
危险权限 : 可以用程序获取到用户的机密数据。若是运用程序在mainfest中申请了危险的权限,用户必须手动权限给运用程序。
在android全部版本中,运用程序都需要在manifest中,申请使用运用程序需要用到的正常权限和危险权限,即所所述的声明权限。 但是声明的效果会因系统版本和运用程序的target sdk而导致不同:
若是设备系统在Android 5.1或者更低系统版本,或者运用程序的target sdk是22或者更低(两者满足其一的前提下),在mainfest中申请的危险权限,用户需要在安装时授权同意。若是用户没有授权,则系统根本不会安装运用程序。
若是设备系统是android 6.0以上(包含6.0),并且运用程序的target sdk是23以上(包含23)(两者都满足的前提下)时,运用程序在manifest中申请权限,程序会在运行使用时请求需要使用的危险权限。用户可以授权或者拒绝任何一个权限,并且运用程序可以继续运行以有限的功能,即使用户拒绝了权限请求。
注意点: 从android 6.0开始,用户可以随时撤销任何运用程序的权限,即便是低版本targetsdk的运用程序。无论运用程序的target sdk多少,在缺少需用使用权限时,应该测试运用程序,以确认其正常操作。
危险和正常权限的官方介绍:
https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
前面描述了权限的使用事项, 这里介绍如何使用Android Support Library来检查权限,请求权限。
使用Android Support Library来处理权限的好处: Android系统框架提供了一些类似在Android 6.0 出现的方法。但是,使用Support Library 更简单。因为运用程序不需要再调用Support Library提供的方法之前,进行检查运行设备的系统版本。
第一步:Check For Permissions:
若是运用程序在使用过程中需要用到危险的权限,则 在执行需被授权权限的操作之前检查用户是否授予权限。
用户是可以随时撤销权限,故用户在昨天使用了相机授予了权限,不能确保今天任然具备授予的权限。
检查权限的方法: ContextCompat.checkSelfPermission()
返回状态为PackageManager.PERMISSION_GRANTED是用户已经授予权限,这运用程序能继续执行需权限的使用。
返回状态为PERMISSION_DENIED是未授权,则应该请求授予权限后,再执行需权限的使用。
这里展示了几种常用的检查权限的方法,效果是一样的:
/**
* PackageManager.checkPermission():检查指定包是否被授予特定权限。(API 1 中出现)
* 返回结果:授权(PackageManager.PERMISSION_GRANTED),不授权(PERMISSION_DENIED)。
* 处理的权限被禁止的做法: try catch语句处理
*
* @param context
* @param permission
* @param packageName
* @return
*/
public static boolean applyAppPermission(Context context, String permission, String packageName){
boolean apply=false;
PackageManager packageManager= context.getPackageManager();
try{
int result= packageManager.checkPermission(permission,packageName);
if(result==PackageManager.PERMISSION_GRANTED){
apply=true;
}
}catch ( Exception e){
e.printStackTrace();
}
return apply;
}
/**
* Context.checkSelfPermission():用于确定是否授予权限,在android api23 框架出现。
*
* 返回结果:授权(PackageManager.PERMISSION_GRANTED),不授权(PERMISSION_DENIED)。
*
*
* @param context
* @param permission
* @return
*/
public static boolean checkContextPermission(Context context,String permission){
boolean checkResult=false;
try{
if(Build.VERSION.SDK_INT>=23){ //当前手机的系统 的api>M(23)
//app的目标版本大于23,才使用Context.checkSelfPermission()
if(getAppTargetVerson(context)>=Build.VERSION_CODES.M){
int result=context.checkSelfPermission(permission);
if(result==PackageManager.PERMISSION_GRANTED){
checkResult=true;
}
}else{//借助v4包下PermissionChecker.checkSelfPermission()
checkResult=(
PermissionChecker.checkSelfPermission(context,permission)
==PackageManager.PERMISSION_GRANTED );
}
}else{//借助android系统下 PackageManager.checkPermission()
checkResult=applyAppPermission(context,permission,context.getPackageName());
}
}catch (Exception e){
e.printStackTrace();
}
return checkResult;
}
/**
* ContextCompat.checkSelfPermission():确定是否已经获取到指定权限,出现在v4包中。
* 返回结果:授权(PackageManager.PERMISSION_GRANTED),不授权(PERMISSION_DENIED)。
*
* @param context
* @param permission
* @return
*/
public static boolean checkPermissionFromSupportLibrary(Context context,String permission){
boolean checkResult=false;
try{
int result= ContextCompat.checkSelfPermission(context,permission);
if(result==PackageManager.PERMISSION_GRANTED){
checkResult=true;
}
}catch (Exception e){
e.printStackTrace();
}
return checkResult;
}
获取运用程序的target sdk的方法:
/**
* 获取app的目标版本
* @param context
* @return
*/
public static int getAppTargetVerson(Context context){
int verson=0;
try{
PackageManager packageManager= context.getPackageManager();
PackageInfo packageInfo= packageManager.getPackageInfo(context.getPackageName(),0);
ApplicationInfo applicationInfo= packageInfo.applicationInfo;
verson=applicationInfo.targetSdkVersion;
}catch (Exception e){
e.printStackTrace();
}
return verson;
}
第二步:Request Permissions:
若是在运用程序的manifest中申请危险权限,则必须用户同意这危险的权限。
Android提供了几种方法来请求权限。调用这些方法时,手机会弹出一个dialog,故不需要开发者自定义。
Explain why the app needs permissions(解释应用程序需要权限的原因):
在某些情况下,应该帮组用户知道运用程序需要权限的原因。例如用户开启相机程序,用户不会惊讶该程序需要使用相机的权限,但是用户不知道该程序需要使用所在位置的权限和联系人权限的原因。在请求权限之前,开发者应该考虑向用户说明权限原因。请记住,不能让用户看到太多的解释,这样会让用户感到沮丧,从而卸载运用程序。
有一种弹出权限解释的方式:在用户已经拒接某个权限的前提下,用户需要使用需该权限的功能时,弹出一个解释,让用户了解运用程序需要该权限的原因。
Android提供了一个utility方法:shouldShowRequestPermissionRationale()用于判断用户是否需要解释的权限。
以下两种情况需注意:
若是运用程序先前请求了此权限,并且用户拒绝了这权限,然后再调用
shouldShowRequestPermissionRationale()会返回true.若是用户拒绝了权限,且在权限弹窗张选中了不再询问的选项,再调用
shouldShowRequestPermissionRationale()会返回false.
Request the permissions you need:
若是运用程序尚未用户所需要的权限,运用程序必须调用 requestPermissions() 来获取对应的权限。
若是在requestPermissions() 中指定了某种权限和请求的标示码,这方法会异步运行。在用户响应弹窗后,系统会在回调方法中将请求的标示码和授权结果回调 。注意点:调用requestPermissions()后,系统会弹出一个弹窗,运用程序不需要再弹出一个多余的弹窗。
若是运用程序弹出一个解释授权的弹窗,应该在调用requestPermissions()之前。这里是检查运用程序是否具备打电话的权限, 是否要解释权限原因,且在必要时候请求拨打电话的权限:
/**
* ActivityCompat.shouldShowRequestPermissionRationale():
* 是否应该显示一个UI控件,来显示请求权限的理由。
*
* 使用情况:
* 1.当某个权限之前已经请求,但是被拒绝,
* 这时调用shouldShowRequestPermissionRationale()返回true.
*
* 2.当用户拒绝某个权限,且在权限授权窗口设置不再提醒时,
* 调用shouldShowRequestPermissionRationale()返回false.
*
* 3.当设备禁止运用程序获取某个权限时,调用shouldShowRequestPermissionRationale()返回false.
* @param activity
* @param permissionName
* @return
*/
public static boolean checkIfShowPermissionRationale(Activity activity, String permissionName){
boolean should=false;
try{
should= ActivityCompat.shouldShowRequestPermissionRationale(activity,permissionName);
}catch ( Exception e){
e.printStackTrace();
}
return should;
}
/**
* 未知电话权限的下,进行电话功能
* @param activity
* @param phone
*/
public void unsafetyCallPhone(Activity activity,String phone){
synchronized (object){
//检查是否授权
boolean isGranted=checkContextPermission(context, PERMISSION_PHONE);
Log.i(TAG," 是否授权 "+isGranted);
if (isGranted) {
safertyCallPhone(activity,phone);
} else {
/* 注意点:
* 1.要请求的权限,必须在Manifests中注册过的权限。
* 2.手机系统必须大于或等于android 6.0(即API23以上),才能使用
* Context#requestPermissions(activity, permissions, requestCode);
*/
boolean isShouldShowRationale=checkIfShowPermissionRationale(
activity, PERMISSION_PHONE);
Log.i(TAG," 是否弹窗解释 "+isShouldShowRationale);
if (isShouldShowRationale) {
//需显示授权的原因: 这里可以显示dialog
//这里弹出一个解释运用程序需要权限原因的弹窗,不过这里被忽略这步。
ActivityCompat.requestPermissions(activity,
new String[]{PERMISSION_PHONE}, PHONE_REQUEST_CODE);
} else {
/*
*直接请求权限 ,因FragmentActivity已经实现了
* ActivityCompat.OnRequestPermissionsResultCallback,
*从onRequestPermissionsResult ()中可以知道请求权限结果。
*/
ActivityCompat.requestPermissions(activity,
new String[]{PERMISSION_PHONE}, PHONE_REQUEST_CODE);
}
}
}
}
/**
* 已经获取电话权限,进行电话功能
*
* 检查手机上程序的是否能解析intent.
* 1.通过Intent#resolveActivity(), 返回不为空代表能解析。
*
* 2.通过PackageManager#queryIntentXXX():返回一个list,list中item不为零,代表能解析。
*
*实际上, PackageManager#queryIntentXXX()返回的list,list中的item 是调用resolveActivity的内容
*
*
* @param activity
* @param phone
*/
public void safertyCallPhone(Activity activity,String phone){
try {
Intent intent=new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:" + phone));
if(intent.resolveActivity(activity.getPackageManager())!=null){
activity.startActivity(intent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
Handle the permissions request response:
当请求权限时,系统会弹出一个弹窗给用户。当用户响应后,系统会调用Activity中重写的onRequestPermissionsResult() 中来传递授权结果和请求的标示码(从requestPermissions()传递过来的)。
这里是一个请求拨打电话的权限,且在Activity中处理响应结果:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case PermissionOpts.PHONE_REQUEST_CODE:
if (grantResults.length > 0 && grantResults[0]
== PackageManager.PERMISSION_GRANTED) {//已经授权
permissionOpts.safertyCallPhone(this, phone);
} else {//未授权
showToast("请开启电话权限,否则无法正常联系");
}
break;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
public void showToast(String content) {
Toast.makeText(BaseApplication.getAppContext(), content, Toast.LENGTH_SHORT).show();
}
为了方便使用,这里是封装了一个权限操作类(完整代码):
/**
* Created by ${新根} on 2016/11/5.
* 博客:http://blog.csdn.net/hexingen
*
* 用途:权限的操作类
*
* 这里是检查联系的权限,且请求相应的权限的案例
*
*/
public class PermissionOpts {
public static final String TAG=PermissionOpts.class.getSimpleName();
private final Object object=new Object();
public final static int PHONE_REQUEST_CODE=101;
public final static String PERMISSION_PHONE= Manifest.permission.CALL_PHONE;
private Context context;
public PermissionOpts(Context context){
this.context=context;
}
//-----------------------以下几种检查运用程序权限的方法,效果是一样的。
/**
* PackageManager.checkPermission():检查指定包是否被授予特定权限。(API 1 中出现)
* 返回结果:授权(PackageManager.PERMISSION_GRANTED),不授权(PERMISSION_DENIED)。
* 处理的权限被禁止的做法: try catch语句处理
*
* @param context
* @param permission
* @param packageName
* @return
*/
public static boolean applyAppPermission(Context context, String permission, String packageName){
boolean apply=false;
PackageManager packageManager= context.getPackageManager();
try{
int result= packageManager.checkPermission(permission,packageName);
if(result==PackageManager.PERMISSION_GRANTED){
apply=true;
}
}catch ( Exception e){
e.printStackTrace();
}
return apply;
}
/**
* Context.checkSelfPermission():用于确定是否授予权限,在android api23 框架出现。
*
* 返回结果:授权(PackageManager.PERMISSION_GRANTED),不授权(PERMISSION_DENIED)。
*
*
* @param context
* @param permission
* @return
*/
public static boolean checkContextPermission(Context context,String permission){
boolean checkResult=false;
try{
if(Build.VERSION.SDK_INT>=23){//当前手机的系统 的api>M(23)
//app的目标版本大于23,才使用Context.checkSelfPermission()
if(getAppTargetVerson(context)>=Build.VERSION_CODES.M){
int result=context.checkSelfPermission(permission);
if(result==PackageManager.PERMISSION_GRANTED){
checkResult=true;
}
}else{//借助v4包下PermissionChecker.checkSelfPermission()
checkResult=(
PermissionChecker.checkSelfPermission(context,permission)
==PackageManager.PERMISSION_GRANTED);
}
}else{//借助android系统下 PackageManager.checkPermission()
checkResult=applyAppPermission(context,permission,context.getPackageName());
}
}catch (Exception e){
e.printStackTrace();
}
return checkResult;
}
/**
* ContextCompat.checkSelfPermission():确定是否已经获取到指定权限,出现在v4包中。
* 返回结果:授权(PackageManager.PERMISSION_GRANTED),不授权(PERMISSION_DENIED)。
*
* @param context
* @param permission
* @return
*/
public static boolean checkPermissionFromSupportLibrary(Context context,String permission){
boolean checkResult=false;
try{
int result= ContextCompat.checkSelfPermission(context,permission);
if(result==PackageManager.PERMISSION_GRANTED){
checkResult=true;
}
}catch (Exception e){
e.printStackTrace();
}
return checkResult;
}
/**
* 获取app的目标版本
* @param context
* @return
*/
public static int getAppTargetVerson(Context context){
int verson=0;
try{
PackageManager packageManager= context.getPackageManager();
PackageInfo packageInfo= packageManager.getPackageInfo(context.getPackageName(),0);
ApplicationInfo applicationInfo= packageInfo.applicationInfo;
verson=applicationInfo.targetSdkVersion;
}catch (Exception e){
e.printStackTrace();
}
return verson;
}
/**
* ActivityCompat.shouldShowRequestPermissionRationale(): 是否应该显示一个UI控件,来显示请求权限的理由。
*
* 使用情况:
* 1.当某个权限之前已经请求,但是被拒绝,这时调用shouldShowRequestPermissionRationale()返回true.
*
* 2.当用户拒绝某个权限,且在权限授权窗口设置不再提醒时,调用shouldShowRequestPermissionRationale()返回false.
*
* 3.当设备禁止运用程序获取某个权限时,调用shouldShowRequestPermissionRationale()返回false.
* @param activity
* @param permissionName
* @return
*/
public static boolean checkIfShowPermissionRationale(Activity activity, String permissionName){
boolean should=false;
try{
should= ActivityCompat.shouldShowRequestPermissionRationale(activity,permissionName);
}catch ( Exception e){
e.printStackTrace();
}
return should;
}
/**
* 未知电话权限的下,进行电话功能
* @param activity
* @param phone
*/
public void unsafetyCallPhone(Activity activity,String phone){
synchronized (object){
//检查是否授权
boolean isGranted=checkContextPermission(context, PERMISSION_PHONE);
Log.i(TAG," 是否授权 "+isGranted);
if (isGranted) {
safertyCallPhone(activity,phone);
} else {
//需显示授权的原因: 这里可以显示dialog
/* 注意点:
* 1.要请求的权限,必须在Manifests中注册过的权限。
* 2.手机系统必须大于或等于android 6.0(即API23以上),
* 才能使用Context#requestPermissions(activity, permissions, requestCode);
*/
boolean isShouldShowRationale=checkIfShowPermissionRationale(activity
, PERMISSION_PHONE);
Log.i(TAG," 是否弹窗解释 "+isShouldShowRationale);
if (isShouldShowRationale) {
//这里弹出一个解释运用程序需要权限原因的弹窗,但是这里忽略这步。
ActivityCompat.requestPermissions(activity,
new String[]{PERMISSION_PHONE}, PHONE_REQUEST_CODE);
} else { //直接请求权限
/**因FragmentActivity已经实现了
ActivityCompat.OnRequestPermissionsResultCallback,
onRequestPermissionsResult ()中可以知道请求权限结果。**/
ActivityCompat.requestPermissions(activity,
new String[]{PERMISSION_PHONE}, PHONE_REQUEST_CODE);
}
}
}
}
/**
* 已经获取电话权限,进行电话功能
*
* 检查手机上程序的是否能解析intent.
* 1.通过Intent#resolveActivity(), 返回不为空代表能解析。
*
* 2.通过PackageManager#queryIntentXXX():返回一个list,list中item不为零,代表能解析。
*
*实际上, PackageManager#queryIntentXXX()返回的list,list中的item 是调用resolveActivity的内容
*
*
* @param activity
* @param phone
*/
public void safertyCallPhone(Activity activity,String phone){
try {
Intent intent=new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:" + phone));
if(intent.resolveActivity(activity.getPackageManager())!=null){
activity.startActivity(intent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
将这个权限操作类的对象放到Appliaction子类中,便于程序全局使用:
/**
* Created by ${新根} on 2016/11/5.
* 博客:http://blog.csdn.net/hexingen
*
* 将权限操作类的对象放到这里,便于全局使用
*/
public class BaseApplication extends Application {
private PermissionOpts permissionOpts;
private static BaseApplication context;
@Override
public void onCreate() {
super.onCreate();
context=this;
initCommonOpts();
}
public static BaseApplication getAppContext(){
return context;
}
private void initCommonOpts() {
permissionOpts=new PermissionOpts(this);
}
public PermissionOpts getPermissionOpts(){
return permissionOpts;
}
}
测试结果: 荣耀7 android 6.0测试环境
安装app,后第一次使用的数据
11-05 15:33:31.617 4128-4128/com.szmj.permissionproject I/PermissionOpts: 是否授权 false
11-05 15:33:31.624 4128-4128/com.szmj.permissionproject I/PermissionOpts: 是否弹窗解释 false
第一次弹出权限的弹窗,效果如下:
点击拒绝权限的操作,第一次使用完成,效果图如下:
在上一步的基础上,再次点击拨打电话的数据
11-05 15:34:51.366 4128-4128/com.szmj.permissionproject I/PermissionOpts: 是否授权 false
11-05 15:34:51.367 4128-4128/com.szmj.permissionproject I/PermissionOpts: 是否弹窗解释 true
依旧弹出权限的弹窗,效果如下:
点击同意授权,第二次使用完成。
在上一步基础上,再次点击拨打电话的数据
11-05 15:35:08.609 4128-4128/com.szmj.permissionproject I/PermissionOpts: 是否授权 true
官方关于android6.0动态权限介绍: https://developer.android.com/training/permissions/requesting.html#perm-request
一些补充:
系统弹出的弹窗会描述运用程序需要访问的权限组,但是不会列出具体的权限。例如,请求READ_CONTACTS的权限,系统弹窗只会描述运用程序需要读取联系人。
用户只需要为每个授权组授一次权限。若是请求 运用程序中manifest中(用户已经授予权限的)授权组的其他权限,系统会自动授予他们,和用户同意授权的传递结果的方式一样,系统调用onRequestPermissionsResult(),传递PERMISSION_GRANTED(授权成功的标示)。
若是用户拒绝权限请求,运用程序应该采取适当的操作。例如,弹出一个弹窗,说明为什么它无法执行需要该权限的用户请求的操作。
源码解析:
ActivityCompat.requestPermissions():
从源码可知设备的系统版本小于6.0,没有执行请求权限的操作,只执行了检查权限的操作,然后将结果回调到onRequestPermissionsResult()中。可见,设备系统版本没有在6.0以上是无法进行请求权限的操作的。
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final int requestCode) {
if (Build.VERSION.SDK_INT >= 23) {
ActivityCompatApi23.requestPermissions(activity, permissions, requestCode);
} else if (activity instanceof OnRequestPermissionsResultCallback) {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
final int[] grantResults = new int[permissions.length];
PackageManager packageManager = activity.getPackageManager();
String packageName = activity.getPackageName();
final int permissionCount = permissions.length;
for (int i = 0; i < permissionCount; i++) {
grantResults[i] = packageManager.checkPermission(
permissions[i], packageName);
}
((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(
requestCode, permissions, grantResults);
}
});
}
}
ActivityCompat.shouldShowRequestPermissionRationale()中的源码:
也是设备系统版本没有在6.0以上才能执行检查是否弹出解释弹窗的操作
public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
@NonNull String permission) {
if (Build.VERSION.SDK_INT >= 23) {
return ActivityCompatApi23.shouldShowRequestPermissionRationale(activity, permission);
}
return false;
}