專案需求討論 - 動態許可權申請分析及相關第三方庫原始碼分析

青蛙要fly發表於2017-09-28

hi,又到了我們具體開發時候遇到的專案需求討論了。

在具體專案開發中,關於Android的動態申請許可權的功能,我想大家都見怪不怪了。很多人開發的app中也都使用過這塊需求。

PS:本文我寫的比較囉嗦,特別是對RxPermission和easyPermission的原始碼分析,因為我寫的比較細(又或者是囉嗦及累贅),所以造成篇幅會很大。可能很多人都不會有耐心看完。

前言

  1. Android 6.0以下:
    都是直接在AndroidManifest.xml中直接填入我們想要的許可權即可。
    比如獲取手機狀態的許可權,我們就直接在這個xml檔案中填入:
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />複製程式碼
  2. Android 6.0以上:
    對於普通許可權(Normal Permission)還是繼續直接在AndroidManifest.xml中寫入即可,可以獲得其許可權
    對於一些重要的許可權(Dangerous Permission)我們需要動態去申請的時候,我們需要動態去獲取。比如:
    身體感測器
    日曆
    攝像頭
    通訊錄
    地理位置
    麥克風
    電話
    簡訊
    儲存空間複製程式碼
    主要是上述幾大塊相關的許可權,需要動態去獲取,具體見下面列表:

PS:題外話

  1. targetSDKVersion < 23 & API(手機系統) < 6.0:安裝時預設獲得許可權,且使用者無法在安裝App之後取消許可權。
  2. targetSDKVersion >= 23 & API(手機系統) < 6.0:安裝時預設獲得許可權,且使用者無法在安裝App之後取消許可權。
  3. targetSDKVersion < 23 & API(手機系統) >= 6.0:安裝時預設獲得許可權,但是使用者可以在安裝App完成後動態取消授權(取消時手機會彈出提醒,告訴使用者這個是為舊版手機打造的應用,讓使用者謹慎操作)。
  4. targetSDKVersion >= 23 & API(手機系統) >= 6.0:安裝時不會獲得許可權,可以在執行時向使用者申請許可權。使用者授權以後仍然可以在設定介面中取消授權。

Android 動態申請許可權:

這裡我會分三大塊來講:

  1. 原生API支援
  2. easyPermission
  3. RxPermissions (Rxjava2)

原生API支援:

我們先從簡單的說起,在Android6.0出來後,在各種第三方許可權庫還沒出來的時候,大家普遍使用的是谷歌原生的申請許可權的流程程式碼:

1. 檢查許可權(check permission):

checkSelfPermission(String permission):23版本api新增,用來檢測自己是否授予了指定permission 。

Context.checkPermission (String permission, int pid, int uid):用來檢測指定uid和pid的程式中是否授予了指定的permission。

Context.checkCallingOrSelfPermission (String permission):用來檢測自己或者呼叫程式中是否授予了指定permission。

Context.checkCallingPermission (String permission):檢查正在處理的呼叫者程式是否授予指定permission 許可權,如果呼叫者是自己那麼返回 。

除了上面的大家可能用的比較多的是ContextCompat.checkSelfPermission(),但其實我們可以跟進去看:

public static int checkSelfPermission(@NonNull Context context, @NonNull String permission) {
    if (permission == null) {
        throw new IllegalArgumentException("permission is null");
    }
    return context.checkPermission(permission, android.os.Process.myPid(), Process.myUid());
}複製程式碼

最終呼叫的還是根據
Context.checkPermission (String permission, int pid, int uid)(用來檢測指定uid和pid的程式中是否授予了指定的permission)來進行檢查。

當然還有其他的攜帶Uri的permission檢查,不過我沒有試驗過,用過的小夥伴留個言,看是在什麼情況下使用及怎麼使用。
類似:

Context.checkCallingOrSelfUriPermission (Uri uri, int modeFlags) 
Context.checkCallingUriPermission (Uri uri, int modeFlags); 
Context.checkUriPermission (Uri uri, int pid, int uid, int modeFlags);
Context.checkUriPermission (Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags);複製程式碼

