Android使用PermissionCompatDelegate优化权限申请框架

背景

Android 6.0以后就需要动态申请权限了,动态申请权限需要调用requestPermissions() 方法,然后在Activity/Fragment的onRequestPermissionsResult方法接收权限申请结果。这看似很简单,但是当我们在开发中使用一些设计模式时就会导致接收权限的逻辑复杂化。于是就出现了一些开源框架来优化这个权限的申请流程。

开源框架分析

经过笔者分析,开源权限申请框架核心原理分为以下两类:

  1. 需要使用者重新申请权限所在的Activty/Fragment的onRequestPermissionsResult方法把结果传回到权限框架内,代表框架:Grant
  2. 新启动一个透明的Activty/Fragment申请权限,在新启动的Activty/Fragment的onRequestPermissionsResult中把结果传回到权限框架内,代表框架:RxPermissions

下面我们就来分析一下这两个开源框架以及他们的优缺点。

Grant

开源地址:Grant
Grant仅有3个类Permissions、PermissionsManager、PermissionsResultAction。Permissions类定义了常量,PermissionsResultAction为结果回调接口,PermissionsManager类为核心实现类。在Android 6.0以上版本使用了ActivityCompat.requestPermissions实现申请权限,代码如下:

    public synchronized void requestPermissionsIfNecessaryForResult(@Nullable Activity activity,
                                                                    @NonNull String[] permissions,
                                                                    @Nullable PermissionsResultAction action) {
        if (activity == null) {
            return;
        }
        addPendingAction(permissions, action);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            doPermissionWorkBeforeAndroidM(activity, permissions, action);
        } else {
            List permList = getPermissionsListToRequest(activity, permissions, action);
            if (permList.isEmpty()) {
                //if there is no permission to request, there is no reason to keep the action int the list
                removePendingAction(action);
            } else {
                String[] permsToRequest = permList.toArray(new String[permList.size()]);
                mPendingRequests.addAll(permList);
                ActivityCompat.requestPermissions(activity, permsToRequest, 1);
            }
        }
    }

使用Grant需要重新onRequestPermissionsResult方法,调用PermissionsManager的notifyPermissionsChange方法把结果传给PermissionsManager,notifyPermissionsChange方法实现如下:

    public synchronized void notifyPermissionsChange(@NonNull String[] permissions, @NonNull int[] results) {
        int size = permissions.length;
        if (results.length < size) {
            size = results.length;
        }
        Iterator> iterator = mPendingActions.iterator();
        while (iterator.hasNext()) {
            PermissionsResultAction action = iterator.next().get();
            for (int n = 0; n < size; n++) {
                if (action == null || action.onResult(permissions[n], results[n])) {
                    iterator.remove();
                    break;
                }
            }
        }
        for (int n = 0; n < size; n++) {
            mPendingRequests.remove(permissions[n]);
        }
    }

RxPermissions

开源地址:RxPermissions
RxPermissions是基于RxJava开发的用于帮助在 Android 6.0 中处理运行时权限检测的框架。RxPermissions也是由3个类实现:Permission、RxPermissions、RxPermissionsFragment。Permission类对权限进行了一个包装,RxPermissionsFragment类为具体进行权限申请的Fragment,RxPermissions为核心入口类,那么我们就从RxPermissions来分析一下他的核心实现。
首先看构造方法,在使用时构造方法一定不能用错,Activity与Fragment使用不同的构造方法,代码如下:

    Lazy mRxPermissionsFragment;
    
    public RxPermissions(@NonNull final FragmentActivity activity) {
        mRxPermissionsFragment = getLazySingleton(activity.getSupportFragmentManager());
    }

    public RxPermissions(@NonNull final Fragment fragment) {
        mRxPermissionsFragment = getLazySingleton(fragment.getChildFragmentManager());
    }

