Github: easypermissions 分析版本: 962b99d
EasyPermissions 是一个在 Android M 或者更高版本的上使用去简化系统权限逻辑的开源库。
使用
添加依赖到 Gradle
:
dependencies { compile 'pub.devrel:easypermissions:0.1.5' }
准备
在使用 EasyPermissions 之前,需要在 Activity
或者 Fragment
中实现EasyPermissions.PermissionCallbacks
接口,并且覆盖以下方法:
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
的回调中添加逻辑操作:@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
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
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
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
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
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterPermissionGranted {
int value();
}
这是一个 RUNTIME
的注解,常用使用方式就是通过反射的形式。
总结
EasyPermissions
通过注解的方式巧妙的减少了在成功请求权限之后的操作,减少的步奏是完成获取权限成功之后自动调用被注解的方法。简单的例子就如 demo 中的一样:
@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 操作。
运行时权限
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
的话就需要自己添加了。