Google-EasyPermissions源码解析

  1. 1. 使用
    1. 1.1. 准备
    2. 1.2. 请求权限
  2. 2. 源码
    1. 2.1. hasPermissions
    2. 2.2. requestPermissions
    3. 2.3. onRequestPermissionsResult
    4. 2.4. runAnnotatedMethods
    5. 2.5. AfterPermissionGranted
  3. 3. 总结
    1. 3.1. 运行时权限
    2. 3.2. 注意

Github: easypermissions 分析版本:962b99d

EasyPermissions 是一个在 Android M 或者更高版本的上使用去简化系统权限逻辑的开源库。

使用

添加依赖到 Gradle :

1
2
3
dependencies {
compile 'pub.devrel:easypermissions:0.1.5'
}

准备

在使用 EasyPermissions 之前,需要在 Activity 或者 Fragment 中实现 EasyPermissions.PermissionCallbacks 接口,并且覆盖以下方法:

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
public class MainActivity extends AppCompatActivity
implements EasyPermissions.PermissionCallbacks {


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);

// 将结果转发给EasyPermissions
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}

@Override
public void onPermissionsGranted(int requestCode, List<String> list) {
// 权限被授予
// ...
}

@Override
public void onPermissionsDenied(int requestCode, List<String> list) {
// 权限被拒绝
// ...
}
}

请求权限

  • 使用 EasyPermissions#hasPermissions(...) 去判断 app 是否已经有权限了。该方法的最后个参数是可变数组形式的,所以可以一次性查询多个权限。

  • 使用 EasyPermissions#requestPermissions 去请求权限。该方法在请求权限的同时有必要的话会显示使用权限理由。requestCode 对于该方法来说必须是唯一的,同时最后个参数也是可变数组形式的,所以可以一次性请求多个权限。

  • 使用 AfterPermissioonGranted 注解。这是可选的,但是提供出来是为了方便。如果所有的请求的权限都被授予了,被注解的方法将会被执行,这样做是为了简化通常的请求权限成功之后再调用方法的流程。同时也可以在 onPermissionsGranted 的回调中添加逻辑操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @AfterPermissionGranted(RC_CAMERA_AND_WIFI)
    private void methodRequiresTwoPermission() {
    String[] perms = {Manifest.permission.CAMERA, Manifest.permission.CHANGE_WIFI_STATE};
    if (EasyPermissions.hasPermissions(this, perms)) {
    // Already have permission, do the thing
    // ...
    } else {
    // Do not have permissions, request them now
    EasyPermissions.requestPermissions(this, getString(R.string.camera_and_wifi_rationale),
    RC_CAMERA_AND_WIFI, perms);
    }
    }

源码

hasPermissions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class EasyPermissions {
/**
* Check if the calling context has a set of permissions.
*
* @param context the calling context.
* @param perms one ore more permissions, such as {@code android.Manifest.permission.CAMERA}.
* @return true if all permissions are already granted, false if at least one permission
* is not yet granted.
*/

public static boolean hasPermissions(Context context, String... perms) {
for (String perm : perms) {
//通过ContextCompat#checkSelfPermission判断
boolean hasPerm = (ContextCompat.checkSelfPermission(context, perm) == PackageManager.PERMISSION_GRANTED);
if (!hasPerm) {
return false;
}
}

return true;
}
}

该方法的作用是判断是否授予了权限,通过 v4 的 ContextCompat#checkSelfPermission 来判断,在返回结果的时候,如果所有的请求的权限都是被授予了的话,就返回 true ,否则返回 false

requestPermissions

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
public class EasyPermissions {
public static void requestPermissions(final Object object, String rationale,
final int requestCode, final String... perms)
{

requestPermissions(object, rationale,
android.R.string.ok,
android.R.string.cancel,
requestCode, perms);
}

/**
* Request a set of permissions, showing rationale if the system requests it.
*
* @param object Activity or Fragment requesting permissions. Should implement
* {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback}
* or
* {@link android.support.v13.app.FragmentCompat.OnRequestPermissionsResultCallback}
* @param rationale a message explaining why the application needs this set of permissions, will
* be displayed if the user rejects the request the first time.
* @param positiveButton custom text for positive button
* @param negativeButton custom text for negative button
* @param requestCode request code to track this request, must be < 256.
* @param perms a set of permissions to be requested.
*/

public static void requestPermissions(final Object object, String rationale, @StringRes int positiveButton, @StringRes int negativeButton, final int requestCode, final String... perms) {
//判断传入参数是否合适
checkCallingObjectSuitability(object);
//拿到PermissionCallbacks对象
final PermissionCallbacks callbacks = (PermissionCallbacks) object;

boolean shouldShowRationale = false;
for (String perm : perms) {
//是否需要显示理由
shouldShowRationale = shouldShowRationale || shouldShowRequestPermissionRationale(object, perm);
}
//如果需要的话,显示dialog进行显示
if (shouldShowRationale) {
AlertDialog dialog = new AlertDialog.Builder(getActivity(object))
.setMessage(rationale)
.setPositiveButton(positiveButton, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//用户同意了,进行系统权限申请操作
executePermissionsRequest(object, perms, requestCode);
}
})
.setNegativeButton(negativeButton, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// act as if the permissions were denied
//没有同意的话,回调出去
callbacks.onPermissionsDenied(requestCode, Arrays.asList(perms));
}
}).create();
dialog.show();
} else {
//不需要显示理由,直接进行权限请求操作
executePermissionsRequest(object, perms, requestCode);
}
}

