動態許可權的使用以及RxPermissions原始碼分析

chenToV發表於2018-04-03

基本使用

Android6.0之後,增加了動態許可權配置,目的在於使用者可以自由的選擇自己是否給予app許可權,就算沒有給予某個許可權,也不影響其他功能的使用,不至於令使用者無法安裝

接下來先看一下基本的使用,程式碼如下:

// 檢測是否授予了CALL_PHONE這個許可權
if (ContextCompat.checkSelfPermission(MainActivity@ this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
    // 還未授予,則申請許可權
    ActivityCompat.requestPermissions(MainActivity@ this, arrayOf(Manifest.permission.CALL_PHONE), 0x111)
} else {
    // 已經授予,則進行相關操作
    call()
}

// 該方法是申請許可權後的回撥方法
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    when (requestCode) {
        // 請求碼對應
        0x111 -> {
            // 判斷申請許可權是否成功
            if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 成功申請,執行相關操作
                call()
            } else {
                // 申請失敗,提示使用者
                Toast.makeText(this, "您拒絕了該許可權", Toast.LENGTH_SHORT).show()
            }
        }
        else -> {

        }
    }
}

// 許可權申請成功後的相關操作
private fun call() {
    try {
        val intent = Intent(Intent.ACTION_CALL)
        intent.setData(Uri.parse("tel:10086"))
        startActivity(intent)
    } catch (e: SecurityException) {
        e.printStackTrace()
    }
}
複製程式碼

以上是申請許可權的基本操作,操作的方式其實類似於startActivityForResult()這種啟動Activity的方法,也是會有具體的回撥方法,但是如果每次都需要這樣寫,未免太過於繁瑣,所以一般情況都需要對其進行封裝,而RxPermissions這個庫就對其進行了很好的封裝,下面就使用RxPermissions進行實現

使用之前需要在專案中新增相關依賴

implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
複製程式碼

下面是詳細實現程式碼

// 定義一個RxPermission物件
private lateinit var mRxPermission:RxPermissions

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // 建立Permission物件
    mRxPermission = RxPermissions(this)
    // 申請許可權
    mRxPermission.request(Manifest.permission.CALL_PHONE).subscribe(object :Action1<Boolean>{
        override fun call(t: Boolean?) {
            // 回撥之後的操作
            if (t!!){
                call()
            }else{
                Toast.makeText(this@MainActivity, "您拒絕了該許可權", Toast.LENGTH_SHORT).show()                  }
            }
    })
複製程式碼

相比一開始的程式碼,利用RxPermissions寫的簡潔了很多,也無需重寫onRequestPermissionsResult()方法,這就是使用了該庫的好處,節省了我們的程式碼量,也使程式碼變得更加清晰有條理,那麼它是怎麼做到的呢,下面就對其原始碼進行解讀

1、其實,RxPermission採用的方式是利用了一個隱形的Fragment來請求許可權,然後在回撥中用RxJava進行資料的組裝和轉化,最後變成了布林型別的資料回撥回來,下面是具體的分析

// 建立RxPermissions的物件
mRxPermission = RxPermissions(this)

// 定義RxPermissions的物件
RxPermissionsFragment mRxPermissionsFragment;

// RxPermissions的構造方法
public RxPermissions(@NonNull Activity activity) {
    // 獲取Fragment的例項
    mRxPermissionsFragment = getRxPermissionsFragment(activity);
}

// 獲取Fragment的方法
private RxPermissionsFragment getRxPermissionsFragment(Activity activity) {
    // 查詢是否已經存在了該Fragment,這樣是為了讓該Fragment只有一個例項
    RxPermissionsFragment rxPermissionsFragment = findRxPermissionsFragment(activity);
    boolean isNewInstance = rxPermissionsFragment == null;
    // 如果還沒有存在,則建立Fragment,並新增到Activity中
    if (isNewInstance) {
        rxPermissionsFragment = new RxPermissionsFragment();
        FragmentManager fragmentManager = activity.getFragmentManager();
        fragmentManager
            .beginTransaction()
            .add(rxPermissionsFragment, TAG)
            .commitAllowingStateLoss();
        fragmentManager.executePendingTransactions();
    }
    return rxPermissionsFragment;
}

