这可能是最精简的 Android6.0 运行时权限处理,70 行代码的工具类。附: 各种权限详细处理

3,129 阅读5分钟
原文链接: www.jianshu.com

0x00:前言

对于Android6.0运行时权限的处理方式网上有很多,包括注解,RxJava等等。一直没有正面提到我关心的问题--如果我不在Activity或者Fragment里面,需要运行时权限该怎么去做?导致我开始一直以为运行时权限的处理必需要在Activity或者Fragment之中。

那么:
我有一个录音的自定义控件在很多页面需要使用怎么办?
我有一个联系人列表,要在adapter里面拨打电话怎么办?
我有一个定位的工具类要在多个页面使用怎么办?
等等...
之前我还问过一些同行,他说用回调,回调到Activity或者Fragment,我当时觉得是一种解决方案,但是却很麻烦,如果有多个页面使用,那不是要处理很多次。

直到某一天在github上看到一个分享了简单的工具类MPermissionUtils ,一下子解决了我的疑惑,虽然他也没有明确给出答案,但是我从他的使用上却恍然大悟,原来是一开始我就理解错了。我们只需要把系统回调方法onRequestPermissionsResult放到BaseActivity里面,将处理结果通过工具类调出来,加一个自定义的回调到请求的发起处即可。

因为你要用到运行时权限的地方总要依赖于Activity的存在,如果不再Activity里面或者当前代码获取不到Activity,那就传过去,一切的处理结果都会回到你发起请求所在的Activity。

那么一不做二不休,我们这时候有没有考虑Fragment里面的处理其实是多余的,我们可不可以都放到Activity里面来处理。于是就化繁为简产生了我的XPermissionUtils

0x01:代码实现

public class XPermissionUtils {

    private static int mRequestCode = -1;

    private static OnPermissionListener mOnPermissionListener;

    public interface OnPermissionListener {

        void onPermissionGranted();

        void onPermissionDenied();
    }

    @TargetApi(Build.VERSION_CODES.M)
    public static void requestPermissions(Context context, int requestCode
            , String[] permissions, OnPermissionListener listener) {
        if (context instanceof Activity) {
            mOnPermissionListener = listener;
            List<String> deniedPermissions = getDeniedPermissions(context, permissions);
            if (deniedPermissions.size() > 0) {
                mRequestCode = requestCode;
                ((Activity) context).requestPermissions(deniedPermissions
                        .toArray(new String[deniedPermissions.size()]), requestCode);

            } else {
                if (mOnPermissionListener != null)
                    mOnPermissionListener.onPermissionGranted();
            }
        } else {
            throw new RuntimeException("Context must be an Activity");
        }
    }

    /**
     * 获取请求权限中需要授权的权限
     */
    private static List<String> getDeniedPermissions(Context context, String... permissions) {
        List<String> deniedPermissions = new ArrayList<>();
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED) {
                deniedPermissions.add(permission);
            }
        }
        return deniedPermissions;
    }

    /**
     * 请求权限结果,对应Activity中onRequestPermissionsResult()方法。
     */
    public static void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (mRequestCode != -1 && requestCode == mRequestCode) {
            if (mOnPermissionListener != null) {
                if (verifyPermissions(grantResults)) {
                    mOnPermissionListener.onPermissionGranted();
                } else {
                    mOnPermissionListener.onPermissionDenied();
                }
            }
        }
    }

    /**
     * 验证所有权限是否都已经授权
     */
    private static boolean verifyPermissions(int[] grantResults) {
        for (int grantResult : grantResults) {
            if (grantResult != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }
}

0x02:使用方式

以拨打电话为例

1、首先AndroidManifest中配置必要的权限

<uses-permission android:name="android.permission.CALL_PHONE"/>

2、在基类中加上回调方法

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

3、调用工具类方法

XPermissionUtils.requestPermissions(Context context, int requestCode, String[] permissions, OnPermissionListener listener)

这里主要注意这个Context必需是一个Activity
如果在Activity中可以传this;
如果在Fragment中传getActivity();
如果在View中传getContext();
等等.....

    private void doCallPhone() {
        XPermissionUtils.requestPermissions(this, 1, new String[]{Manifest.permission
                .CALL_PHONE}, new XPermissionUtils.OnPermissionListener() {
            @Override
            public void onPermissionGranted() {
                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:10010"));
                startActivity(intent);
            }

            @Override
            public void onPermissionDenied() {
                //弹出权限被禁用的提示框
            }
        });
    }

0x03:各种运行时权限处理详谈

其实在6.0之前已经存在运行时权限,只不过没有明确提出这个概念,在6.0之前,获取位置、读取通讯录、拍照、录音等都是需要在操作的时候去获取权限的。那么这些权限的区别是6.0以后需要我们去写请求获取权限的代码,而之前是当代码执行到需要权限的地方就会弹出提示框。
那么针对不同的权限可能有不同的处理方式,下面简单列举,如果需要看代码可以在源码的Demo中查看

1、拨打电话

拨打电话其实如果不是产品要求直接拨出去可以使用调转到拨号页面实现的,这个不需要权限:

 Intent intent = new Intent(Intent.ACTION_DIAL);
        Uri data = Uri.parse("tel:10010");
        intent.setData(data);
        startActivity(intent);

2、录音

(1)录音权限在6.0之前是无法判断是否获取权限的,只能通过非常规的方法获取,详见项目Demo
(2)长按按钮录音,在第一次获取权限的时候需要特殊处理,弹出获取权限的提示框之后手指已经离开,不能进行录音的操作。

3、打开相机

相机权限在6.0之前同样也是无法判断是否获取权限的,只能通过非常规的方法获取,详见项目Demo

4、获取位置

(1)首先手机需要开启位置服务,如果没有开启,那么即使app开启获取位置权限也是获取不到的
(2)(使用系统Api,有时候小功能不需要集成百度,高德)要注意在室内如果选择Gps定位会获取不到位置,这里可以参考Demo中LocationUtils的实现思路。

5、获取外部存储

这个在有些手机上比较特殊,比如小米就不需要运行时权限,华为就需要,这个还是需要在使用的时候主动请求一下。

0x04 特别鸣谢

MPermissionUtils
PermissionGen

如有不足,欢迎指正。最后附上源码地址
XPermissionUtils