/**
* 判断传入的对象合适合法,判断规则是传入的object是不是 Fragment 或者 Activity 类,同时是否实现了 PermissionCallbacks
*
* @param object
*/

private static void checkCallingObjectSuitability(Object object) {
// Make sure Object is an Activity or Fragment
if (!((object instanceof Fragment) || (object instanceof Activity))) {
throw new IllegalArgumentException("Caller must be an Activity or a Fragment.");
}

// Make sure Object implements callbacks
if (!(object instanceof PermissionCallbacks)) {
throw new IllegalArgumentException("Caller must implement PermissionCallbacks.");
}
}

/**
* 是否需要显示请求权限理由
*
* @param object
* @param perm
* @return 需要的话返回true,不需要的话返回false
*/

private static boolean shouldShowRequestPermissionRationale(Object object, String perm) {
if (object instanceof Activity) {
return ActivityCompat.shouldShowRequestPermissionRationale((Activity) object, perm);
} else if (object instanceof Fragment) {
return ((Fragment) object).shouldShowRequestPermissionRationale(perm);
} else {
return false;
}
}

/**
* 执行权限请求操作
*
* @param object
* @param perms
* @param requestCode
*/

private static void executePermissionsRequest(Object object, String[] perms, int requestCode) {
//判断传入参数是否合适
checkCallingObjectSuitability(object);

if (object instanceof Activity) {
ActivityCompat.requestPermissions((Activity) object, perms, requestCode);
} else if (object instanceof Fragment) {
((Fragment) object).requestPermissions(perms, requestCode);
}
}

/**
* 拿到Activity对象
*
* @param object
* @return
*/

private static Activity getActivity(Object object) {
if (object instanceof Activity) {
return ((Activity) object);
} else if (object instanceof Fragment) {
return ((Fragment) object).getActivity();
} else {
return null;
}
}
}

requestPermission() 有两个不同参数的实现,区别在于弹出的对话框中的 positive 和 negative 文字是否自定义。传入的 Object 对象得是 Activity 或者 Fragment 并且实现了 android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback 或者 android.support.v13.app.FragmentCompat.OnRequestPermissionsResultCallback。然后通过 shouldShowRequestPermissionRationale 方法去判断是否需要显示请求权限的理由,当申请的权限中有一个需要显示请求权限的话,那么就会弹出 dialog 。如果需要弹出 dialog ,用户取消的话那么直接回调出去,没有取消的话就让系统进行权限的申请。

走到这里 requestPermission() 的任务完成了,那么当用户同意或者不同意授予请求的权限,会进入到 android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback 或者 android.support.v13.app.FragmentCompat.OnRequestPermissionsResultCallback。再回过头看看 使用#准备 中的内容,发现在 OnRequestPermissionsResultCallback 方法中调用了 EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);

onRequestPermissionsResult

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class EasyPermissions {
/**
* Handle the result of a permission request, should be called from the calling Activity's
* {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])}
* method.
* <p/>
* If any permissions were granted or denied, the Activity will receive the appropriate
* callbacks through {@link PermissionCallbacks} and methods annotated with
* {@link AfterPermissionGranted} will be run if appropriate.
*
* @param requestCode requestCode argument to permission result callback.
* @param permissions permissions argument to permission result callback.
* @param grantResults grantResults argument to permission result callback.
* @param object the calling Activity or Fragment.
* @throws IllegalArgumentException if the calling Activity does not implement
* {@link PermissionCallbacks}.
*/

public static void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults, Object object) {
//判断传入参数是否合适
checkCallingObjectSuitability(object);
//合适的话直接强转,不合适抛异常
PermissionCallbacks callbacks = (PermissionCallbacks) object;

// Make a collection of granted and denied permissions from the request.
ArrayList<String> granted = new ArrayList<>();
ArrayList<String> denied = new ArrayList<>();
//判断返回的权限数据,如果权限被授予,添加到granted的List中,没有被授予则添加到denied的List中
for (int i = 0; i < permissions.length; i++) {
String perm = permissions[i];
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
granted.add(perm);
} else {
denied.add(perm);
}
}

