RxPermissions源碼解析

項(xiàng)目地址:RxPermissions,本文分析版本: 4c4d4e1

1.簡(jiǎn)介

RxPermissions是基于RxJava開(kāi)發(fā)的用于幫助在Android 6.0中處理運(yùn)行時(shí)權(quán)限檢測(cè)的框架。在Android 6.0中,系統(tǒng)新增了部分權(quán)限的運(yùn)行時(shí)動(dòng)態(tài)獲取。而不再是在以前的版本中安裝的時(shí)候授予權(quán)限。

對(duì)于運(yùn)行時(shí)的權(quán)限獲取提示,國(guó)內(nèi)的Android工程師們應(yīng)該并不陌生,國(guó)內(nèi)的第三方ROM例如MIUI在很早前就做了類(lèi)似的功能。但是第三方ROM并不能提供給我們權(quán)限請(qǐng)求成功或失敗的接口,這就導(dǎo)致我們無(wú)法通過(guò)PackageManager提供的checkPermission()方法來(lái)準(zhǔn)確的獲取到我們是否獲得該權(quán)限。只能根據(jù)具體的權(quán)限來(lái)做相應(yīng)的處理。但是在Android 6.0中我們可以準(zhǔn)確的獲取我們的應(yīng)用是否獲取某個(gè)權(quán)限,具體的方法希望大家看這篇文章:Android 6.0 運(yùn)行時(shí)權(quán)限處理。大致方法是通過(guò)API 23中的ActivityrequestPermissions(String[] permissions, int requestCode);方法請(qǐng)求權(quán)限,并在onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults)回調(diào)方法中處理請(qǐng)求結(jié)果。這個(gè)方法與onActivityResult()類(lèi)似。RxPermissions在這個(gè)基礎(chǔ)上做了封裝,使我們?cè)谔幚磉\(yùn)行時(shí)權(quán)限變得更加的簡(jiǎn)單。

另外關(guān)于RxJava如果你現(xiàn)在還沒(méi)了解過(guò)RxJava可以直接翻到文章最下面去查看我總結(jié)的一些RxJava相關(guān)的文章,不然并不推薦直接看這篇文章。下面我們就來(lái)具體看看RxPermissions的使用方法以及源碼分析。

2.使用方法

1.直接獲取權(quán)限(使用Retrolambda使代碼更加簡(jiǎn)潔,當(dāng)然并不是必須使用):

// 必須在初始化階段調(diào)用,例如onCreate()方法中
RxPermissions.getInstance(this)
    .request(Manifest.permission.CAMERA)
    .subscribe(granted -> {
        if (granted) { // 在android 6.0之前會(huì)默認(rèn)返回true
           // 已經(jīng)獲取權(quán)限
        } else {
           // 未獲取權(quán)限
        }
    });

2.通過(guò)條件觸發(fā)獲取權(quán)限(結(jié)合RxBinding使用)

// 必須在初始化階段調(diào)用,例如onCreate()方法中
RxView.clicks(findViewById(R.id.enableCamera))
    .compose(RxPermissions.getInstance(this).ensure(Manifest.permission.CAMERA))
    .subscribe(granted -> {
        // 當(dāng)R.id.enableCamera被點(diǎn)擊的時(shí)候觸發(fā)獲取權(quán)限
    });

3.一次請(qǐng)求多個(gè)權(quán)限(有兩種方式)

如果同時(shí)請(qǐng)求多個(gè)權(quán)限,下面這種方式會(huì)合并請(qǐng)求結(jié)果,即所有權(quán)限請(qǐng)求成功會(huì)返回true,若有一個(gè)權(quán)限未成功則返回false。

RxPermissions.getInstance(this)
    .request(Manifest.permission.CAMERA,
             Manifest.permission.READ_PHONE_STATE)
    .subscribe(granted -> {
        if (granted) {
           // 所有權(quán)限請(qǐng)求被同意
        } else {
           // 至少有一個(gè)權(quán)限沒(méi)同意
        }
    });

當(dāng)然你可以通過(guò)requestEach or ensureEach 來(lái)分別獲取每一個(gè)權(quán)限請(qǐng)求的結(jié)果。