// 利用tag去找是否已經有該Fragment的例項
private RxPermissionsFragment findRxPermissionsFragment(Activity activity) {
    return (RxPermissionsFragment) activity.getFragmentManager().findFragmentByTag(TAG);
}
複製程式碼

通過以上的程式碼可以知道,在建立RxPermissions的物件中,其實就是獲取Fragment的例項而已,既然這樣,我們就需要到這個Fragment的實現中,看它在被建立的時候做了什麼事情

// Fragment的構造方法
public RxPermissionsFragment() {
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 禁止橫豎屏切換時的Fragment的重建
    setRetainInstance(true);
}
複製程式碼

可以看到,在Fragment被建立時,並沒有重寫onCreateView()方法,來進行佈局檔案的載入,只是重寫了onCreate()方法,然後禁止了橫豎屏Fragment的重建,這就代表說,這個Fragment是一個沒有佈局的隱形Fragment,不會在螢幕上展示出來,但是這個Fragment卻是關鍵,許可權的申請與申請結果的回撥都是在Fragment中完成的,這樣,我們才不需要為申請結果重寫回撥方法

2、接下來是具體的請求

mRxPermission.request(Manifest.permission.CALL_PHONE).subscribe(object :Action1<Boolean>{
    override fun call(t: Boolean?) {
        if (t!!){
            call()
        }else{
            Toast.makeText(this@MainActivity, "您拒絕了該許可權", Toast.LENGTH_SHORT).show()
        }
    }

})
複製程式碼

上面的程式碼主要是呼叫RxPermissions物件中的request()方法,然後該方法會返回一個Observable,下面看具體的request()方法程式碼

@SuppressWarnings({"WeakerAccess", "unused"})
public Observable<Boolean> request(final String... permissions) {
    return Observable.just(null).compose(ensure(permissions));
}

複製程式碼

首先,利用just(null)方法建立Observable物件 接下來會呼叫compose()方法。compose()的引數就為Observable.Transformer物件,該物件的作用是將一個型別的Observable物件轉換成另外一個型別的Observable物件,同時也可以對自身物件的一些重複操作進行封裝,避免重複編寫程式碼

public Observable.Transformer<Object, Boolean> ensure(final String... permissions) {
    return new Observable.Transformer<Object, Boolean>() {
        @Override
        public Observable<Boolean> call(Observable<Object> o) {
            return request(o, permissions)
            // 將Observable<Permission>轉換成Observable<Boolean>
            .buffer(permissions.length)
            .flatMap(new Func1<List<Permission>, Observable<Boolean>>() {
                @Override
                public Observable<Boolean> call(List<Permission> permissions) {
                    if (permissions.isEmpty()) {
                        // 如果申請的許可權列表為空,則返回一個只回撥onComplete()方法的Observable物件
                        return Observable.empty();
                    }
                    // 迴圈許可權列表,判斷許可權是否申請成功
                    for (Permission p : permissions) {
                        if (!p.granted) {
                            return Observable.just(false);
                        }
                    }
                    return Observable.just(true);
                }
            });
        }
    };
}
複製程式碼

上面程式碼的作用是傳入一個Observable物件,然後轉換成一個Observable物件,而這個Observable物件就是前面Observable.just(null)所建立的物件,同時ensure()方法傳入了permissions許可權申請列表,在轉換的初步,就是需要根據permissions許可權列表去申請許可權,具體操作在request()方法中,下面看看request()的實現

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");
    }
    // 首先,合併了兩個Observable物件,然後通過flatMap將Object物件轉換成Observable<Permission>物件
    return oneOf(trigger, pending(permissions))
            .flatMap(new Func1<Object, Observable<Permission>>() {
                @Override
                public Observable<Permission> call(Object o) {
                    return requestImplementation(permissions);
                }
            });
    }
複製程式碼

通過上面程式碼可以看出,首先會判斷申請許可權列表是否為空,如果為空就會丟擲異常,然後通過oneOf方法和pending()方法來建立合併Observable物件,下面看一下這兩個方法