我們呼叫相關方法後,如果返回的int值與PackageManager.PERMISSION_GRANTED值相等,那麼表示已經授權了,如果為PackageManager.PERMISSION_DENIED,則說明申請許可權被拒絕了。

2.請求許可權:

int requestCode = 1;
requestPermissions(new String[]{Manifest.permission.READ_PHONE_STATE},requestCode);複製程式碼

3.回撥事件處理:

/**
 * 引數1:requestCode-->是requestPermissions()方法傳遞過來的請求碼。
 * 引數2:permissions-->是requestPermissions()方法傳遞過來的需要申請許可權
 * 引數3:grantResults-->是申請許可權後,系統返回的結果,PackageManager.PERMISSION_GRANTED表示授權成功,PackageManager.PERMISSION_DENIED表示授權失敗。
 * grantResults和permissions是一一對應的
 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}複製程式碼

4.請求許可權提示:

android6.0(API23)及以上,提供了一個方法:shouldshowrequestpermissionrationale(),如果應用程式請求此許可權,並且使用者拒絕了請求,則此方法返回true。

ps:使用者在過去拒絕了許可權請求,對話方塊中選擇了“不再詢問”選項,該方法返回false。如果設定中禁止應用程式具有該許可權,該方法還將返回false。

所以我們可以當使用者拒絕了這個許可權的時候,我們可以用這個方法判斷,然後可以彈出一個彈框,並且寫上我們的提示內容,比如我們可以彈出一個彈框,上面寫著“如果要使用我們的XXX功能,一定要開啟XXX許可權哦!!!!”,然後同時我們可以讓點選彈框的“確認”按鈕後跳到“設定”應用,讓使用者手動去開啟許可權。

我們也可以在Activity中覆寫該方法:

@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
  return super.shouldShowRequestPermissionRationale(permission);
}複製程式碼

easyPermission:

前面我們已經講了如何用沒有使用封裝的庫直接用原生API來進行許可權申請。接下去我們要介紹easyPermisson;
相關github連結:easyPermisson

這個庫是谷歌推出的,所以我也使用過這個。怎麼使用這個庫我就不多說了。我們直接來看相關的原始碼分析。

我們直接看他官方給的Demo:

1. 判斷是否有許可權:

EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA);
比如查詢是否有相機許可權,我們繼續看具體的hasPermissions方法的內容:

public static boolean hasPermissions(Context context, @NonNull String... perms) {
    // Always return true for SDK < M, let the system deal with the permissions
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        Log.w(TAG, "hasPermissions: API version < M, returning true by default");

        // DANGER ZONE!!! Changing this will break the library.
        return true;
    }

    // Null context may be passed if we have detected Low API (less than M) so getting
    // to this point with a null context should not be possible.
    if (context == null) {
        throw new IllegalArgumentException("Can't check permissions for null context");
    }

    for (String perm : perms) {
        if (ContextCompat.checkSelfPermission(context, perm)
                != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
    }

    return true;
}複製程式碼

我們可以看到,先會判斷SDK是否是小於android 6.0,如果是那麼直接返回true,因為6.0以下不需要動態申請;然後我們發現它這裡接下去檢查是否有許可權用的就是我們上面提到過的谷歌API中的ContextCompat.checkSelfPermission()方法,這個方法上面已經介紹過了。這裡也不多說了。可以文章往上拉一下看看。

2. 申請許可權:

前提,我們假設是在Activity中去申請相關許可權(PS:如果是fragment的話,一些方法的呼叫不同,但是原理是差不多的。)

private static final int RC_CAMERA_PERM = 123;

EasyPermissions.requestPermissions(
        this,
        getString(R.string.rationale_camera),
        RC_CAMERA_PERM,
        Manifest.permission.CAMERA);複製程式碼

我們一步步分析下去:

/**
 * Request permissions from an Activity with standard OK/Cancel buttons.
 *
 * @see #requestPermissions(Activity, String, int, int, int, String...)
 */