RxPermissions.getInstance(this)
    .requestEach(Manifest.permission.CAMERA,
             Manifest.permission.READ_PHONE_STATE)
    .subscribe(permission -> { // 會(huì)發(fā)送兩個(gè)Permission對(duì)象
        if (permission.granted) {
           // `permission.name` is granted !
        }
    });

注意:由于在請(qǐng)求權(quán)限的過(guò)程中app有可能會(huì)被重啟,所以權(quán)限請(qǐng)求必須放在初始化的階段,比如在Activity.onCreate/onResume, 或者
View.onFinishInflate方法中。如果不這樣處理,那么如果app在請(qǐng)求過(guò)程中重啟的話(huà),權(quán)限請(qǐng)求結(jié)果將不會(huì)發(fā)送給訂閱者即subscriber

2.整體介紹

RxPermission一共就只有三個(gè)類(lèi):Permission是定義的權(quán)限model類(lèi),用來(lái)存放權(quán)限名稱(chēng)以及是否獲取權(quán)限的信息。RxPermissions就是最主要的類(lèi)了,利用RxJava提供了我們上面在使用方法中介紹的所有方法。還有一個(gè)ShadowActivity類(lèi)是用來(lái)請(qǐng)求權(quán)限用的。下面我們就來(lái)詳細(xì)介紹RxPermission的實(shí)現(xiàn)。

注意:如果你還未了解過(guò)RxJava那么可以先閱讀本文最后的一系列優(yōu)秀文章。如果你已經(jīng)了解過(guò)RxJava的話(huà),那么下面我將會(huì)介紹RxJava中的部分操作符在RxPermission中的實(shí)際運(yùn)用。相信能幫助你更好的理解RxJava中操作符的使用。

3.源碼分析

我們依然按照我們慣用的方法來(lái)分析,通過(guò)使用方法來(lái)分析調(diào)用流程,最終理解整個(gè)項(xiàng)目。首先再回顧一遍使用方法(注意這里我同時(shí)請(qǐng)求了兩個(gè)權(quán)限),那么結(jié)果將是如果所有權(quán)限請(qǐng)求成功會(huì)返回true,若有一個(gè)權(quán)限未成功則返回false。代碼如下:

// 必須在初始化階段調(diào)用,例如onCreate()方法中
RxPermissions.getInstance(this)
    .request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
    .subscribe(granted -> {
        if (granted) { // 在android 6.0之前會(huì)默認(rèn)返回true
           // 已經(jīng)獲取權(quán)限
        } else {
           // 未獲取權(quán)限
        }
    });

1.RxPermissions.getInstance(this)的實(shí)現(xiàn)


    static RxPermissions sSingleton;
    private Context :;

    public static RxPermissions getInstance(Context) {
        if (sSingleton == null) {
            sSingleton = new RxPermissions(ctx.getApplicationContext());
        }
        return sSingleton;
    }

    RxPermissions(Context ctx) {
        mCtx = ctx;
    }

很明顯是維護(hù)RxPermissions的單例,不再多做介紹,緊接著我們來(lái)看RxPermissionsrequest(Manifest.permission.CAMERA)方法的實(shí)現(xiàn):

2.request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)的實(shí)現(xiàn):


    public Observable<Boolean> request(final String... permissions) {
        return Observable.just(null).compose(ensure(permissions));
    }

首先從返回值看到是返回一個(gè)Observable<Boolean>對(duì)象,方法中也是直接returnObservable.just(null).compose(ensure(permissions))。這里涉及兩個(gè)方法分別是just()以及compose()我們先解釋這兩個(gè)操作符:

1.Observable.just(null)

just操作符可以將某個(gè)對(duì)象轉(zhuǎn)化為Observable對(duì)象,并且將其發(fā)射出去,可以是一個(gè)數(shù)字、一個(gè)字符串、數(shù)組、Iterate對(duì)象等,是RxJava中非??旖莸膭?chuàng)建Observable對(duì)象的方法。在這里just()方法中雖然傳入的是null但是并不影響創(chuàng)建出的Observable的作用,如果有subscriber訂閱依然會(huì)依次調(diào)用其onNext()onCompleted()方法。所以這里就是為了創(chuàng)建出一個(gè)Observable對(duì)象,便于后續(xù)的處理。創(chuàng)建完Observable對(duì)象之后緊接著調(diào)用了compose()方法:

2.compose(Transformer)操作符

compose()操作符是針對(duì)Observable自身的變換,通過(guò)我們自己定義的Transformer對(duì)象可以將對(duì)Observable對(duì)象變換的操作封裝起來(lái),實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Transformer對(duì)象如下:


    class myTransformer implements Observable.Transformer<Object, Boolean> {

        @Override
        public Observable<Boolean> call(Observable<Object> objectObservable) {
            return objectObservable.map(new Func1<Object, Boolean>() {
                @Override
                public Boolean call(Object o) {
                    return true;
                }
            });
        }
    }

通過(guò)上面這個(gè)Transformer就可以將任何Observable<Object>對(duì)象轉(zhuǎn)換成Observable<Boolean>對(duì)象了。當(dāng)然在Transformer里你也可以返回一個(gè)全新的Observable對(duì)象。RxPermissions就是這樣做的,那么回到項(xiàng)目中再來(lái)看compose(ensure(permissions));那么ensure(permissions);一定是返回一個(gè)Transformer對(duì)象了。

3.ensure(permissions);方法的實(shí)現(xiàn)

我們來(lái)看看ensure(permissions)方法的實(shí)現(xiàn):


    public Observable.Transformer<Object, Boolean> ensure(final String... permissions) {
        //創(chuàng)建一個(gè)Transformer對(duì)象返回
        return new Observable.Transformer<Object, Boolean>() {
            // o表示當(dāng)前Observable對(duì)象。
            @Override
            public Observable<Boolean> call(Observable<Object> o) {
                //request(o, permissions) 方法返回 Observable<Permission>對(duì)象
                return request(o, permissions)
                        // 將Observable<Permission>轉(zhuǎn)換為Observable<Boolean>
                        // buffer操作符
                        .buffer(permissions.length)
                        // flatMap操作符
                        .flatMap(new Func1<List<Permission>, Observable<Boolean>>() {
                            @Override
                            public Observable<Boolean> call(List<Permission> permissions) {
                                // 如果permissions為空那么直接返回Observable.empty();
                                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();
                                }
                                // 遍歷所有Permission,如果有一個(gè)未成功則返回false,全部成功返回true。
                                for (Permission p : permissions) {
                                    if (!p.granted) {
                                        return Observable.just(false);
                                    }
                                }
                                return Observable.just(true);
                            }
                        });
            }
        };
    }

確實(shí)是返回一個(gè)Observable.Transformer對(duì)象,那么在call()方法里首先調(diào)用了request(o, permissions)方法,然后又進(jìn)行了buffer()flatMap()的處理,最終會(huì)返回Observable.empty();、Observable.just(false);Observable.just(true);對(duì)象。我們先來(lái)看看request(o, permissions)方法的實(shí)現(xiàn):

4.request(o, permissions);方法的實(shí)現(xiàn)

    private Observable<Permission> request(final Observable<?> trigger,
                                           final String... permissions) {
        //如果并沒(méi)有請(qǐng)求的權(quán)限則拋出異常
        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 Func1<Object, Observable<Permission>>() {
                    @Override
                    public Observable<Permission> call(Object o) {
                        return request_(permissions);
                    }
                });
    }

首先對(duì)permissions做了判斷,然后調(diào)用了oneOf(trigger, pending(permissions))方法,并通過(guò)flatMap()操作符在call()方法中調(diào)用了request_(permissions);按照慣例我們應(yīng)該去看oneOf()方法的實(shí)現(xiàn)了。但是oneOf()方法里其實(shí)并沒(méi)有什么實(shí)際意義,看了項(xiàng)目的commit log我覺(jué)得應(yīng)該是歷史遺留問(wèn)題,作者可能是想處理一些相同重復(fù)的權(quán)限請(qǐng)求,但是并沒(méi)有實(shí)現(xiàn)。所以其實(shí)這個(gè)方法完全可以這樣代替;


    private Observable<Permission> request(final Observable<?> trigger,
                                           final String... permissions) {
       return request_(permissions);
    }

