Google-EasyPermissions

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值