Android6.0运行时权限库-EasyPermissions的使用与源码解析

Android6.0采用运行时权限,并不像以前在安装时把应用申请的所有权限都开通了。由于权限是可以在任何时候随意关闭,所以我们在进行需要权限的操作时都需要先检查是否有权限。当然在吃上棒棒糖之前,国产ROMs在6.0之前就加上了运行时权限,但那个不需要开发者额外适配。但是在Android6.0上确实需要实时检测权限,googlesamples上有一个项目EasyPermissions,就是来处理运行时权限的。
首先介绍一下EasyPermissions的使用,在CodeBlog中我在EasyPermissions上又封装了一层PermissionUtil

// 请求权限
if (PermissionUtil.checkInitPermission(BaseActivity.getTopActivity())) {
    jumpMainActivityDeLay(2000);
}

我是放在开屏页面调用的,就是在应用启动时,先检查一些必要的权限,Manifest.permission.READ_PHONE_STATE在国内的设备上好像是默认已经开启了的,但还是需要在启动时确认一下,否则默认没开启或者手动关闭这个权限后回导致crash(一般的应用基本都需要读取设备标识码,特别是一些第三方统计)。

    public static boolean checkInitPermission(Activity activity) {
        // SDK 小于23默认已经授权
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return true;
        }

        String[] perms = {Manifest.permission.READ_PHONE_STATE, Manifest.permission.WRITE_EXTERNAL_STORAGE};

        // 手机状态和写SDCARD的权限是必须的
        if (EasyPermissions.hasPermissions(activity, perms)) {
            return true;
        } else {
            EasyPermissions.requestPermissions(activity, "需要存储数据到设备!", PERMISSION_REQUEST_CODE_INIT, perms);
            return false;
        }
    }

在activity中覆写onRequestPermissionsResult

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

        // Forward results to EasyPermissions
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

可以看到,权限请求结果回调直接丢给了EasyPermissions,我们直接处理EasyPermissions的回调:

    @Override
    public void onPermissionsGranted(int requestCode, List perms) {
        // 请求权限
        if (PermissionUtil.checkInitPermission(this)) {
            jumpMainActivityDeLay(2000);
        }
    }

    @Override
    public void onPermissionsDenied(int requestCode, List perms) {
        if (requestCode == PermissionUtil.PERMISSION_REQUEST_CODE_INIT) {
            // (Optional) Check whether the user denied any permissions and checked "NEVER ASK AGAIN."
            // This will display a dialog directing them to enable the permission in app settings.
            if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
                PermissionUtil.showPermissionDetail(this, "应用必须权限", true);
//                new AppSettingsDialog.Builder(this, "应用必须权限")
//                        .setTitle("权限设置")
//                        .setPositiveButton(getString(R.string.setting))
//                        .setNegativeButton(getString(R.string.cancel), null /* click listener */)
//                        .setRequestCode(PermissionUtil.PERMISSION_REQUEST_CODE_INIT)
//                        .build()
//                        .show();
            } else {
                PermissionUtil.checkInitPermission(this);
            }
        }
    }

当然这里也可以直接在系统的onRequestPermissionsResult中处理。


下面就学习一下EasyPermissions的源码,下图为EasyPermissions的文件结构,主要的逻辑只有一个EasyPermissions.java


Android6.0运行时权限库-EasyPermissions的使用与源码解析_第1张图片