直接調(diào)用request_(permissions);即可。我測(cè)試中并沒(méi)有發(fā)現(xiàn)問(wèn)題。目前我還沒(méi)有聯(lián)系到作者詢(xún)問(wèn)這個(gè)方法的實(shí)現(xiàn)目的,稍后可能會(huì)提一個(gè)issue,如果有結(jié)果會(huì)在文章中更新。所以這里大家就完全可以理解成直接調(diào)用了request_(permissions);方法:

5.request_(permissions);方法的實(shí)現(xiàn)

    @TargetApi(Build.VERSION_CODES.M)
    private Observable<Permission> request_(final String... permissions) {

        //創(chuàng)建出一個(gè)存放Observable<Permission>的list
        List<Observable<Permission>> list = new ArrayList<>(permissions.length);
        //存放還為請(qǐng)求權(quán)限的list
        List<String> unrequestedPermissions = new ArrayList<>();

        // 在請(qǐng)求多個(gè)權(quán)限的時(shí)候,我們?yōu)槊恳粋€(gè)請(qǐng)求的權(quán)限都創(chuàng)建一個(gè)observable對(duì)象,在最后
        // 這些observable會(huì)被合并成一個(gè)response。
        for (String permission : permissions) {
            log("Requesting permission " + permission);
            //如果是已經(jīng)獲得的權(quán)限,或者Android版本在6.0之前則直接添加一個(gè)
            // Observable.just(new Permission(permission, true))對(duì)象.
            if (isGranted(permission)) {
                // Already granted, or not Android M
                // Return a granted Permission object.
                list.add(Observable.just(new Permission(permission, true)));
                continue;
            }
            // 如果是已經(jīng)拒絕的權(quán)限則添加
            // Observable.just(new Permission(permission, false))對(duì)象.
            if (isRevoked(permission)) {
                // Revoked by a policy, return a denied Permission object.
                list.add(Observable.just(new Permission(permission, false)));
                continue;
            }

            PublishSubject<Permission> subject = mSubjects.get(permission);
            // 如果mSubjects 不存在當(dāng)前 permission,則添加到unrequestedPermissions中
            // 并且創(chuàng)建PublishSubject對(duì)象并添加到mSubjects中。
            if (subject == null) {
                unrequestedPermissions.add(permission);
                subject = PublishSubject.create();
                mSubjects.put(permission, subject);
            }
            //并且添加到list中
            list.add(subject);
        }

        //如果有未請(qǐng)求的權(quán)限
        if (!unrequestedPermissions.isEmpty()) {
            startShadowActivity(unrequestedPermissions
                    .toArray(new String[unrequestedPermissions.size()]));
        }
        return Observable.concat(Observable.from(list));
    }

    void startShadowActivity(String[] permissions) {
        log("startShadowActivity " + TextUtils.join(", ", permissions));
        Intent intent = new Intent(mCtx, ShadowActivity.class);
        intent.putExtra("permissions", permissions);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mCtx.startActivity(intent);
    }

代碼如上,注釋非常清晰,整體上就是將已經(jīng)允許的權(quán)限和已經(jīng)拒絕過(guò)的權(quán)限添加到list中,并且將還未請(qǐng)求的權(quán)限分別添加到mSubjectslist中。然后調(diào)用startShadowActivity();方法。最后通過(guò)Observable.concat(Observable.from(list));返回。這里主要包含PublishSubjectconcat()操作符的知識(shí):

6.PublishSubject對(duì)象

