源码剖析:RxPermissions 如何实现监听权限的变化

RxPermissions

正常情况下,是通过ContextCompat.checkSelfPermission检查是否有权限,通过ActivityCompat.requestPermissions来获取授权,在onRequestPermissionsResult回调获取授权结果,必须在一个Activity实现两处代码才可以完成整个授权,非常的麻烦。

开源库 RxPermission 通过RxJava很好地封装了一套方案,大大简化了权限申请,我们现在剖析RxPermission的源码,看看他是如何实现的。

RxPermissions rxPermissions = new RxPermissions(this);
rxPermissions.request(Manifest.permission.CAMERA).subscribe(granted -> {
        if (granted) { // Always true pre-M
           // I can control the camera now
        } else {
           // Oups permission denied
        }
    });
代码结构

RxPermissions的代码很少,所有代码都在以下目录中,只有三个类,全部代码量只有五百多行。

lib/src/main/java/com/tbruyelle/rxpermissions2

└── rxpermissions2
    ├── Permission.java
    ├── RxPermissions.java
    └── RxPermissionsFragment.java

可以看到非常有趣的事情,一个权限申请库为何会有一个Fragment类,有何作用呢?

构造方法源码

我们从RxPermissions的构造函数开始,提供了两个构造函数,可以传入FragmentActivityFragment,他们都是用于创建RxPermissionsFragment,我们都知道授权后需要在 Activity 或者 Fragment 的onRequestPermissionsResult的回调方法才能知道是否授权成功,所以猜测RxPermissionsFragment是用于获取响应授权信息。

    public RxPermissions(@NonNull final FragmentActivity activity) {
        mRxPermissionsFragment = getLazySingleton(activity.getSupportFragmentManager());
    }
    public RxPermissions(@NonNull final Fragment fragment) {
        mRxPermissionsFragment = getLazySingleton(fragment.getChildFragmentManager());
    }
    @NonNull
    private Lazy<RxPermissionsFragment> getLazySingleton(@NonNull final FragmentManager fragmentManager) {
        return new Lazy<RxPermissionsFragment>() {
            private RxPermissionsFragment rxPermissionsFragment;
            @Override
            public synchronized RxPermissionsFragment get() {
                if (rxPermissionsFragment == null) {
                    rxPermissionsFragment = getRxPermissionsFragment(fragmentManager);
                }
                return rxPermissionsFragment;
            }
        };
    }

懒加载:从上面的getLazySingleton方法,我们看到一个非常有趣的写法,这里使用Lazy封装了一种懒加载的方式,在构造方法就已经传入的相关的创建Fragment的参数,但是并没有马上创建,等真正需要使用时候调用mRxPermissionsFragment .get()才创建Fragment实体。

RxPermissions.request() 入口分析
    public Observable<Boolean> request(final String... permissions) {
        return Observable.just(TRIGGER).compose(ensure(permissions));
    }

Observable.just() :just操作符可以将某个对象转化为Observable对象,是RxJava中非常快捷的创建Observable对象的方法。

compose():该操作符是针对Observable自身的变换,通过我们自己定义的Transformer对象可以将对Observable对象变换的操作封装起来,比如可以把,甚至返回一个全新的Observable。

看到请求权限的入口是request(),这里用到了Observable.just(TRIGGER),然后调用了compose()操作符,这里这里可以看到,实际上相关的权限申请处理封装在ensure()方法中。

ensure()
    public <T> ObservableTransformer<T, Boolean> ensure(final String... permissions) {
        return new ObservableTransformer<T, Boolean>() {
            @Override
            public ObservableSource<Boolean> apply(Observable<T> o) {
                return request(o, permissions)
                        // Transform Observable<Permission> to Observable<Boolean>
                        .buffer(permissions.length)
                        .flatMap(new Function<List<Permission>, ObservableSource<Boolean>>() {
                            @Override
                            public ObservableSource<Boolean> apply(List<Permission> permissions) {
                                if (permissions.isEmpty()) {
                                    // Occurs during orientation change, when the subject receives onComplete.
                                    // In that case we don't want to propagate that empty list to the
                                    // subscriber, only the onComplete.
                                    return Observable.empty();
                                }
                                // Return true if all permissions are granted.
                                for (Permission p : permissions) {
                                    if (!p.granted) {
                                        return Observable.just(false);
                                    }
                                }
                                return Observable.just(true);
                            }
                        });
            }
        };
    }

buffer:这个是RxJava的一个操作符,字面意思就是缓冲,其实就是缓存多个Observable响应,等多个Observable返回结果后才一起进行下一步的操作,这里就是把多个权限申请的结果合并为一个结果返回。

