神刀安全网

04-24

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 的话就需要自己添加了。

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 04-24

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址