PublishSubject文檔中,可以看出是繼承自Subject,Subject是既可以充當(dāng)Observer又能充當(dāng)Observable的。從文檔中的Example中可以看到訂閱PublishSubjectobserver只會(huì)接收到訂閱之后PublishSubject發(fā)送的數(shù)據(jù),但是本項(xiàng)目中并沒(méi)有體現(xiàn)出此特性,主要是利用PublishSubject中的onNext()onCompleted()方法,這里大致了解這么多,下面是Example的代碼:

 PublishSubject<Object> subject = PublishSubject.create();
  // observer1 will receive all onNext and onCompleted events
  subject.subscribe(observer1);
  subject.onNext("one");
  subject.onNext("two");
  // observer2 will only receive "three" and onCompleted
  subject.subscribe(observer2);
  subject.onNext("three");
  subject.onCompleted();
7.concat()操作符

Concat操作符將多個(gè)Observable結(jié)合成一個(gè)Observable并發(fā)射數(shù)據(jù),并且嚴(yán)格按照先后順序發(fā)射數(shù)據(jù),前一個(gè)Observable的數(shù)據(jù)沒(méi)有發(fā)射完,是不能發(fā)射后面Observable的數(shù)據(jù)的。引用自此篇文章。所以在本項(xiàng)目中Concat()是為了保證請(qǐng)求的權(quán)限按順序返回。接下來(lái)我們看看ShadowActivity的實(shí)現(xiàn):

8.ShadowActivity的實(shí)現(xiàn)
@TargetApi(Build.VERSION_CODES.M)
public class ShadowActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState == null) {
            handleIntent(getIntent());
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        handleIntent(intent);
    }

    private void handleIntent(Intent intent) {
        String[] permissions = intent.getStringArrayExtra("permissions");
        requestPermissions(permissions, 42);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        RxPermissions.getInstance(this).onRequestPermissionsResult(requestCode, permissions, grantResults);
        finish();
    }
}

很簡(jiǎn)單其實(shí)就是按照系統(tǒng)提供給我們的方法進(jìn)行權(quán)限請(qǐng)求,最后回調(diào)RxPermissionsonRequestPermissionsResult()方法:

9.onRequestPermissionsResult()方法的實(shí)現(xiàn)

    void onRequestPermissionsResult(int requestCode,
                                    String permissions[], int[] grantResults) {
        for (int i = 0, size = permissions.length; i < size; i++) {
            log("onRequestPermissionsResult  " + permissions[i]);
            // 取出對(duì)應(yīng)的PublishSubject對(duì)象
            PublishSubject<Permission> subject = mSubjects.get(permissions[i]);
            if (subject == null) {
                // No subject found
                throw new IllegalStateException("RxPermissions.onRequestPermissionsResult invoked but didn't find the corresponding permission request.");
            }
            //從mSubjects移除
            mSubjects.remove(permissions[i]);
            //獲取結(jié)果
            boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
            //調(diào)用onNext()方法發(fā)送結(jié)果
            subject.onNext(new Permission(permissions[i], granted));
            //調(diào)用onCompleted()方法。
            subject.onCompleted();
        }
    }

簡(jiǎn)單的來(lái)說(shuō)就是拿到結(jié)果并發(fā)送結(jié)果。所以就又回到了最初的ensure(permissions);方法中的request(o, permissions)之后,代碼如下:


        request(o, permissions)
        // 將Observable<Permission>轉(zhuǎn)換為Observable<Boolean>
        // buffer操作符
        .buffer(permissions.length)
        // flatMap操作符
        .flatMap(new Func1<List<Permission>, Observable<Boolean>>() {
            @Override
            public Observable<Boolean> call(List<Permission> permissions) {
                // 如果permissions為空那么直接返回Observable.empty();
                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();
                }
                // 遍歷所有Permission,如果有一個(gè)未成功則返回false,全部成功返回true。
                for (Permission p : permissions) {
                    if (!p.granted) {
                        return Observable.just(false);
                    }
                }
                return Observable.just(true);
            }
        });

所以這里會(huì)不斷的發(fā)送Observable<Permission>對(duì)象,請(qǐng)求了幾個(gè)權(quán)限就會(huì)發(fā)送幾次,但是這里用了一個(gè)buffer()操作符,關(guān)于buffer()操作符:

10.buffer()操作符

