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