先从上面使用到的EasyPermissions.hasPermissions(activity, perms)开始:

    /**
     * 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) {
        // Always return true for SDK < M, let the system deal with the permissions
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            Log.w(TAG, "hasPermissions: API version < M, returning true by default");
            return true;
        }

        for (String perm : perms) {
            boolean hasPerm = (ContextCompat.checkSelfPermission(context, perm) ==
                    PackageManager.PERMISSION_GRANTED);
            if (!hasPerm) {
                return false;
            }
        }

        return true;
    }

可以看到,SDK版本在Build.VERSION_CODES.M(SDK23)以下是不需要做权限请求的,在国产ROM上也不必单独处理。
判断权限时用一个for循环分别判断权限,只要有一个权限没有被授予,则返回false,也就是需要请求权限。
请求权限时,也是直接把数组传进去,也不需要关心具体是哪个权限没有被授予,因为Android系统提供的权限请求接口也是酱紫处理的,数组列表已经被授予的权限不会再弹授权提示。

    @TargetApi(23)
    private static void executePermissionsRequest(Object object, String[] perms, int requestCode) {
        checkCallingObjectSuitability(object); // 检测是否为Activity 或 Fragment, 不是的话直接抛运行时异常

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

这里已经把权限请求发送到系统提供的接口了,系统处理完权限请求后(弹窗提示用户授权)会调用Activity.onRequestPermissionsResult(...),上面我们直接把回调的参数完整的传递给EasyPermissions.onRequestPermissionsResult(...)

    public static void onRequestPermissionsResult(int requestCode, String[] permissions,
                                                  int[] grantResults, Object object) {

        checkCallingObjectSuitability(object);

        // Make a collection of granted and denied permissions from the request.
        ArrayList granted = new ArrayList<>();
        ArrayList denied = new ArrayList<>();
        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
            if (object instanceof PermissionCallbacks) {
                ((PermissionCallbacks) object).onPermissionsGranted(requestCode, granted);
            }
        }

        // Report denied permissions, if any.
        if (!denied.isEmpty()) {
            if (object instanceof PermissionCallbacks) {
                ((PermissionCallbacks) object).onPermissionsDenied(requestCode, denied);
            }
        }

        // If 100% successful, call annotated methods
        if (!granted.isEmpty() && denied.isEmpty()) {
            runAnnotatedMethods(object, requestCode);
        }
    }

英文注释很请求,并且强调了,只要权限请求列表中有被授权的,则会回调PermissionCallbacks.onPermissionsGranted(requestCode, granted);只要有没有被授权的,则会回调PermissionCallbacks.onPermissionsDenied(requestCode, granted)。只有请求列表中的所有权限被授予,才会调用注解(@AfterPermissionGranted(PERMISSION_REQUEST_CODE_INIT))的方法。

再看回调注解的地方:

    private static void runAnnotatedMethods(Object object, int requestCode) {
        Class clazz = object.getClass();
        if (isUsingAndroidAnnotations(object)) {
            clazz = clazz.getSuperclass();
        }
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(AfterPermissionGranted.class)) {
                // Check for annotated methods with matching request code.
                AfterPermissionGranted ann = method.getAnnotation(AfterPermissionGranted.class);
                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
                        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);
                    }
                }
            }
        }
    }

注释中说明了注解的方法不能有返回值,否则会抛异常。


还有一个需要注意的地方,在请求权限的弹窗中有一个“禁止后不再询问”选项,若勾选了这个并禁止了该权限,则以后请求该权限时就不会弹出授权提示窗了。所以在授权被拒绝时需要判断是否被永久禁止了。

Android6.0运行时权限库-EasyPermissions的使用与源码解析_第2张图片

在EasyPermissions的demo中有给出解决办法,就是在权限被永久禁止后弹窗提示到设置的应用详情页面进行权限设置:

// AppSettingsDialog.java

        // Positive click listener, launches app screen
        dialogBuilder.setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // Create app settings intent
                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                Uri uri = Uri.fromParts("package", context.getPackageName(), null);
                intent.setData(uri);

                // Start for result
                startForResult(activityOrFragment, intent, settingsRequestCode);
            }
        });

系统也提供了检查权限是否被永久禁用的接口,EasyPermissions也是进行了封装:

    @TargetApi(23)
    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 if (object instanceof android.app.Fragment) {
            return ((android.app.Fragment) object).shouldShowRequestPermissionRationale(perm);
        } else {
            return false;
        }
    }

下图为EasyPermissionsd的outline:


Android6.0运行时权限库-EasyPermissions的使用与源码解析_第3张图片

方法很少,代码也不多。本来运行时权限的处理也不难,只是使用EasyPermissions会更省事一些。

EasyPermissions就介绍到这里啦!


CodeBlog是我做的一个编程技术学习客户端,集成了很多技术网站上的博客,应用宝详情页

你可能感兴趣的:(Android6.0运行时权限库-EasyPermissions的使用与源码解析)