1、运行时权限定义
android6.0 运行时权限是指抛弃以前我们在manifest.xml文件中一次性赋予app权限,转而在程序真正运行时授予权限(需要用户同意),如果没有授予权限,则出现异常的一种机制。
6.0之前的权限模型
- 在AndroidManifest中声明可能用到的所有权限
- 用户在安装时,系统展示所有权限,用户安装即授予所有权限,取消则拒绝安装
6.0新的运行时权限
- 将权限分为一般权限和危险权限两种,一般权限跟以前一样在AndroidManifest声明,危险权限需要开发者在代码中手动的动态申请
- 动态申请权限,系统弹出对话框,用户可点击确定或拒绝,系统提供用户的选择回调,从而开发者处理相应的逻辑
- 用户可以选择拒绝并不再提醒
2、对比传统权限声明和运行时权限请求的区别
传统权限声明针对 Android 6.0 及其以下版本使用,Android 6.0对应的API版本23,声明的方式直接将所有应用程序用到的权限统一在 manifest 清单文件中定义,使用标签 ,应用程序点击安装的过程,罗列清单文件声明的所有权限,安装完成后用户可以选择是否授予应用程序某个隐私的权限,Android系统提供:允许、提示和禁止三种选择,下面看一组演示:
build.gradle 选择编译版本、目标版本都是 API 19,运行在Android 4.4.2系统(华为)效果:
build.gradle 选择编译版本、目标版本都是 API 19,运行在Android 6.0.1系统(小米)效果
build.gradle选择编译版本、目标版本都是API 23,运行在Android 4.4.2系统(华为)效果:
build.gradle选择编译版本、目标版本都是API 23,运行在Android 6.0.1系统(小米)效果:
图1演示传统权限授予过程,在完成安装的过程中可以选择某个权限是否允许、提示和禁止状态;
图2演示低版本应用程序在Android 6.0以上系统安装过程,默认授予应用程序清单文件声明的所有权限,小米手机测试无法修改权限状态;
图3和图4演示API版本23开发的应用程序分别安装在低版本和高版本系统权限授予过程,安装在低版本时授予权限过程和传统的方式一样,用户可以修改权限的状态;安装到高版本时授予权限过程发生了很大变化,用户安装过程无法修改权限状态,最后运行应用程序的录音功能,出现闪退、崩溃现象。
3 权限分类
android6.0以后将权限分为两类,分别是危险权限与普通权限
危险权限
危险权限即需要动态申请的权限,一共9组,取得一组中某一个权限的授权,则自动拥有该组的所有授权
一般权限
除上面的危险权限之外的权限即为一般权限(普通权限)
android6.0 运行时权限机制
当编译版本、目标版本都是 API 23及其以上时,安装在Android 6.0系统以上的手机默认授予所有普通权限,不授予危险权限,而危险权限只在程序将要使用这个功能时才进行授予(将会弹出对话框提示用户,用户同意,用户已同意则直接授予)。如果这时没有授予响应权限,系统将会抛出异常。
4 相关API及运行时权限例子
运行时权限主要的API就是以下几个
ContextCompat
//检查是否授予某个权限
static int checkSelfPermission(Context context, String permission)
主要用于检测某个权限是否已经被授予,方法返回值为PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。当返回DENIED就需要进行申请授权了
ActivityCompat
//申请授权
static void requestPermissions(Activity activity, String[] permissions, int requestCode)
该方法是异步的,第一个参数是Context;第二个参数是需要申请的权限的字符串数组;第三个参数为requestCode,主要用于回调的时候检测。可以从方法名requestPermissions以及第二个参数看出,是支持一次性申请多个权限的,系统会通过对话框逐一询问用户是否授权。
Activity
//处理权限申请回调
public void onRequestPermissionsResult(int requestCode,String permissions[], int[] grantResults)
对于权限的申请结果,首先验证requestCode定位到你的申请,然后验证grantResults对应于申请的结果,这里的数组对应于申请时的第二个权限字符串数组。如果你同时申请两个权限,那么grantResults的length就为2,分别记录你两个权限的申请结果。如果申请成功,就可以做你的事情了~
static boolean shouldShowRequestPermissionRationale(Activity activity, String permission)
这个API主要用于给用户一个申请权限的解释,该方法只有在用户在上一次已经拒绝过你的这个权限申请。也就是说,用户已经拒绝一次了,你又弹个授权框,你需要给用户一个解释,为什么要授权,则使用该方法。
一般来说:视频运行时权限步骤如下:
1 在AndroidManifest文件中添加需要的权限
2 在代码中判断是否授予某个权限
3 没有授予权限进行权限的申请
4 对申请权限用户处理后的结果进行处理
例子:
一个打电话的小例子
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;
@Override
protected 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);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
if (requestCode == MY_PERMISSIONS_REQUEST_CALL_PHONE)
{
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
callPhone();
} else
{
// Permission Denied
Toast.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去牌照等
5 运行时权限封装
一般来说我们申请权限的代码如下:
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.XXXX)
!= PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.XXXX},
MY_PERMISSIONS_REQUEST_CODE_XX);
} else
{
xxx();
}
虽然权限处理并不复杂,但是需要编写很多重复的代码,所以目前也有很多库对用法进行了封装,大家可以在github首页搜索:android permission,对比了几个库的使用方式,发现https://github.com/lovedise/PermissionGen这个库还不错,很多大神也在推荐
对于申请权限,我们需要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,我相信上面代码,大家扫一眼就可以了解了。
处理权限回调
对于回调,因为要根据是否授权去执行不同的事情,所以很多框架也需要些一连串的代码,或者和前面的申请代码耦合。不过这个框架还是比较方便的,也是我选择它来讲解的原因。
回调主要做的事情:
了解是否授权成功。
根据授权情况进行回调。
对于第一条逻辑都一样;对于第二条,因为涉及到两个分支,每个分支执行不同的方法。
对于第二条,很多框架选择将两个分支的方法在申请权限的时候进行注册,然后在回调中根据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,到此我们的运行时权限相对于早起版本的变化、特点、以及如何处理和封装都介绍完了。
不过这个库有个缺点,就是运行时注解,对性能有影响
参考:
http://blog.youkuaiyun.com/lmj623565791/article/details/50709663
http://www.jianshu.com/p/d4a9855e92d3
https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650238900&idx=1&sn=6ec8ef71058d5bf81e8fc03a0a4af1d5&chksm=88639edbbf1417cd66d93c549c5537e32e54cf2e068eb6bc8cd0476cd8873c865bf245abe208&mpshare=1&scene=23&srcid=0501hrGGRxXdPlcZQOeUR60i#rd