使用懒加载构造了一个RxPermissionsFragment实例。我们看一下这个懒加载是如何进行初始化的:

    private RxPermissionsFragment getRxPermissionsFragment(@NonNull final FragmentManager fragmentManager) {
        RxPermissionsFragment rxPermissionsFragment = findRxPermissionsFragment(fragmentManager);
        boolean isNewInstance = rxPermissionsFragment == null;
        if (isNewInstance) {
            rxPermissionsFragment = new RxPermissionsFragment();
            fragmentManager
                    .beginTransaction()
                    .add(rxPermissionsFragment, TAG)
                    .commitNow();
        }
        return rxPermissionsFragment;
    }

这里就构造了一个RxPermissionsFragment实例,再通过Fragment的requestPermissions方法去申请权限。因为是基于RxJava包装了Observable,代码比较多久不再赘述,我们直接看权限的接收吧,权限的接收是在RxPermissionsFragment的onRequestPermissionsResult进行接收,并通过PublishSubject分发出去。

    void onRequestPermissionsResult(String permissions[], int[] grantResults, boolean[] shouldShowRequestPermissionRationale) {
        for (int i = 0, size = permissions.length; i < size; i++) {
            log("onRequestPermissionsResult  " + permissions[i]);
            // Find the corresponding subject
            PublishSubject subject = mSubjects.get(permissions[i]);
            if (subject == null) {
                // No subject found
                Log.e(RxPermissions.TAG, "RxPermissions.onRequestPermissionsResult invoked but didn't find the corresponding permission request.");
                return;
            }
            mSubjects.remove(permissions[i]);
            boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
            subject.onNext(new Permission(permissions[i], granted, shouldShowRequestPermissionRationale[i]));
            subject.onComplete();
        }
    }

对比

Grant RxPermissions
优点 代码逻辑简单易读 无需重写onRequestPermissionsResult
缺点 需要重写onRequestPermissionsResult方法,易遗漏出错 注意事项较多:如构造方法不能用错;初始化请求权限需要在Activity.onCreate, 或 View.onFinishInflate方法中,不能在pause或者onResume方法中请求。添加Fragment导致性能没有Grant好

那么有没有一种方式即不需要重写onRequestPermissionsResult又不需要添加隐藏的Activity/Fragment呢?
答案就是:PermissionCompatDelegate

PermissionCompatDelegate

发现PermissionCompatDelegate是一个偶然,笔者在阅读FragmentActivity源码时发现以下代码:

    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        ...
        ActivityCompat.PermissionCompatDelegate delegate =
                ActivityCompat.getPermissionCompatDelegate();
        if (delegate != null && delegate.onActivityResult(this, requestCode, resultCode, data)) {
            // Delegate has handled the activity result
            return;
        }

        super.onActivityResult(requestCode, resultCode, data);
    }

简介

PermissionCompatDelegate是什么东西?这里还调用了PermissionCompatDelegate.onActivityResult方法,如果返回true就不再调用super.onActivityResult了。笔者Google百度了一下PermissionCompatDelegate的资料极少,仅找到了官方文档。
PermissionCompatDelegate官方定义为:可自定义的委托,允许将权限兼容性方法委托给自定义实现。意思就是我们可以自己定义权限申请和接收的方法,而不需要通过Activity/Fragment的onRequestPermissionsResult方法接收权限。PermissionCompatDelegate的定义也极为简单,就是两个方法,一个是申请权限的方法,一个是返回结果的方法。

    public interface PermissionCompatDelegate {
        boolean requestPermissions(@NonNull Activity activity,
                @NonNull String[] permissions, @IntRange(from = 0) int requestCode);

        boolean onActivityResult(@NonNull Activity activity,
                @IntRange(from = 0) int requestCode, int resultCode, @Nullable Intent data);
    }

使用

那这不就很简单了吗?我们自己实现一个PermissionCompatDelegate,然后调用ActivityCompat#setPermissionCompatDelegate方法,最后再申请权限不就完了吗?说干就干,把ActivityCompat申请权限的代码,Activity中读取权限的代码拷贝出来实现了一个PermissionCompatDelegate。So easy!