public static void requestPermissions(
        @NonNull Activity host, @NonNull String rationale,
        int requestCode, @NonNull String... perms) {

    requestPermissions(host, rationale, android.R.string.ok, android.R.string.cancel,
            requestCode, perms);
}複製程式碼

我們看見呼叫了另外一個requestPermissions方法,繼續看下去:

/**
 * Request permissions from a Support Fragment.
 *
 * @see #requestPermissions(Activity, String, int, int, int, String...)
 */
public static void requestPermissions(
        @NonNull Fragment host, @NonNull String rationale,
        @StringRes int positiveButton, @StringRes int negativeButton,
        int requestCode, @NonNull String... perms) {

    requestPermissions(PermissionHelper.newInstance(host), rationale,
            positiveButton, negativeButton,
            requestCode, perms);
}複製程式碼

在這裡,我們看到它通過PermissionHelper.newInstance(host)生成了一個PermissionHelper物件。我們繼續檢視接下去呼叫的requestPermissions方法:

private static void requestPermissions(
    @NonNull PermissionHelper helper, @NonNull String rationale,
    @StringRes int positiveButton, @StringRes int negativeButton,
    int requestCode, @NonNull String... perms) {

    // Check for permissions before dispatching the request
    if (hasPermissions(helper.getContext(), perms)) {
        notifyAlreadyHasPermissions(helper.getHost(), requestCode, perms);
        return;
    }

    // Request permissions
    helper.requestPermissions(rationale, positiveButton,
            negativeButton, requestCode, perms);
}複製程式碼

我們可以看到這裡面用到了我們上面新生成的PermissionHelper物件的相關方法,在申請許可權前,又呼叫了一次hasPermissions方法來判斷下是否已經有該許可權,如果有就直接return出這個方法。然後沒有該許可權,就呼叫helper的方法

helper.requestPermissions(rationale, positiveButton,
        negativeButton, requestCode, perms);複製程式碼

所以我們知道了接下去肯定是呼叫了PermissionHelper裡面相關的方法,那我們上面建立該物件的時候是用

PermissionHelper.newInstance(host)複製程式碼

所以我們看下它具體生成物件的方法:

 @NonNull
public static PermissionHelper newInstance(Activity host) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        return new LowApiPermissionsHelper(host);
    }

    if (host instanceof AppCompatActivity) {
        return new AppCompatActivityPermissionHelper((AppCompatActivity) host);
    } else {
        return new ActivityPermissionHelper(host);
    }
}複製程式碼

我們發現當小於Android 6.0的時候,返回的是LowApiPermissionsHelper,而這個類裡面的相關方法,都是丟擲異常,比如說:

@Override
public void showRequestPermissionRationale(
        @NonNull String rationale,int positiveButton, 
        int negativeButton,int requestCode,
        @NonNull String... perms) {

        throw new IllegalStateException("Should never be requesting permissions on API < 23!");

    }複製程式碼

而我們在Android 6.0以上獲取到的就是AppCompatActivityPermissionHelper物件或者ActivityPermissionHelper物件。
我們這裡因為Activity繼承AppCompatActivity,所以我們這裡生成的是是AppCompatActivityPermissionHelper物件。
我們檢視到呼叫到的PermissionHelper中的requestPermission方法:

public void requestPermissions(@NonNull String rationale,
                       @StringRes int positiveButton,
                       @StringRes int negativeButton,
                       int requestCode,
                       @NonNull String... perms) {

    if (shouldShowRationale(perms)) {
        showRequestPermissionRationale(
                rationale, positiveButton, negativeButton, requestCode, perms);
    } else {
        directRequestPermissions(requestCode, perms);
    }
}複製程式碼

我們可以看到在else的程式碼塊中,最後呼叫了AppCompatActivityPermissionHelperdirectRequestPermissions方法(if中程式碼塊的內容放在4.請求許可權提示中具體講解):