buffer英文是緩沖區(qū)的意思。所以Buffer操作符所要做的事情就是將數(shù)據(jù)按照規(guī)定的大小做一下緩存,然后將緩存的數(shù)據(jù)作為一個(gè)集合發(fā)射出去。詳細(xì)可以看這里,所以在本項(xiàng)目中就是講這些Observable<Permission>轉(zhuǎn)換成Observable<List<Permission>>對(duì)象,緊接著又使用了flatMap()操作符然后返回了我們最終的結(jié)果。以上就是整個(gè)的調(diào)用流程了,如果有不清楚的建議可以多多的調(diào)試RxPermission的代碼以及查閱各種資料幫助理解。

11. requestEach()、ensureEach()、ensure()的實(shí)現(xiàn)

以上我們分析了request()方法的實(shí)現(xiàn),看似好像還剩下上面三個(gè)方法沒(méi)有分析。其實(shí)仔細(xì)看的同學(xué)應(yīng)該已經(jīng)看明白了。上面三個(gè)方法其實(shí)都是差不多的。如果你看懂了request()方法的實(shí)現(xiàn),那么這三個(gè)方法你一定能看懂,有興趣的同學(xué)可以自行去源碼里研究。

4.個(gè)人評(píng)價(jià)

其實(shí)Android 6.0的權(quán)限處理我自己在項(xiàng)目中都沒(méi)有使用過(guò),因?yàn)槟媚壳皣?guó)內(nèi)市場(chǎng)來(lái)說(shuō),首先Android 6.0的手機(jī)占有量非常少。再者我們可以使用很簡(jiǎn)單的方法將targetSdkVersion設(shè)置為22來(lái)兼容6.0的權(quán)限處理。所以目前項(xiàng)目中應(yīng)該很少需要使用RxPermissions這個(gè)項(xiàng)目。但是這個(gè)項(xiàng)目作為RxJava的學(xué)習(xí)資料是相當(dāng)?shù)暮?。從中我們可以學(xué)到大量的RxJava相關(guān)的使用知識(shí)。如果你現(xiàn)在在學(xué)習(xí)RxJava,強(qiáng)烈推薦這個(gè)項(xiàng)目。

5.RxJava相關(guān)文章

RxJava
深入淺出RxJava(大頭鬼教父的文章)
給 Android 開(kāi)發(fā)者的 RxJava 詳解
RxJava 與 Retrofit 結(jié)合的最佳實(shí)踐
RxJava的操作符(有四篇)
RxJava操作符 (有八篇)
那些年我們錯(cuò)過(guò)的響應(yīng)式編程
不要打破鏈?zhǔn)剑菏褂肦xjava的compose()操作符
當(dāng)EventBus遇上RxJava
Rx小鄧子的簡(jiǎn)書(shū)相當(dāng)多RxJava相關(guān)文章

我每周會(huì)寫(xiě)一篇源代碼分析的文章,以后也可能會(huì)有其他主題.
如果你喜歡我寫(xiě)的文章的話(huà),歡迎關(guān)注我的新浪微博@達(dá)達(dá)達(dá)達(dá)sky
地址: http://weibo.com/u/2030683111
每周我會(huì)第一時(shí)間在微博分享我寫(xiě)的文章,也會(huì)積極轉(zhuǎn)發(fā)更多有用的知識(shí)給大家.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評(píng)論 25 709
  • 安卓 6.0獲取位置權(quán)限造成閃退 開(kāi)發(fā)用下面的方法解決的。1.簡(jiǎn)介RxPermissions是基于RxJava開(kāi)發(fā)...
    望月成三人閱讀 5,087評(píng)論 0 8
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評(píng)論 19 139
  • 歷經(jīng)千辛萬(wàn)苦,說(shuō)盡千言萬(wàn)語(yǔ),跑遍千山萬(wàn)水,想盡千方百計(jì)
    小飛機(jī)1948閱讀 325評(píng)論 0 0
  • 藍(lán)水晶看不過(guò)這一切,一群公豺在為王位互相毆打,斯鷗豺群公豺損失摻重。 公豺的尸體到處都是...
    九翼天龍閱讀 377評(píng)論 2 3

友情鏈接更多精彩內(nèi)容