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<String> perms) {
        // 请求权限
        if (PermissionUtil.checkInitPermission(this)) {
            jumpMainActivityDeLay(2000);
        }
    }

    @Override
    public void onPermissionsDenied(int requestCode, List<String> 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


先从上面使用到的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<String> granted = new ArrayList<>();
        ArrayList<String> 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);
                    }
                }
            }
        }
    }

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


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


在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:



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

EasyPermissions就介绍到这里啦!


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355

推荐阅读更多精彩内容