@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
    ActivityCompat.requestPermissions(getHost(), perms, requestCode);
}複製程式碼

我們就發現了,最終它的申請許可權的核心程式碼還是呼叫了我們上面介紹過的谷歌的API的ActivityCompat.requestPermissions()方法。

3.回撥事件處理:

 @Override
public void onRequestPermissionsResult(int requestCode,
                           @NonNull String[] permissions,
                           @NonNull int[] grantResults) {

    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    // EasyPermissions handles the request result.
    EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }複製程式碼

我們可以看到在onRequestPermissionsResult回撥方法中,呼叫

EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);複製程式碼

ps:這裡介紹下第四個引數:

an array of objects that have a method annotated with {@link AfterPermissionGranted} or implement {@link PermissionCallbacks}複製程式碼

需要有註解AfterPermissionGranted的方法,或者是實現了PermissionCallbacks介面。我們這邊是直接在Activity中實現了PermissionCallbacks介面,所以只需要傳入this即可。

public interface PermissionCallbacks extends ActivityCompat.OnRequestPermissionsResultCallback {
    void onPermissionsGranted(int requestCode, List<String> perms);
    void onPermissionsDenied(int requestCode, List<String> perms);
}複製程式碼

我們具體的看EasyPermissions.onRequestPermissionsResult這個方法的程式碼:

public static void onRequestPermissionsResult(int requestCode,
                                  @NonNull String[] permissions,
                                  @NonNull int[] grantResults,
                                  @NonNull Object... receivers){

    // Make a collection of granted and denied permissions from the request.
    List<String> granted = new ArrayList<>();
    List<String> denied = new ArrayList<>();
    for (int i = 0; i < permissions.length; i++) {
        String perm = permissions[i];
        if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
            granted.add(perm);
        } else {
            denied.add(perm);
        }
    }

    // iterate through all receivers
    for (Object object : receivers) {
        // Report granted permissions, if any.
        if (!granted.isEmpty()) {
            if (object instanceof PermissionCallbacks) {
                ((PermissionCallbacks) object).onPermissionsGranted(requestCode, granted);
            }
        }

        // Report denied permissions, if any.
        if (!denied.isEmpty()) {
            if (object instanceof PermissionCallbacks) {
                ((PermissionCallbacks) object).onPermissionsDenied(requestCode, denied);
            }
        }

        // If 100% successful, call annotated methods
        if (!granted.isEmpty() && denied.isEmpty()) {
            runAnnotatedMethods(object, requestCode);
        }
    }
}複製程式碼

我們可以看到新建了二個ArrayList(granted 和 denied),分別存放通過的許可權及拒絕的許可權,當granted裡面的個數不為0,則呼叫PermissionCallbacksonPermissionsGranted(requestCode, granted);如果denied不為空的時候,則呼叫PermissionCallbacksonPermissionsDenied(requestCode, granted);然後如果granted不為空,同時denied為空,說明所申請的許可權全部被同意了。則呼叫被@AfterPermissionGranted修飾的方法。

4.請求許可權提示:

我們在用easyPermission申請許可權的時候:

EasyPermissions.requestPermissions(
            this,
            getString(R.string.rationale_location_contacts),
            RC_LOCATION_CONTACTS_PERM,
            LOCATION_AND_CONTACTS);複製程式碼

我們可以看到我們在第二個引數傳了一個字串。這個字串就是當我們的許可權被拒絕後,用來提示使用者時候顯示的文字。我們可以仔細看下原理,我們再回到PermissionrequestPermission方法中:

public void requestPermissions(@NonNull String rationale,
                       @StringRes int positiveButton,
                       @StringRes int negativeButton,
                       int requestCode,
                       @NonNull String... perms) {
    if (shouldShowRationale(perms)) {
        showRequestPermissionRationale(
                rationale, positiveButton, negativeButton, requestCode, perms);
    } else {
        directRequestPermissions(requestCode, perms);
    }
}複製程式碼

