Android6.0如何进行权限适配

Android6.0如何进行权限适配?

本篇仅讨论如何使用,至于原理请看《Android6.0权限管理原理》

背景

在Android6.0之前Android的权限都是在安装的时候授予的,6.0之后,为了简化安装流程,并且方便用户控制权限,改成在使用时动态申请。当设置targetSdkVersion为23时,运行在Android6.0+的手机,就会调用6.0相关的API,达到动态控制权限的目的,但是如果没有在代码层面对于Android6.0权限

什么权限需要动态适配?

Android将权限分为两种:

  • 普通权限
  • 敏感(危险)权限

普通权限

这类权限不会泄露用户隐私,同时也不会导致手机安全问题,比如网络请求权限、WIFI状态等。

  • ACCESS_NETWORK_STATE
  • BLUETOOTH
  • ACCESS_WIFI_STATE

敏感权限

这类权限与普通权限对应,可能会影响用户的隐私,存储数据等,比如拍照、存储、通讯录、地理GPS等,这类权限需要在Manifest中列出来。

  • CALENDAR
  • CAMERA
  • CONTACTS
  • LOCATION
  • PHONE

特殊权限

例如系统弹窗等等

  • SYSTEM_ALERT_WINDOW
  • WRITE_SETTINGS

怎么动态适配权限?

对于敏感权限有一个原则,每次需要权限时都要检查,因为权限可能随时被回收,但是APP不一定知道,所以每次都需要检查,一旦没有,就需要根据返回结果处理后续逻辑。

实现步骤

    1. 在Manifest中列出来

      不管是什么权限,都需要在Manifest中列出来,这样同时也是对6.0之前版本的一种兼容。

例如:

1
2
3
4
<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"/>
    1. 需要时,显示的请求

      在权限没被授予前提下,系统会显示授权对话框,让用户操作,目前授权对话框不可定制,不过可以在申请之前添加一些解释,告诉用户为什么需要该权限,但是Google提醒,不要做过多的解释,可能会使用户感到厌烦。

permissions, getNextRequestCode());
1
2
3
   。。
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final int requestCode) {
    1. 处理授权回调

      • 兼容6.0之前的处理:在6.0之前只存在install权限,一旦安装,所有权限都是授予,只需要处理授权成功即可。
      • 需要对6.0的授权成功、失败、永不询问做处理。

具体做法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (this.mOnGrantedListener != null) {
if (PermissionUtils.getTargetSdkVersion(this) < 23 && !PermissionUtils.hasSelfPermissions(this, permissions)) {
this.mOnGrantedListener.onGranted(this, permissions);
//兼容之前的处理方式
return;
}
// 需要根据结果进行验证 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);
}

如何在App中对于权限进行处理?

    1. 简单的封装回调
  • 2.基于APT,采用注解方式简化编码逻辑,自动封装回调

采用最简单也是最直接的回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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++;
}
}

在用的时候,根据结果处理后续的逻辑请求即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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) {
});

基于APT注解,在编译过程中生成代码,自动添加回调

  1. 基于APT,定义一系列的Annotation,并动态生成辅助Listener类
  2. 添加Android支持库,在基类统一处理回调
  3. 添加工具类,连接绑定Listener与Activity(fragment)

APT生成支持库

1
PermissionProcessor.java

部分参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@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);
}
...

Android支持库

1
2
3
4
* BasePermissionCompatActivity.java
* BasePermissionCompatFragment.java
* PermissionCompat.java
* PermissionUtils.java

参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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());
}

使用

  • 1、Activity继承BasePermissionCompatActivity
  • 2、用注解写回调函数,支持权限分组,跟单独处理,但是每个分组都要写自己的回调函数(目前回调函数,不支持参数)
  • 3、回调必需配套,也就是一个权限必须对应四个函数,否则编译不通过
  • 4、请求的权限必须有回调函数,不然报运行时错误–崩溃
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@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});
}
}

详见权限兼容库PermissionCompat