源碼剖析:RxPermissions 如何實現(xiàn)監(jiān)聽權(quán)限的變化

RxPermissions

正常情況下,是通過ContextCompat.checkSelfPermission檢查是否有權(quán)限辨宠,通過ActivityCompat.requestPermissions來獲取授權(quán)预麸,在onRequestPermissionsResult回調(diào)獲取授權(quán)結(jié)果盖溺,必須在一個Activity實現(xiàn)兩處代碼才可以完成整個授權(quán)砰苍,非常的麻煩莫鸭。

開源庫 RxPermission 通過RxJava很好地封裝了一套方案闹丐,大大簡化了權(quán)限申請,我們現(xiàn)在剖析RxPermission的源碼被因,看看他是如何實現(xiàn)的卿拴。

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
        }
    });
代碼結(jié)構(gòu)

RxPermissions的代碼很少衫仑,所有代碼都在以下目錄中,只有三個類堕花,全部代碼量只有五百多行文狱。

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

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

可以看到非常有趣的事情,一個權(quán)限申請庫為何會有一個Fragment類缘挽,有何作用呢瞄崇?

構(gòu)造方法源碼

我們從RxPermissions的構(gòu)造函數(shù)開始,提供了兩個構(gòu)造函數(shù)壕曼,可以傳入FragmentActivityFragment苏研,他們都是用于創(chuàng)建RxPermissionsFragment,我們都知道授權(quán)后需要在 Activity 或者 Fragment 的onRequestPermissionsResult的回調(diào)方法才能知道是否授權(quán)成功窝稿,所以猜測RxPermissionsFragment是用于獲取響應(yīng)授權(quán)信息楣富。

    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封裝了一種懶加載的方式纹蝴,在構(gòu)造方法就已經(jīng)傳入的相關(guān)的創(chuàng)建Fragment的參數(shù),但是并沒有馬上創(chuàng)建踪少,等真正需要使用時候調(diào)用mRxPermissionsFragment .get()才創(chuàng)建Fragment實體塘安。

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

Observable.just() :just操作符可以將某個對象轉(zhuǎn)化為Observable對象,是RxJava中非吃荩快捷的創(chuàng)建Observable對象的方法兼犯。

compose():該操作符是針對Observable自身的變換,通過我們自己定義的Transformer對象可以將對Observable對象變換的操作封裝起來集漾,比如可以把切黔,甚至返回一個全新的Observable。

看到請求權(quán)限的入口是request()具篇,這里用到了Observable.just(TRIGGER)纬霞,然后調(diào)用了compose()操作符,這里這里可以看到驱显,實際上相關(guān)的權(quán)限申請?zhí)幚矸庋b在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響應(yīng)伏恐,等多個Observable返回結(jié)果后才一起進行下一步的操作,這里就是把多個權(quán)限申請的結(jié)果合并為一個結(jié)果返回栓霜。

這里的關(guān)鍵的代碼在request(o, permissions).flatMap(new Function<List<Permission>,...先看后者翠桦,后者是對前者Observable列表響應(yīng)的權(quán)限進行轉(zhuǎn)換,由于是可以同時進行多個權(quán)限的請求胳蛮,如果多個權(quán)限申請中某個權(quán)限沒有通過都會返回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權(quán)限列表進行判斷,不允許傳入空的數(shù)據(jù)闻鉴,否則就會拋出異常。然后就是return oneOf(trigger, pending(permissions))...這部分代碼茂洒,我無法理解這里代碼的意義孟岛,在我實際測試,直接return requestImplementation(permissions);也是可以實現(xiàn)同樣的功能督勺,所以這里的代碼就不再展開渠羞,直接下一步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重要的類之一智哀,我們有必要詳細(xì)了解一下次询,這里就簡單描述,PublishSubject繼承于Subject瓷叫,與普通的Subject不同屯吊,在訂閱時并不立即觸發(fā)訂閱事件,而是允許我們在任意時刻手動調(diào)用onNext,onError(),onCompleted來觸發(fā)事件摹菠。比如可用在Service下載多個文件盒卸,使用PublishSubject來監(jiān)聽具體的情況,然后響應(yīng)給Activity(相當(dāng)于EventBus的功能)次氨。

這個方法的代碼比較多蔽介,但是也是比較重要的一部分,從代碼的注釋和方法命名煮寡,我們就可以理解這段代碼的意思虹蓄,其實就是對傳入的權(quán)限進行判斷,isGranted(permission)判斷APP是否已經(jīng)獲得該權(quán)限幸撕,isRevoked(permission)用于判斷APP是否在AndroidManifest.xml申請了權(quán)限薇组,如果沒有獲得權(quán)限就在RxPermissionsFragment創(chuàng)建一個一一對應(yīng)的PublishSubject,用于監(jiān)聽權(quán)限的響應(yīng)情況杈帐,方法的最后就是requestPermissionsFromFragment体箕,真正的發(fā)起權(quán)限申請的地方就是這里了,那么接下來我們就開始分析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();
        }
    }

這里的代碼很簡單累铅,其實就是發(fā)起權(quán)限申請后,獲取響應(yīng)的情況站叼,grantResults獲取申請是否已經(jīng)申請成功娃兽,shouldShowRequestPermissionRationale是用來獲取用戶是否勾選了禁止后不再詢問,通過PublishSubject響應(yīng)結(jié)果尽楔。到了這一步投储,我們可以重新回到前面的ensure()段落重新看待這部分的代碼就可以理解整個過程第练。

總結(jié)

RxPermissions的代碼量不多,由于無法做到非入侵式監(jiān)聽Activity的onRequestPermissionsResult玛荞,所以非常奇妙地創(chuàng)建一個Fragment來實現(xiàn)監(jiān)聽的功能娇掏,設(shè)計得非常優(yōu)雅。也大量使用了RxJava操作符勋眯,簡化了各種流程的轉(zhuǎn)接問題婴梧,這個庫也是學(xué)習(xí)RxJava非常重要的素材,非常值得研究客蹋。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末塞蹭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子讶坯,更是在濱河造成了極大的恐慌番电,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辆琅,死亡現(xiàn)場離奇詭異漱办,居然都是意外死亡,警方通過查閱死者的電腦和手機涎跨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門洼冻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人隅很,你說我怎么就攤上這事撞牢。” “怎么了叔营?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵屋彪,是天一觀的道長。 經(jīng)常有香客問我绒尊,道長畜挥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任婴谱,我火速辦了婚禮蟹但,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谭羔。我一直安慰自己华糖,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布瘟裸。 她就那樣靜靜地躺著客叉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兼搏,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天卵慰,我揣著相機與錄音政钟,去河邊找鬼弛针。 笑死,一個胖子當(dāng)著我的面吹牛窟感,可吹牛的內(nèi)容都是我干的吓著。 我是一名探鬼主播再扭,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼夜矗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起让虐,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤紊撕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后赡突,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體对扶,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年惭缰,在試婚紗的時候發(fā)現(xiàn)自己被綠了浪南。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡漱受,死狀恐怖络凿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昂羡,我是刑警寧澤絮记,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站虐先,受9級特大地震影響怨愤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛹批,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一撰洗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腐芍,春花似錦差导、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春络断,著一層夾襖步出監(jiān)牢的瞬間裁替,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工貌笨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留弱判,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓锥惋,卻偏偏與公主長得像昌腰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子膀跌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容