我們可以看到,如果shouldShowRationale方法返回false,才會去呼叫我們的申請許可權的方法,我們看下shouldShowRationale

public boolean shouldShowRationale(@NonNull String... perms) {
    for (String perm : perms) {
        if (shouldShowRequestPermissionRationale(perm)) {
            return true;
        }
    }
    return false;
}複製程式碼

shouldShowRequestPermissionRationale呼叫了

@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
    return ActivityCompat.shouldShowRequestPermissionRationale(getHost(), perm);
}複製程式碼

我們發現最終呼叫了ActivityCompat.shouldShowRequestPermissionRationale,所以只要有一個許可權被拒絕了後,就會返回true,然後呼叫:

showRequestPermissionRationale(
    rationale, positiveButton, negativeButton, requestCode, perms);複製程式碼

我們具體看程式碼,是在BaseSupportPermissionsHelper.java中:

public abstract class BaseSupportPermissionsHelper<T> extends PermissionHelper<T> {

    public BaseSupportPermissionsHelper(@NonNull T host) {
        super(host);
    }

    public abstract FragmentManager getSupportFragmentManager();

    @Override
    public void showRequestPermissionRationale(@NonNull String rationale,
                           int positiveButton,
                           int negativeButton,
                           int requestCode,
                           @NonNull String... perms) {

        RationaleDialogFragmentCompat
                .newInstance(positiveButton, negativeButton, rationale, requestCode, perms)
                .show(getSupportFragmentManager(), RationaleDialogFragmentCompat.TAG);
    }
}複製程式碼

在這裡最後彈出一個彈框,彈框的內容就是我們傳入的字串,然後“確定”按鈕設定了點選事件:

@Override
public void onClick(DialogInterface dialog, int which) {
    if (which == Dialog.BUTTON_POSITIVE) {
        if (mHost instanceof Fragment) {
            PermissionHelper.newInstance((Fragment) mHost).directRequestPermissions(
                    mConfig.requestCode, mConfig.permissions);
        } else if (mHost instanceof android.app.Fragment) {
            PermissionHelper.newInstance((android.app.Fragment) mHost).directRequestPermissions(
                    mConfig.requestCode, mConfig.permissions);
        } else if (mHost instanceof Activity) {
            PermissionHelper.newInstance((Activity) mHost).directRequestPermissions(
                    mConfig.requestCode, mConfig.permissions);
        } else {
            throw new RuntimeException("Host must be an Activity or Fragment!");
        }
    } else {
        notifyPermissionDenied();
    }
}複製程式碼

當使用者按了確認按鈕後,重新去進行許可權的申請方法:

PermissionHelper.newInstance((Activity) mHost).directRequestPermissions(
       mConfig.requestCode, mConfig.permissions);複製程式碼

RxPermissions

RxPermissions是我第二個使用的許可權申請庫,因為專案中使用了Rxjava2,所以裡面也就使用了基於Rxjava2的RxPermissions。我們也還是一樣來看RxPermission是怎麼封裝的。

我們直接看申請許可權的完整程式碼:

RxPermissions rxPermissions = new RxPermissions(this);
rxPermissions.request(Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.READ_PHONE_STATE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_EXTERNAL_STORAGE
    ).subscribe(new Observer<Boolean>() {
        @Override
        public void onSubscribe(Disposable d) {

        }

        @Override
        public void onNext(Boolean aBoolean) {
            if (aBoolean) {
                BaseToast.info("獲取相應應用許可權成功");
            } else {
                BaseToast.error("獲取相應應用許可權失敗");
            }
        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onComplete() {

        }
    });複製程式碼

PS:這裡我使用request 來分析,request是申請多個許可權時候,比如我們申請三個,就要這三個都被使用者同意後,才會返回true,但是我們也可以使用requestEach來分別對每個許可權的申請結果來進行處理

第一步:

RxPermissions rxPermissions = new RxPermissions(this);
我們可以看到我們例項化了RxPermissions的物件了。

第二步:

rxPermissions.request(Manifest.permission.ACCESS_COARSE_LOCATION,
    Manifest.permission.ACCESS_FINE_LOCATION,
    Manifest.permission.READ_PHONE_STATE,
    Manifest.permission.WRITE_EXTERNAL_STORAGE,
    Manifest.permission.READ_EXTERNAL_STORAGE
)複製程式碼

我們跟進去看request裡面做了什麼處理:

public Observable<Boolean> request(final String... permissions) {
    return Observable.just(TRIGGER).compose(ensure(permissions));
}複製程式碼

我們可以看到這個方法最後返回了一個Observable<Boolean>物件,而這個Boolean值就是我們最後是不是把所有申請的許可權都同意的結果值,如果都同意則返回true,否則返回false;我們可以看到:

static final Object TRIGGER = new Object();
Observable.just(TRIGGER);複製程式碼

建立一個Observable用來發射資訊,資訊內容是一個Object物件,這裡只是單純為了建立一個Observable而已,所以發射什麼內容無所謂。Rxjava1中的是直接Observable.just(null),但是在Rxjava2中這麼寫是會報錯的,所以這裡直接發射了一個Object物件。

Observable.just(TRIGGER).compose(ensure(permissions));複製程式碼

我們可以看到把我們原本發射物件Object的Observable變為了Observable<Boolean>。所以就算我先不講compose操作符的作用,大家也都能猜到是用來轉換Observable。


我們可以看到API中Compose的介紹:通過一個特定的Transformer函式來轉換Observable。

所以我們也就可以猜到我們程式碼中的ensure(permissions)是一個Transformer。

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) throws Exception {
                            if (permissions.isEmpty()) {
                                // Occurs during orientation change, when the subject receives onComplete.
                                // In that case we do not 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);
                        }
                    });
        }
    };
}複製程式碼

我們可以看到返回了一個ObservableTransformer物件,用來轉換Observable的。我們繼續看裡面的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) throws Exception {
                    return requestImplementation(permissions);
                }
            });
}複製程式碼

可以看到如果我們申請的許可權為空或者個數為0,則丟擲異常,否則返回一個Observable<Permission>

我們繼續看oneOf(trigger, pending(permissions))方法:

private Observable<?> oneOf(Observable<?> trigger, Observable<?> pending) {
    if (trigger == null) {
        return Observable.just(TRIGGER);
    }
    return Observable.merge(trigger, pending);
}複製程式碼

其實這邊的oneOf方法是判斷如果觸發的Observable為空,則直接返回一個發射Object的Observable,不然就合併觸發的Observablepending這個Observablepending這個Observable是由pending(permissions)生成的。

pending方法:

private Observable<?> pending(final String... permissions) {
    for (String p : permissions) {
        if (!mRxPermissionsFragment.containsByPermission(p)) {
            return Observable.empty();
        }
    }
    return Observable.just(TRIGGER);
}複製程式碼

這裡我們發現把我們傳入的許可權字串,拿到了mRxPermissionsFragment去判斷:

public PublishSubject<Permission> setSubjectForPermission(@NonNull String permission, @NonNull PublishSubject<Permission> subject) {
    return mSubjects.put(permission, subject);
}複製程式碼

mRxPermissionsFragment中維護了一個HashMap集,裡面維護了一個key為許可權字串,value為每個許可權相對於的Observable的鍵值對。這樣如果我們重複申請某個許可權的時候,我們直接返回了一個建立一個不發射任何資料但是正常終止的ObservableObservable.empty(),不然就返回一個傳送Object的Observable
所以這裡oneOf方法最終的結果是:二個Observable.just(TRIGGER)合併傳送,或者一個Observable.just(TEIGGER)與一個Observable.empty()合併,也就是傳送二次Object,或者傳送一次Object(因為empty不傳送)。

因為後面在對某個許可權申請做同意或者拒絕的時候,就會把這個許可權的key-value從mRxPermissionsFragment的HashMap中移除,所以這邊獲得到的一直就是Observable.just(TEIGGER)與一個Observable.empty()合併。