这里的关键的代码在request(o, permissions).flatMap(new Function<List<Permission>,...先看后者,后者是对前者Observable列表响应的权限进行转换,由于是可以同时进行多个权限的请求,如果多个权限申请中某个权限没有通过都会返回false;那么我们进一步看前者的代码。

request(o, permissions)
    private Observable<Permission> request(final Observable<?> trigger, final String... permissions) {
        if (permissions == null || permissions.length == 0) {
            throw new IllegalArgumentException("RxPermissions.request/requestEach requires at least one input permission");
        }
        return oneOf(trigger, pending(permissions))
                .flatMap(new Function<Object, Observable<Permission>>() {
                    @Override
                    public Observable<Permission> apply(Object o) {
                        return requestImplementation(permissions);
                    }
                });
    }

这里的代码首先对传入的permissions权限列表进行判断,不允许传入空的数据,否则就会抛出异常。然后就是return oneOf(trigger, pending(permissions))...这部分代码,我无法理解这里代码的意义,在我实际测试,直接return requestImplementation(permissions);也是可以实现同样的功能,所以这里的代码就不再展开,直接下一步requestImplementation分析。

requestImplementation(permissions)
@TargetApi(Build.VERSION_CODES.M)
    private Observable<Permission> requestImplementation(final String... permissions) {
        List<Observable<Permission>> list = new ArrayList<>(permissions.length);
        List<String> unrequestedPermissions = new ArrayList<>();

        // In case of multiple permissions, we create an Observable for each of them.
        // At the end, the observables are combined to have a unique response.
        for (String permission : permissions) {
            mRxPermissionsFragment.get().log("Requesting permission " + permission);
            if (isGranted(permission)) {
                // Already granted, or not Android M
                // Return a granted Permission object.
                list.add(Observable.just(new Permission(permission, true, false)));
                continue;
            }

            if (isRevoked(permission)) {
                // Revoked by a policy, return a denied Permission object.
                list.add(Observable.just(new Permission(permission, false, false)));
                continue;
            }

            PublishSubject<Permission> subject = mRxPermissionsFragment.get().getSubjectByPermission(permission);
            // Create a new subject if not exists
            if (subject == null) {
                unrequestedPermissions.add(permission);
                subject = PublishSubject.create();
                mRxPermissionsFragment.get().setSubjectForPermission(permission, subject);
            }

            list.add(subject);
        }

        if (!unrequestedPermissions.isEmpty()) {
            String[] unrequestedPermissionsArray = unrequestedPermissions.toArray(new String[unrequestedPermissions.size()]);
            requestPermissionsFromFragment(unrequestedPermissionsArray);
        }
        return Observable.concat(Observable.fromIterable(list));
    }
    @TargetApi(Build.VERSION_CODES.M)
    void requestPermissionsFromFragment(String[] permissions) {
        mRxPermissionsFragment.get().log("requestPermissionsFromFragment " + TextUtils.join(", ", permissions));
        mRxPermissionsFragment.get().requestPermissions(permissions);
    }

PublishSubject:这个类是RxJava重要的类之一,我们有必要详细了解一下,这里就简单描述,PublishSubject继承于Subject,与普通的Subject不同,在订阅时并不立即触发订阅事件,而是允许我们在任意时刻手动调用onNext,onError(),onCompleted来触发事件。比如可用在Service下载多个文件,使用PublishSubject来监听具体的情况,然后响应给Activity(相当于EventBus的功能)。

这个方法的代码比较多,但是也是比较重要的一部分,从代码的注释和方法命名,我们就可以理解这段代码的意思,其实就是对传入的权限进行判断,isGranted(permission)判断APP是否已经获得该权限,isRevoked(permission)用于判断APP是否在AndroidManifest.xml申请了权限,如果没有获得权限就在RxPermissionsFragment创建一个一一对应的PublishSubject,用于监听权限的响应情况,方法的最后就是requestPermissionsFromFragment,真正的发起权限申请的地方就是这里了,那么接下来我们就开始分析RxPermissionsFragment的onRequestPermissionsResult方法。

onRequestPermissionsResult
    @Override
    @TargetApi(Build.VERSION_CODES.M)
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode != PERMISSIONS_REQUEST_CODE) return;
        boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length];
        for (int i = 0; i < permissions.length; i++) {
            shouldShowRequestPermissionRationale[i] = shouldShowRequestPermissionRationale(permissions[i]);
        }
        onRequestPermissionsResult(permissions, grantResults, shouldShowRequestPermissionRationale);
    }

    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<Permission> 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();
        }
    }

这里的代码很简单,其实就是发起权限申请后,获取响应的情况,grantResults获取申请是否已经申请成功,shouldShowRequestPermissionRationale是用来获取用户是否勾选了禁止后不再询问,通过PublishSubject响应结果。到了这一步,我们可以重新回到前面的ensure()段落重新看待这部分的代码就可以理解整个过程。

总结

RxPermissions的代码量不多,由于无法做到非入侵式监听Activity的onRequestPermissionsResult,所以非常奇妙地创建一个Fragment来实现监听的功能,设计得非常优雅。也大量使用了RxJava操作符,简化了各种流程的转接问题,这个库也是学习RxJava非常重要的素材,非常值得研究。

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

推荐阅读更多精彩内容