从6.0 MarshMallow开始,Android支持动态权限管理,即有些权限须要在使用到的时候动态申请,根据用户的选择须要有不一样的处理,具体表现能够看下图:javascript
本文并不关心权限适配的原理,原理能够参考Android权限管理原理 ,这里只是针对6.0中的表现作适配,先思考如下几个问题:html
6.0以前Android的权限都是在安装的时候授予的,6.0以后,为了简化安装流程,而且方便用户控制权限,Android容许在运行的时候动态控制权限。对于开发而言就是将targetSdkVersion设置为23,当运行在Android 6.0 +的手机上时,就会调用6.0相关的API,达到动态控制权限的目的。可是,若是仅仅是将targetSdkVersion设置为23,而在代码层面没有针对Android 6.0作适配,就可能在申请系统服务的时候,因为权限不足,引起崩溃。java
并不是全部的权限都须要动态申请,Android6.0将权限分为两种,普通权限跟敏感(危险)权限,普通权限是不须要动态申请的,可是敏感权限须要动态申请。android
一、普通权限(Normal permissions):不会泄露用户隐私,同时也不会致使手机安全问题。如网络请求权限、WIFI状态等,这类权限只须要在Manifest列出来,以后,系统会自动赋给APP权限:git
二、敏感权限(Dangerous permissions):与普通权限对应,可能会影响用户的隐私,存储数据等,好比拍照、存储、通信录、地理GPS等,这类权限须要在Manifest列出来,在须要的的时候,显示的请求用户准许。github
敏感权限的请求是按照分组进行提醒的,并不是仅仅针对一条,好比通信录的读取权限与写权限,只要一个权限获到,下次请求权限的时候会自动提供,固然也要请求。不然仍是有问题。安全
三、特殊权限(Special Permissions) --不在本文分析范围网络
There are a couple of permissions that don't behave like normal and dangerous permissions. SYSTEM_ALERT_WINDOW and WRITE_SETTINGS app
对于敏感权限的适配有一个原则,那就是实时检查,由于权限随时可能被回收,好比用户能够在设置里面把权限给取消,可是APP并不必定知道,所以每次都须要检查,一旦没有,就须要请求,以后,根据返回结果处理后续逻辑。ide
一、在Manifest中列出来
不管普通权限仍是敏感权限,都须要在Manifest中列出来,同时也是对6.0以前的版本的一种兼容。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.snail.labaffinity">
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>复制代码
在权限没被授予前提下,系统会显示受权对话框,让用户操做,目前受权对话框不可定制,不过能够在申请以前添加一些解释,告诉用户为何须要该权限,可是Google提醒,不要作过多的解释,可能会使用户感到厌烦,具体就是使用ActivityCompat的requestPermissions函数:
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);
}
});
}
}复制代码
三、处理受权回调
须要对6.0的受权成功、失败、永不询问作处理
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(this.mOnGrantedListener != null) {
<!--6.0以前-->
if(PermissionUtils.getTargetSdkVersion(this) < 23 && !PermissionUtils.hasSelfPermissions(this, permissions)) {
this.mOnGrantedListener.onGranted(this, permissions);
return;
}
//<!--6.0以后--> 须要根据结果进行验证
if(PermissionUtils.verifyPermissions(grantResults)) {
this.mOnGrantedListener.onGranted(this, permissions);
} else if(!PermissionUtils.shouldShowRequestPermissionRationale(this, permissions)) {
this.mOnGrantedListener.onNeverAsk(this, permissions);
} else {
this.mOnGrantedListener.onDenied(this, permissions);
}
}复制代码
}
先看一下直接回调的方式
首先在基类Activity或者Fragment中统一设置受权回调监听,这里咱们用一个
public class BasePermissionCompatActivity extends AppCompatActivity {
private SparseArray<OnGrantedListener<BasePermissionCompatActivity>> mOnGrantedListeners = new SparseArray<>();
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
OnGrantedListener<BasePermissionCompatActivity> listener = mOnGrantedListeners.get(requestCode);
if (listener == null)
return;
if (PermissionUtils.verifyPermissions(grantResults)) {
listener.onGranted(this, permissions);
} else {
if (PermissionUtils.shouldShowRequestPermissionRationale(this, permissions)) {
listener.onDenied(this, permissions);
} else {
listener.onNeverAsk(this, permissions);
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mOnGrantedListeners.clear();
mOnGrantedListeners = null;
}
public void requestPermissions(final @NonNull String[] permissions, OnGrantedListener<BasePermissionCompatActivity> onGrantedListener) {
int requestCode = getNextRequestCode();
ActivityCompat.requestPermissions(this, permissions, requestCode);
mOnGrantedListeners.put(requestCode, onGrantedListener);
}
private static int sNextCode;
private static int getNextRequestCode() {
return sNextCode++;
}
}复制代码
以后在须要时候的请求,并根据结果处理后续逻辑便可。
requestPermissions(activity, P_CAMERA, new OnGrantedListener() {
// 根据permissions自行处理,可合并,可分开
@Override
public void onGranted(SecondActivity target, String[] permissions,int requestCode) {
}
@Override
public void onDenied(SecondActivity target, String[] permissions,int requestCode) {
}
@Override
public void onNeverAsk(SecondActivity target, String[] permissions,int requestCode) {
}
@Override
public void onShowRationale(SecondActivity target, String[] permissions,int requestCode) {
});复制代码
上面的方法比较直接,灵活,不过每次都要本身实现回调监听Listener,接下来看第二种实现,基于APT,经过注解的方式,自动添加Listener,这种实现参考了ButterKnife的实现方式。
相应的实现分三个库:
主要用来定义一些回调方法注解、及请求实体的类注解
* ActivityPermission
* FragmentPermission
* OnDenied
* OnGranted
* OnGrantedListener
* OnNeverAsk
* OnShowRationale复制代码
主要用来在编译阶段,动态生Listener类
PermissionProcessor.java复制代码
部分参考代码:
@AutoService(Processor.class)
public class PermissionProcessor extends AbstractProcessor {
private Elements elementUtils;
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(OnDenied.class);
annotations.add(OnGranted.class);
annotations.add(OnNeverAsk.class);
annotations.add(OnShowRationale.class);
return annotations;
}
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!checkIntegrity(roundEnv))
return false;
Set<? extends Element> elementActivities = roundEnv.getElementsAnnotatedWith(ActivityPermission.class);
Set<? extends Element> elementFragments = roundEnv.getElementsAnnotatedWith(FragmentPermission.class);
return makeListenerJavaFile(elementActivities) && makeListenerJavaFile(elementFragments);
}
...复制代码
主要会封装了一些工具类,基类以及对回调的处理
* BasePermissionCompatActivity.java
* BasePermissionCompatFragment.java
* PermissionCompat.java
* PermissionUtils.java复制代码
参考代码:
public class PermissionCompat {
private static int sNextRequestCode;
static final Map<Class<?>, OnGrantedListener> BINDERS = new LinkedHashMap<>();
// 分批次请求权限
public static void requestPermission(BasePermissionCompatActivity target, String[] permissions) {
Class<?> targetClass = target.getClass();
try {
// 找到监听Listener类,并实例一个
OnGrantedListener<BasePermissionCompatActivity> listener = findOnGrantedListenerForClass(targetClass, permissions);
if (PermissionUtils.hasSelfPermissions(target, permissions)) {
listener.onGranted(target, permissions);
} else if (PermissionUtils.shouldShowRequestPermissionRationale(target, permissions)) {
// 拒绝过,再次请求的时候,这个函数是否有必要,不在询问后,返回false,第一次返回false,
//listener.onShowRationale(target, permissions);
startRequest(target, listener, permissions);
} else {
startRequest(target, listener, permissions);
}
} catch (Exception e) {
throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
}
}
private static void startRequest(BasePermissionCompatActivity target, OnGrantedListener listener, final @NonNull String[] permissions) {
target.setOnGrantedListener(listener);
ActivityCompat.requestPermissions(target, permissions, getNextRequestCode());
}复制代码
四、请求的权限必须有回调函数,否则报运行时错误--崩溃
@ActivityPermission
public class PermssionActivity extends BasePermissionCompatActivity {
。。。
@OnGranted(value = {Manifest.permission.CAMERA})
void granted() {
LogUtils.v("granted");
}
@OnDenied(value = {Manifest.permission.CAMERA})
void onDenied() {
LogUtils.v("onDenied");
}
@OnNeverAsk(value = {Manifest.permission.CAMERA})
void OnNeverAsk() {
LogUtils.v("OnNeverAsk");
}
@OnShowRationale(value = {Manifest.permission.CAMERA})
void OnShowRationale() {
LogUtils.v("OnShowRationale");
}
<!--什么时候的时机调用-->
@OnClick(R.id.get)
void get() {
PermissionCompat.requestPermission(this, new String[]{Manifest.permission.CAMERA});
}
}复制代码
6.0以前权限管理即不是原生功能又没有制定相应标准,每一个厂家的实现都是彻底不一样的,虽然4.3 Google官方试图推出AppOpsManager来动态适配权限管理,但因为不成熟,一直到6.0也没走向前台。不过,看6.0以前国内ROM的表现,基本是在每一个服务内部触发鉴权请求,对原生权限的判断并没多大影响,因此兼容没太大问题。
最后附上GitHub Demo及第三方库连接 权限兼容库 PermissionCompat
一、Requesting Permissions at Run Time
二、PermissionDispatcher
三、Android6.0权限适配之WRITE_EXTERNAL_STORAGE(SD卡写入)