oneOf我們分析過了,好了我們回頭再來看:

oneOf(trigger, pending(permissions))
    .flatMap(new Function<Object, Observable<Permission>>() {
        @Override
        public Observable<Permission> apply(Object o) throws Exception {
            return requestImplementation(permissions);
        }
    });複製程式碼

我們知道了oneOf返回的是一個Observable,所以我們看到了程式碼對Observable進行了flatMap操作,把我們的Observable變成了新的Observable來傳送。具體的是requestImplementation(permissions);

PS:所以我覺得這裡的oneOf的功能有點問題,因為前面這樣各種判斷去獲取一個Observable,到後面還是會被flatmap給替換掉,所以這裡我覺得不用OneOf函式去獲取然後再呼叫flatMap,而是直接就用requestImplementation(permissions)這個Observable我覺得就可以了。

我們具體來看下requestImplementation方法的實現:

@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.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.getSubjectByPermission(permission);
        // Create a new subject if not exists
        if (subject == null) {
            unrequestedPermissions.add(permission);
            subject = PublishSubject.create();
            mRxPermissionsFragment.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));
}複製程式碼

我們來分析上面這段核心程式碼:

1. 判斷是否有許可權:

其中Rxjava的授權與否的判斷程式碼詳情:

public boolean isGranted(String permission) {
    return !isMarshmallow() || mRxPermissionsFragment.isGranted(permission);
}

@SuppressWarnings("WeakerAccess")
public boolean isRevoked(String permission) {
    return isMarshmallow() && mRxPermissionsFragment.isRevoked(permission);
}

boolean isMarshmallow() {
    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}複製程式碼

mRxPermissionsFragment.isGranted(permission)mRxPermissionsFragment.isRevoked(permission)的程式碼:

 @TargetApi(Build.VERSION_CODES.M)
boolean isGranted(String permission) {
    return getActivity().checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
}

@TargetApi(Build.VERSION_CODES.M)
boolean isRevoked(String permission) {
    return getActivity().getPackageManager().isPermissionRevokedByPolicy(permission, getActivity().getPackageName());
}複製程式碼

所以判斷許可權用的原生的API:getActivity().checkSelfPermission(permission),這裡和easyPermission沒什麼不同;但是在判斷許可權處於拒絕狀態就不一樣了,
easyPermission用的是

getActivity().checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED;複製程式碼

而RxPermission用的是getActivity().getPackageManager().isPermissionRevokedByPolicy(permission, getActivity().getPackageName())

2.生成相應的不同許可權的Observable:

我們現在已經根據是否授權,把相關的許可權進行區別,然後分別生成 放到了二個ArrayList中:

List<Observable<Permission>> list = new ArrayList<>(permissions.length);
List<String> unrequestedPermissions = new ArrayList<>();

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.getSubjectByPermission(permission);
// Create a new subject if not exists
if (subject == null) {
    unrequestedPermissions.add(permission);
    subject = PublishSubject.create();
    mRxPermissionsFragment.setSubjectForPermission(permission, subject);
}

list.add(subject);複製程式碼

我們可以看到ArrayList裡面新增了發射內容為Permission物件的Observable。而這個Permission中的第二個引數所對應的屬性就是用來標記是否已經處於同意狀態。然後如果有許可權還處於帶詢問狀態(既沒有同意有沒有拒絕),則新建一個Observable,並且加入到了我們mRxPermissionsFragment中提過的HashMap中,以便後面可以重複使用。
最後我們的List<Observable<Permission>> list = new ArrayList<>(permissions.length);裡面,放了已經標記了通過的PermissionObservable,被拒絕了的PermissionObservable,還有待詢問的的新建的Observable。這時候我們如果真的還有待詢問的Observable,則呼叫requestPermissionsFromFragment方法去申請許可權:

@TargetApi(Build.VERSION_CODES.M)
void requestPermissionsFromFragment(String[] permissions) {
    mRxPermissionsFragment.log("requestPermissionsFromFragment " + TextUtils.join(", ", permissions));
    mRxPermissionsFragment.requestPermissions(permissions);
}

也就是最後呼叫了RxPermissionsFragment的申請許可權的功能:
@TargetApi(Build.VERSION_CODES.M)
void requestPermissions(@NonNull String[] permissions) {
    requestPermissions(permissions, PERMISSIONS_REQUEST_CODE);
}複製程式碼

所以這一塊我們總結一下,就是遍歷我們傳入的申請的許可權字串,然後去判斷:

  1. 如果這個申請的許可權前面已經同意過了。就直接標記一個Permission物件,包含了申請許可權的name值,是否同意的Boolean值,並且為true。
  2. 如果這個申請許可權已經被拒絕了,就直接標記一個Permission物件,包含了申請許可權的name值,是否同意的Boolean值,並且為false。
  3. 如果直接這個申請的許可權是詢問狀態,新建一個Observable,並且會根據申請許可權的name為key儲存到mRxPermissionsFragment中的HashMap中,為什麼要存進去呢,因為這時候要 呼叫Fragment的requestPermissions方法,這時候手機就會出現申請許可權的申請框。如果使用者選擇了通過或者拒絕,這時候我們就要把這個Observable傳送相應的同意的Permission物件或者拒絕的Permission物件。

然後返回了:Observable.concat(Observable.fromIterable(list));

PS:Observable.fromIterable(Iterable<? extends T> source)
此方法接收一個繼承自Iterable介面的引數,簡單的說就是java中的集合類。因此你可以傳入一個list集合等等。

最後通過Observable.concatObservable.fromIerable生成的多資料原合併在一起變為一個Observable

這時候我們就又回到了最剛開始的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) throws Exception {
                            if (permissions.isEmpty()) {
                                // Occurs during orientation change, when the subject receives onComplete.
                                // In that case we do not 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);
                        }
                    });
        }
    };
}複製程式碼

這時候我們的request(o, permissions)已經瞭解完了。按照原來的設定是會發射一個帶有不同結果的Permission物件的Observable;我們可以看到了這裡用到了buffer操作符,buffer英文是緩衝區的意思。所以Buffer操作符所要做的事情就是將資料按照規定的大小做一下快取,然後將快取的資料作為一個集合發射出去。所以在我們下面的flatMap中的new Function的第一個引數是一個集合List<Permission>。然後就是遍歷集合,看裡面的Permissiongranted屬性是true還是false,只要有一個false,就返回Observable.just(false),否則就Observable.just(true)

然後我們最終在Activity的程式碼就等效於(假設最後都同意申請的許可權):

Observable.just(true)
        .subscribe(new Observer<Boolean>() {
            @Override
            public void onSubscribe(Disposable d) {

            }

            @Override
            public void onNext(Boolean aBoolean) {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onComplete() {

            }
    });複製程式碼

題外話:
關於rxPermissions.requestEach()只是它最後沒有用bufferflatMap拼在一起去判斷然後是否傳送Observable.just(true)或者是Observable.just(false)傳送,而是把Permission的結果一個個傳送出來。所以接受到的是Permission物件,所以我們可以針對Permission物件一個個去處理結果:

rxPermissions.requestEach(Manifest.permission.CAMERA,
    Manifest.permission.READ_PHONE_STATE)
    .subscribe(new Observer<Permission>() {
        @Override
        public void onSubscribe(Disposable d) {

        }

        @Override
        public void onNext(Permission permission) {

        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onComplete() {

        }
});複製程式碼


PS:最後提到一個大坑,那就是不同系統的國產手機,申請許可權的時候,有時候提示成功了。可是還是沒有獲取到,明明拒絕了,可能還是獲取成功。畢竟國內的手機廠家太多。我也就這邊提一下。不知道大家遇到過沒有。

最後的最後,寫完了。。如果哪裡寫錯了。希望大家輕點噴。哈哈。。可以留言指出。

相關文章