private Observable<?> pending(final String... permissions) {
    // 迴圈遍歷,查詢該許可權是否已經在申請過了
    for (String p : permissions) {
        if (!mRxPermissionsFragment.containsByPermission(p)) {
            // 如果列表中有一個許可權未在Fragment的HashMap集合中儲存
            // 則返回Observeble.empty(),返回的這個Observable物件
            // 只會呼叫onComplete()方法,所以並不會進入flatMap等操作// 符號中
            return Observable.empty();
        }
    }
    return Observable.just(null);
}

private Observable<?> oneOf(Observable<?> trigger, Observable<?> pending) {
    // 判斷Observable物件是否為null,在這裡其實是不太可能會為null的
    if (trigger == null) {
        return Observable.just(null);
    }
    //返回合併的Observable物件
    return Observable.merge(trigger, pending);
}
複製程式碼

從以上的兩個方法的程式碼可以看出,pending()主要是判斷許可權申請列表中是否全部都在Fragment中的mSubjects集合中,如果有一個不在集合中,則返回Observable.empty()方法,如果已經全部在集合中,則返回Observable.just(null),然後在oneOf()方法中根據trigger是否為null來判斷是返回Observable.just(null)還是Observable.merge(trigger, pending)。這兩個方法在我看來最終並沒有起到實際的作用,因為具體的請求許可權是在requestImplementation(permissions)方法中實現的,而通過以上兩個方法得到的Observable物件最終在flatMap操作轉換時並不會用到它們的物件,而是直接根據許可權申請列表“permissions”作為引數,直接呼叫requestImplementation()方法,進行許可權的實際請求,所以接下來主要看看requestImplementation()方法的具體實現

private Observable<Permission> requestImplementation(final String... permissions) {
    // 建立集合儲存已經申請的許可權
    List<Observable<Permission>> list = new ArrayList<>(permissions.length);
    // 建立集合儲存未申請的許可權
    List<String> unrequestedPermissions = new ArrayList<>();

    // 迴圈許可權申請列表
    for (String permission : permissions) {
        mRxPermissionsFragment.log("Requesting permission " + permission);
        // 如果許可權已經申請過了,則直接儲存到集合中
        if (isGranted(permission)) {
            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;
        }

        // 先去RxPermissionsFragment中查詢是否已經存在了該許可權
        PublishSubject<Permission> subject = mRxPermissionsFragment.getSubjectByPermission(permission);
        // Create a new subject if not exists
        // 如果還未存在,則建立一個PublishSubject物件
        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);
    }
    // 利用list集合建立Observable物件,並且用concat進行連結,返回一個Observable<Permission>物件
    return Observable.concat(Observable.from(list));
}
複製程式碼

上面的就是進行許可權申請的具體程式碼,主要的操作就是定義一個集合儲存儲存一次申請的所有許可權,無論這個許可權是否已經申請,還是被撤銷,還是未申請,最終都會儲存到list這個集合中,這樣,我們在後續的操作中,才可以進行轉換,同時,定義一個集合,用於儲存未申請的許可權,然後在迴圈結束之後進行未申請許可權的申請。接下來看看requestPermissionsFromFragment()放到,它最終呼叫的還是mRxPermissionsFragment.requestPermissions(permissions)方法,就是RxPermissionsFragment中的requestPermissions方法了,所以我們進到該方法看看

void requestPermissions(@NonNull String[] permissions) {
    requestPermissions(permissions, PERMISSIONS_REQUEST_CODE);
}
複製程式碼

可以看到,該方法並沒有進行什麼特別操作,就是申請許可權,那麼既然申請了許可權了,是否申請成功的處理應該在回撥中,所以我們看看回撥的處理

public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    
    // 如果請求碼不符合,則直接返回
    if (requestCode != PERMISSIONS_REQUEST_CODE) return;

    // 以許可權列表的長度為容器的size建立一個boolean陣列
    boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length];
    
    // 迴圈遍歷,看許可權是否被永久拒絕了
    for (int i = 0; i < permissions.length; i++) {
        // 這裡會呼叫Fragment中的shouldShowRequestPermissionRationale()方法,然後這個方法如果是申請成功會返回true,如果被點選了不在提醒,並且拒絕許可權時,則會返回false
        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
        // 查詢mSubjects集合中是否存在代表該permission的PublishSubject物件
        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;
        }
        // 將集合中的permission的PublishSubject物件進行移除
        mSubjects.remove(permissions[i]);
        // 判斷是否申請成功
        boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
        // 返回相應的物件
        subject.onNext(new Permission(permissions[i], granted, shouldShowRequestPermissionRationale[i]));
        subject.onCompleted();
    }
}
複製程式碼