// Report granted permissions, if any.
//进行回调
if (!granted.isEmpty()) {
// Notify callbacks
callbacks.onPermissionsGranted(requestCode, granted);
}

// Report denied permissions, if any.
//进行回调
if (!denied.isEmpty()) {
callbacks.onPermissionsDenied(requestCode, denied);
}

// If 100% successful, call annotated methods
//如果所有请求的权限都被授予,则调用被注解的方法
if (!granted.isEmpty() && denied.isEmpty()) {
runAnnotatedMethods(object, requestCode);
}
}
}

onRequestPermissionsResult() 方法处理系统请求权限之后返回的数据,将授予和没有授予的权限通过 PermissionCallbacks 分别回调出去。最后如果请求的权限都被授予的话,则自动去调用被注解了的方法。

runAnnotatedMethods

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 EasyPermissions {
/**
* 通过反射的方式调用被注解了的方法
*
* @param object
* @param requestCode
*/

private static void runAnnotatedMethods(Object object, int requestCode) {
Class clazz = object.getClass();
for (Method method : clazz.getDeclaredMethods()) {
//是否被AfterPermissionGranted注解了的方法
if (method.isAnnotationPresent(AfterPermissionGranted.class)) {
// Check for annotated methods with matching request code.
AfterPermissionGranted ann = method.getAnnotation(AfterPermissionGranted.class);
//requestCode和AfterPermissionGranted注解传入的requestCode相同的话
if (ann.value() == requestCode) {
// Method must be void so that we can invoke it
//必须是没有参数的方法
if (method.getParameterTypes().length > 0) {
throw new RuntimeException("Cannot execute non-void method " + method.getName());
}

try {
// Make method accessible if private
//如果是private的话,设置Accessible
if (!method.isAccessible()) {
method.setAccessible(true);
}
//调用
method.invoke(object);
} catch (IllegalAccessException e) {
Log.e(TAG, "runDefaultMethod:IllegalAccessException", e);
} catch (InvocationTargetException e) {
Log.e(TAG, "runDefaultMethod:InvocationTargetException", e);
}
}
}
}
}
}

runAnnotatedMethods() 方法通过反射方法去调用被注解了的方法,同时这个方法得满足 requestCode 相同且方法没有参数。

AfterPermissionGranted

1
2
3
4
5
6
7
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterPermissionGranted {

int value();

}

这是一个 RUNTIME 的注解,常用使用方式就是通过反射的形式。

总结

EasyPermissions 通过注解的方式巧妙的减少了在成功请求权限之后的操作,减少的步奏是完成获取权限成功之后自动调用被注解的方法。简单的例子就如 demo 中的一样:

1
2
3
4
5
6
7
8
9
10
@AfterPermissionGranted(RC_CAMERA_PERM)
public void cameraTask() {
if (EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA)) {
// Have permission, do the thing!
Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show();
} else {
// Ask for one permission
EasyPermissions.requestPermissions(this, getString(R.string.rationale_camera), RC_CAMERA_PERM, Manifest.permission.CAMERA);
}
}

没有权限的情况下请求权限,请求完之后如果成功则又自动进入这个方法,进行 Toast 操作。

运行时权限

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
41
42
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS

group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP
permission:android.permission.PROCESS_OUTGOING_CALLS
permission:com.android.voicemail.permission.ADD_VOICEMAIL

group:android.permission-group.CALENDAR
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR

group:android.permission-group.CAMERA
permission:android.permission.CAMERA

group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS

group:android.permission-group.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION
permission:android.permission.ACCESS_COARSE_LOCATION

group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE

group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO

group:android.permission-group.SMS
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS

同时也可以通过 adb shell pm list permissions -d -g 进行查看。

运行权限也分为了一组一组的,同时申请权限的时候也是按组来申请的,也就是说 app 对 READ_CONTACTS 已经授权了,当你的 app 申请 WRITE_CONTACTS 时,系统会直接授权通过

注意

EasyPermissions 提供的 Fragment 是 v4 包的,如果要使用 android.app.Fragment 的话就需要自己添加了。