public class MyPermissionCompatDelegate implements ActivityCompat.PermissionCompatDelegate {
    @Override
    public boolean requestPermissions(@NonNull Activity activity, @NonNull String[] permissions, int requestCode) {
        if (activity instanceof ActivityCompat.RequestPermissionsRequestCodeValidator) {
            ((ActivityCompat.RequestPermissionsRequestCodeValidator) activity)
                    .validateRequestPermissionsRequestCode(requestCode);
        }
        activity.requestPermissions(permissions, requestCode);
        return true;
    }

    @Override
    public boolean onActivityResult(@NonNull Activity activity, int requestCode, int resultCode, @Nullable Intent data) {
        ActivityCompat.setPermissionCompatDelegate(null);
        String[] permissions = (data != null) ? data.getStringArrayExtra(
                Constant.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
        final int[] grantResults = (data != null) ? data.getIntArrayExtra(
                Constant.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
        //TODO 分发权限
        return true;
    }
}

编译、运行、测试无效。What?难道是手机问题?杀进程、重启手机、换手机都不行。笔者陷入了深深的思考中。

分析

在经过对Android权限申请源码分析后发现了问题。下面我们先看一下权限申请以及结果通知的过程。
Android使用PermissionCompatDelegate优化权限申请框架_第1张图片
请注意看图中红色字体部分,通过分析源码得知,在请求权限时调用了startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null)代码如下:

    public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
        if (requestCode < 0) {
            throw new IllegalArgumentException("requestCode should be >= 0");
        }
        if (mHasCurrentPermissionsRequest) {
            Log.w(TAG, "Can request only one set of permissions at a time");
            // Dispatch the callback with empty arrays which means a cancellation.
            onRequestPermissionsResult(requestCode, new String[0], new int[0]);
            return;
        }
        Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
        startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
        mHasCurrentPermissionsRequest = true;
    }

这里跟我们通常使用的startActivityForResult不一样,多了一个参数,就是第一个参数REQUEST_PERMISSIONS_WHO_PREFIX,这个参数就是一个标志量。当权限结果返回后会判断这个标志量如果等于REQUEST_PERMISSIONS_WHO_PREFIX就直接调用了onRequestPermissionsResult。并没有把结果传递给onActivityResult,也就是说我们的MyPermissionCompatDelegate根本不会生效!

优化

既然申请权限的时候传了REQUEST_PERMISSIONS_WHO_PREFIX就会直接回调onRequestPermissionsResult,那我们干脆直接修改申请权限的方法,不让他传REQUEST_PERMISSIONS_WHO_PREFIX过去了就行了。理论上代码如下:

Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);

很遗憾的是buildRequestPermissionsIntent是hide方法,不能直接调用,buildRequestPermissionsIntent的内部实现也调用了hide方法。查阅文档得知buildRequestPermissionsIntent方法是灰名单中的方法:灰名单方法列表。当前我们可以通过反射调用,反射调用灰名单方法时会有日志提示。为了兼容后续灰名单无法反射调用的情况笔者进行了try catch,当接收到异常时会启用第二套方案,也就是上文中介绍的Fragment方法。PermissionCompatDelegate代码如下:

    @Override
    public boolean requestPermissions(@NonNull Activity activity, @NonNull String[] permissions, int requestCode) {
        try {
            Method method = activity.getPackageManager().getClass().getMethod("buildRequestPermissionsIntent", String[].class);
            Intent intent = (Intent) method.invoke(activity.getPackageManager(), (Object) permissions);
            if (intent != null) {
                activity.startActivityForResult(intent, requestCode);
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean onActivityResult(@NonNull Activity activity, int requestCode, int resultCode, @Nullable Intent data) {
        ActivityCompat.setPermissionCompatDelegate(null);
        String[] permissions = (data != null) ? data.getStringArrayExtra(
                Constant.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
        final int[] grantResults = (data != null) ? data.getIntArrayExtra(
                Constant.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
        SpaPermissions.getInstance().notifyPermissionsChange(activity, permissions, grantResults);
        return true;
    }

开源

笔者把这种申请权限的方案进行了整理并开源到GitHub,欢迎star,开源地址如下:
https://github.com/kongxiaojun/Spaniel

你可能感兴趣的:(android,权限,app)