上面的程式碼是回撥處理許可權申請的全部過程,主要用到了兩個方法,第一個方法主要用於建立一個boolean陣列,用於儲存判斷許可權申請的狀態,如果許可權被申請,並且使用者在對話方塊中點選了“不在提醒”選項,這個時候shouldShowRequestPermissionRationale()這個方法還是會返回false,代表說這個許可權被拒絕,我們可以根據這個在給使用者做一些提示性的工作。然後就到了過載的方法了,在過載的方法中,首先判斷在mSubjects集合中是否已經存在了該permission的PublishSubject物件,如果沒有,則直接返回,代表出錯,但是這裡的物件在RxPermissions#requestImplementation()中已經通過mRxPermissionsFragment.setSubjectForPermission(permission, subject);語句進行賦值了,所以一定會是存在的。接著mSubjects集合將該permission的PublishSubjects物件移除,這是為什麼呢,這個要看RxPermissions#requestImplementation()中的實現,在該方法中會PublishSubject subject = mRxPermissionsFragment.getSubjectByPermission(permission);這個語句出現,這個語句代表說,如果許可權未申請過,也未被撤銷,那麼就直接在RxPermissionsFragment的mSubjects集合中查詢是否存在該permission的PublishSubject物件,如果有,就直接通過list.add(subject);這個語句儲存到集合中了,那麼這樣被拒絕的許可權,下次將不會被重新申請,所以需要移除,之後就是發射資料,傳送時間結束標識了

分析完了RxPermissionsFragment中的許可權申請的操作,我們就需要回到Rxpermissions#request()方法中,在該方法的flatMap的回撥方法中返回了Observable物件,因此這個方法的任務完成,接著再往回看,在ensure()方法中,我們可以看到在該方法中呼叫了request()方法之後,接著呼叫了buffer(permissions.length)方法,其實就是Observable物件呼叫了buffer()方法,那麼這個buffer()的作用是什麼呢,它的作為是為了將一個序列的Observable物件轉換成Observable<List>物件,為什麼要這樣轉換,就需要看requestImplementation()方法中的返回值,該方法返回值為Observable.concat(Observable.from(list));這代表說這是一個Observable物件序列,所以需要通過buffer()方法進行轉換,接著就使用flatMap操作符轉換成Observable物件,看看程式碼

@Override
public Observable<Boolean> call(List<Permission> permissions) {
   // 如果集合為空,代表說沒有許可權申請,直接返回Observable.empty()
   if (permissions.isEmpty()) {
   return Observable.empty();
   }
   // 迴圈遍歷集合
   for (Permission p : permissions) {
        // 如果許可權列表中有一個許可權申請失敗,則直接返回申請失敗
        if (!p.granted) {
        return Observable.just(false);
        }
    }
    // 全部申請成功,則返回申請成功
    return Observable.just(true);
}
複製程式碼

通過以上程式碼可以看到,最終會遍歷Permissions集合,這個集合其實就是一開始我們呼叫request(final String... permissions)這個方法之後的許可權,經過處理返回的Observable序列物件,這裡是庫提供的一個預設實現,它會遍歷集合,如果有一個許可權申請失敗,都當作是申請許可權失敗了,但是我們也可以自己來決定返回後的Observable物件要怎麼處理,那就是呼叫requestEach()方法,然後最終得到的就是一個未經過轉換成Observable處理的Observable物件,我們可以自己根據實際需要進行必要的處理,而放棄掉預設提供的實現,不過一般情況下,使用預設的實現已經足夠

總結

如今大部分的Android手機裝置的系統都在6.0以上,動態許可權就變成了我們日常開發中必須要做的工作,那麼為了避免編寫重複程式碼,就需要使用到第三方庫來簡化我們的操作,但是我們在使用第三方庫的時候還是需要懂得其實現的原理,這樣使用起來會更加的方便,它好的思想也可以促進我們編碼水平的提供,最後就是,如果有特別的需求,我們也可以在其庫的基礎上進行特別的定製,來適應自身的需求,這也是看其原始碼的目的和意義

相關文章