| Markdown版本筆記 | 我的GitHub首頁 | 我的博客 | 我的微信 | 我的郵箱 |
|---|---|---|---|---|
| MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
RxJava RxPermissions 動態權限 簡介 原理 案例 MD
GitHub
Demo地址
目錄
基本使用步驟
RxPermissions是基於 RxJava 開發的用於幫助在 Android 6.0 中處理運行時權限檢測的框架。
1、配置
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
2、創建 RxPermissions 實例
final RxPermissions rxPermissions = new RxPermissions(this); // where this is an Activity or Fragment instance
注意:this 參數可以是 FragmentActivity 或 Fragment。如果你在 fragment 中使用 RxPermissions,你應該傳遞 fragment 實例作為構造函數參數而不是 fragment.getActivity(),否則你可能拋出異常 java.lang.IllegalStateException: FragmentManager is already executing[執行]
3、請求權限,必須在初始化階段比如 onCreate 中調用
// Must be done during an initialization phase like onCreate
rxPermissions.request(Manifest.permission.CAMERA)
.subscribe(granted -> {
if (granted) { // Always true pre-M
// I can control the camera now
} else {
// Oups permission denied
}
});
If you need to trigger觸發 the permission request from a specific event特定事件, you need to setup your event as an observable inside an initialization phase初始化階段.
RxView.clicks(view)
.compose(new RxPermissions(this).ensure(Manifest.permission.CAMERA))
.subscribe(granted -> Toast.makeText(this, granted ? "已賦予權限" : "已拒絕權限", Toast.LENGTH_SHORT).show());
注意事項和優點
注意:
如上所述,由於您的應用程序可能在權限請求期間重新啟動,因此請求必須在初始化階段完成。這可能是 Activity.onCreate 或View.onFinishInflate,但不能在 onResume 等 pausing 方法中,因為由於您請求的 activity 在權限請求期間被框架暫停了,因此您可能會創建一個無限的請求循環。
如果沒有,並且如果您的應用程序在權限請求期間重新啟動了(例如,由於配置更改),則用戶的響應將永遠不會發送給訂閱者。
你可以在這里找到更多相關細節。
優點
- 不需要擔心框架的版本。 如果sdk是 pre-M,則觀察者將自動接收授予的結果。
- 您不需要拆分權限請求和結果處理之間的代碼。目前,如果沒有這個庫,你必須在一個地方請求權限,並在 Activity.onRequestPermissionsResult() 中處理結果。
- 所有RX提供的轉換、過濾、鏈接[transformation, filter, chaining]......
API
構造方法
RxPermissions(FragmentActivity activity) //不可以是普通 Activity ,因為他的原理是通過 FragmentManager 操作 Fragment 實現的
RxPermissions(Fragment fragment)
請求權限
Observable<Boolean> request(String... permissions)
<T> ObservableTransformer<T, Boolean> ensure(final String... permissions) //與 compose 操作符一起使用
觀察詳細的結果
Observable<Permission> requestEach(String... permissions) //申請了多少個權限就回調多少次,相當於 flatMap 的效果
Observable<Permission> requestEachCombined(String... permissions) //只回調一次,回調對象的的屬性值是多個原始對象的組合值
<T> ObservableTransformer<T, Permission> ensureEach(final String... permissions)
<T> ObservableTransformer<T, Permission> ensureEachCombined(final String... permissions)
其他方法
boolean isGranted(String permission) //是否已賦予權限
void setLogging(boolean logging)
Observable<Boolean> shouldShowRequestPermissionRationale(Activity activity, String... permissions)
測試案例
入口Activity
public class RxPermissionsActivity extends ListActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] array = {"最簡單實用的案例,request",
"和 RxView 一起使用,compose + ensure",
"一次請求多個權限",
"觀察詳細的結果,requestEach、ensureEach",
"觀察詳細的結果,requestEachCombined 和 ensureEachCombined",
"shouldShowRequestPermissionRationale 和 isGranted 的使用 ",};
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Intent intent = new Intent(this, RxPermissionsFragmentActivity.class);
intent.putExtra("type", position);
startActivity(intent);
}
}
FragmentActivity
public class RxPermissionsFragmentActivity extends FragmentActivity {
private TextView view;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new TextView(this);
view.setBackgroundColor(0xff00ff00);
setContentView(view);
int type = getIntent().getIntExtra("type", 0);
init(type);
}
private void init(int type) {
switch (type) {
case 0:
new RxPermissions(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.subscribe(granted -> {
if (granted) { //在 Android M(6.0) 之前始終為true
Toast.makeText(this, "已賦予權限", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "已拒絕權限", Toast.LENGTH_SHORT).show();
}
});
break;
case 1:
RxView.clicks(view)
.compose(new RxPermissions(this).ensure(Manifest.permission.CAMERA))
.subscribe(granted -> Toast.makeText(this, granted ? "已賦予權限" : "已拒絕權限", Toast.LENGTH_SHORT).show());
break;
case 2:
new RxPermissions(this)
.request(Manifest.permission.CALL_PHONE, Manifest.permission.SEND_SMS)//只有所有權限都賦予了才會回調成功
.subscribe(granted -> Toast.makeText(this, granted ? "已賦予權限" : "已拒絕權限", Toast.LENGTH_SHORT).show());
RxView.clicks(view)
.compose(new RxPermissions(this).ensure(Manifest.permission.CALL_PHONE, Manifest.permission.SEND_SMS))
.subscribe(granted -> Toast.makeText(this, granted ? "已賦予權限" : "已拒絕權限", Toast.LENGTH_SHORT).show());
break;
case 3:
RxView.clicks(view)
.compose(new RxPermissions(this).ensureEach(Manifest.permission.CALL_PHONE, Manifest.permission.SEND_SMS))
.subscribe(permission -> {//申請了多少個權限就回調多少次,相當於 flatMap 的效果
if (permission.granted) {
Toast.makeText(this, "已賦予權限:" + permission.name, Toast.LENGTH_SHORT).show();
} else if (permission.shouldShowRequestPermissionRationale) { //拒絕了權限,但是沒有勾選"ask never again"
Toast.makeText(this, "已拒絕權限:" + permission.name + " ,但可以繼續申請", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "已拒絕權限:" + permission.name + " ,並且不會再顯示", Toast.LENGTH_SHORT).show();
}
});
break;
case 4:
RxView.clicks(view)
.compose(new RxPermissions(this).ensureEachCombined(Manifest.permission.CALL_PHONE, Manifest.permission.SEND_SMS))
.subscribe(permission -> {//只回調一次,回調的 permission 的屬性值是多個原始 permission 的組合值
if (permission.granted) {
Toast.makeText(this, "已賦予全部所需的權限:" + permission.name, Toast.LENGTH_SHORT).show();
} else if (permission.shouldShowRequestPermissionRationale) { //拒絕了權限,但是沒有勾選"ask never again"
Toast.makeText(this, "有權限被拒絕:" + permission.name + " ,但可以繼續申請", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "有權限被拒絕:" + permission.name + " ,並且不會再顯示", Toast.LENGTH_SHORT).show();
}
});
break;
case 5:
boolean isGranted = new RxPermissions(this).isGranted(Manifest.permission.CAMERA);
Toast.makeText(this, "是否已賦予權限:" + isGranted, Toast.LENGTH_SHORT).show();
new RxPermissions(this)
.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE, Manifest.permission.SEND_SMS)
.subscribe(granted -> Toast.makeText(this, granted ? "全部權限已授權,或有權限被拒絕但可以繼續申請" :
"有權限被拒絕,並且不會再顯示", Toast.LENGTH_SHORT).show());
break;
default:
break;
}
}
}
原理
RxPermissions 就包含4個類,很明顯 BuildConfig 與原理沒任何關系,所以真正有用的是 Permission、RxPermissions、RxPermissionsFragment這三個:
Permission 是定義權限的 model類,用來存放權限名稱以及是否獲取權限的信息:
public class Permission {
public final String name;
public final boolean granted;
public final boolean shouldShowRequestPermissionRationale;
}
RxPermissions 就是最主要的類了,我們根據一個基本使用案例分析一下調用流程:
new RxPermissions(this)
.request(Manifest.permission.CALL_PHONE, Manifest.permission.SEND_SMS)//只有所有權限都賦予了才會回調成功
.subscribe(granted -> Toast.makeText(this, granted ? "已賦予權限" : "已拒絕權限", Toast.LENGTH_SHORT).show());
構造方法
首先看構造方法的具體邏輯:
public RxPermissions(@NonNull FragmentActivity activity) {
this.mRxPermissionsFragment = this.getLazySingleton(activity.getSupportFragmentManager());
}
public RxPermissions(@NonNull Fragment fragment) {
this.mRxPermissionsFragment = this.getLazySingleton(fragment.getChildFragmentManager());
}
在構造方法中初始化了成員 RxPermissionsFragment:
@NonNull
private RxPermissions.Lazy<RxPermissionsFragment> getLazySingleton(@NonNull final FragmentManager fragmentManager) {
return new RxPermissions.Lazy<RxPermissionsFragment>() {
private RxPermissionsFragment rxPermissionsFragment;
public synchronized RxPermissionsFragment get() {
if (this.rxPermissionsFragment == null) this.rxPermissionsFragment = RxPermissions.this.getRxPermissionsFragment(fragmentManager);
return this.rxPermissionsFragment;
}
};
}
可以看到,其維護的是一個單例,如果已經存在則直接返回,否則通過 getRxPermissionsFragment 方法獲取:
static final String TAG = RxPermissions.class.getSimpleName();
private RxPermissionsFragment getRxPermissionsFragment(@NonNull FragmentManager fragmentManager) {
RxPermissionsFragment rxPermissionsFragment = this.findRxPermissionsFragment(fragmentManager);
boolean isNewInstance = rxPermissionsFragment == null;
if (isNewInstance) {
rxPermissionsFragment = new RxPermissionsFragment();
fragmentManager.beginTransaction().add(rxPermissionsFragment, TAG).commitNow();
}
return rxPermissionsFragment;
}
里面的邏輯很簡單,如果已經存在則直接返回,否則如果是新創建的,則通過 FragmentManager 將其 add 到當前 FragmentActivity 或 Fragment 中。
判斷是否已存在的邏輯同樣是通過 FragmentManager 來判斷的:
private RxPermissionsFragment findRxPermissionsFragment(@NonNull FragmentManager fragmentManager) {
return (RxPermissionsFragment)fragmentManager.findFragmentByTag(TAG);
}
request 方法
再看看請求權限的具體邏輯:
public Observable<Boolean> request(String... permissions) {
return Observable.just(TRIGGER).compose(this.ensure(permissions)); //【static final Object TRIGGER = new Object();】
}
首先,利用 just 方法創建Observable對象,接下來會調用 compose 方法,compose 的參數的作用是將一個類型的 Observable 對象轉換成另外一個類型的Observable對象,同時也可以對自身對象的一些重復操作進行封裝,避免重復編寫代碼。我們看到,compose 方法中調用了我們可能用到的另一個方法 ensure :
public <T> ObservableTransformer<T, Boolean> ensure(final String... permissions) {
return new ObservableTransformer<T, Boolean>() {
public ObservableSource<Boolean> apply(Observable<T> o) {
return RxPermissions.this
.request(o, permissions) //核心步驟
.buffer(permissions.length)
.flatMap(new Function<List<Permission>, ObservableSource<Boolean>>() { /* 轉換操作 */});
}
};
}
上面代碼的作用是傳入一個 Observable
private Observable<Permission> request(Observable<?> trigger, final String... permissions) {
if (permissions != null && permissions.length != 0) {
return this.oneOf(trigger, this.pending(permissions)).flatMap(new Function<Object, Observable<Permission>>() {
public Observable<Permission> apply(Object o) {
return RxPermissions.this.requestImplementation(permissions); //核心步驟
}
});
} else throw new IllegalArgumentException("RxPermissions.request/requestEach requires at least one input permission");
}
通過上面代碼可以看出,首先會判斷申請權限列表是否為空,如果為空就會拋出異常,然后通過 oneOf 方法和 pending 方法來創建合並 Observable 對象,下面看一下這兩個方法
private Observable<?> oneOf(Observable<?> trigger, Observable<?> pending) {
return trigger == null ? Observable.just(TRIGGER) : Observable.merge(trigger, pending);
}
private Observable<?> pending(String... permissions) {
String[] var2 = permissions;
int var3 = permissions.length;
for(int var4 = 0; var4 < var3; ++var4) {
String p = var2[var4];
if (!((RxPermissionsFragment)this.mRxPermissionsFragment.get()).containsByPermission(p)) {
return Observable.empty();
}
}
return Observable.just(TRIGGER);
}
private Map<String, PublishSubject<Permission>> mSubjects = new HashMap();
public boolean containsByPermission(@NonNull String permission) {
return this.mSubjects.containsKey(permission);
}
從以上的兩個方法的代碼可以看出,pending 主要是判斷權限申請列表中是否全部都在 Fragment 中的 mSubjects 集合中:如果有一個不在集合中,則返回 Observable.empty(),如果已經全部在集合中,則返回Observable.just(TRIGGER)。然后在 oneOf 方法中根據 trigger 是否為 null 來判斷是哪種返回。
這兩個方法在我看來最終並沒有起到實際的作用,因為具體的請求權限是在 requestImplementation(permissions) 方法中實現的,而通過以上兩個方法得到的 Observable 對象最終在 flatMap 操作轉換時並不會用到它們的對象,而是直接根據權限申請列表 permissions 作為參數,直接調用 requestImplementation 方法進行權限的實際請求,所以接下來主要看看 requestImplementation 方法的具體實現:
@TargetApi(23)
private Observable<Permission> requestImplementation(String... permissions) {
List<Observable<Permission>> list = new ArrayList(permissions.length); //保存所有已經處理過的權限
List<String> unrequestedPermissions = new ArrayList();//保存所有需要申請的權限
String[] unrequestedPermissionsArray = permissions;
int var5 = permissions.length;
for (int var6 = 0; var6 < var5; ++var6) { //循環權限申請列表
String permission = unrequestedPermissionsArray[var6];
((RxPermissionsFragment) this.mRxPermissionsFragment.get()).log("Requesting permission " + permission);
if (this.isGranted(permission)) {
list.add(Observable.just(new Permission(permission, true, false))); //如果權限已經具有,則直接保存到集合中
} else if (this.isRevoked(permission)) {
list.add(Observable.just(new Permission(permission, false, false))); //如果權限被拒絕,則將其作為申請被拒絕的權限保存到集合中
} else {
PublishSubject<Permission> subject = ((RxPermissionsFragment) this.mRxPermissionsFragment.get()).getSubjectByPermission(permission);
if (subject == null) { // 先查詢 map 中是否已經存在了該 subject,如果還未存在,則創建一個 PublishSubject 對象,並保存起來
unrequestedPermissions.add(permission); //此為需要申請的權限
subject = PublishSubject.create();
((RxPermissionsFragment) this.mRxPermissionsFragment.get()).setSubjectForPermission(permission, subject); //保存到 map 中
}
list.add(subject);//將新創建的需要申請的權限保存到集合中
}
}
if (!unrequestedPermissions.isEmpty()) { //在循環結束之后,如果有未申請的權限,則進行權限的申請操作
unrequestedPermissionsArray = (String[]) unrequestedPermissions.toArray(new String[unrequestedPermissions.size()]);
this.requestPermissionsFromFragment(unrequestedPermissionsArray); //權限的申請操作
}
return Observable.concat(Observable.fromIterable(list));
}
上面的就是進行權限申請的具體代碼,主要的操作就是定義一個集合保存保存一次申請的所有權限,無論這個權限是否已經申請,還是被撤銷,還是未申請,最終都會保存到list這個集合中,這樣,我們在后續的操作中,才可以進行轉換。同時,定義另外一個集合,用於保存未申請的權限,然后在循環結束之后進行未申請權限的申請。
申請權限的方法
上面分析道,在遍歷所有申請的權限狀態結束后,調用 requestPermissionsFromFragment 進行未申請權限的申請,下面我們看一下具體流程。
@TargetApi(23)
void requestPermissionsFromFragment(String[] permissions) {
((RxPermissionsFragment)this.mRxPermissionsFragment.get()).requestPermissions(permissions);
}
其實就是調用了 RxPermissionsFragment 中的 requestPermissions 方法,所以我們進到該方法看看:
@TargetApi(23)
void requestPermissions(@NonNull String[] permissions) {
this.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; //如果請求碼不符合,則直接返回
boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length]; //保存權限申請結果
for (int i = 0; i < permissions.length; i++) { //循環遍歷,看權限是否被永久拒絕了
//調用Fragment中的**方法獲取權限申請結果,如果被點擊了不再在提醒,並且拒絕權限時,則會返回 false,否則返回 true
shouldShowRequestPermissionRationale[i] = this.shouldShowRequestPermissionRationale(permissions[i]);
}
// 調用 Fragment 中的調用重載方法
this.onRequestPermissionsResult(permissions, grantResults, shouldShowRequestPermissionRationale);//【核心中的核心】
}
主要是創建了一個 boolean 數組,用於保存判斷權限申請的狀態。如果權限被申請過,並且用戶在對話框中點擊了“不在提醒”選項,這個時候調用 Fragment 中的shouldShowRequestPermissionRationale 方法會返回false,代表說這個權限被拒絕,我們可以根據這個結果再給用戶做一些提示性的工作。
最后看最核心的部分:onRequestPermissionsResult
@Override
void onRequestPermissionsResult(String permissions[], int[] grantResults, boolean[] shouldShowRequestPermissionRationale) {
for (int i = 0, size = permissions.length; i < size; i++) { //循環權限列表
// 查詢 mSubjects 集合中是否存在代表該 permission 的 PublishSubject 對象
PublishSubject<Permission> subject = (PublishSubject)this.mSubjects.get(permissions[i]);
if (subject == null) return; // 如果沒有,則直接返回
this.mSubjects.remove(permissions[i]); //將集合中的 permission 的 PublishSubject 對象進行移除
boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED; //判斷是否申請成功
subject.onNext(new Permission(permissions[i], granted, shouldShowRequestPermissionRationale[i])); //返回相應的對象
subject.onCompleted();
}
}
首先判斷在 mSubjects 集合中是否已經存在了該 permission 的 PublishSubject 對象,如果沒有,則直接返回,代表出錯。其實這里的對象在RxPermissions 的 requestImplementation() 中已經通過 mRxPermissionsFragment.setSubjectForPermission(permission, subject) 語句進行賦值了,所以一定會是存在的。
接着 mSubjects 集合將該 permission 的 PublishSubjects 對象移除,這是為什么呢?這個要看 RxPermissions#requestImplementation() 中的實現,在該方法中會 PublishSubject<Permission> subject = mRxPermissionsFragment.getSubjectByPermission(permission); 這個語句出現,這個語句代表說,如果權限未申請過,也未被撤銷,那么就直接在 RxPermissionsFragment 的 mSubjects 集合中查找是否存在該 permission 的 PublishSubject 對象,如果有,就直接通過 list.add(subject) 這個語句保存到集合中了,那么這樣被拒絕的權限,下次將不會被重新申請,所以需要移除,之后就是發射數據,發送時間結束標識了。
申請結果回調方法
分析完了 RxPermissionsFragment 中的權限申請的操作,我們就需要回到 Rxpermissions#request() 方法中,在該方法的 flatMap 的回調方法中返回了 Observable
return RxPermissions.this.request(o, permissions)
.buffer(permissions.length)
.flatMap(new Function<List<Permission>, ObservableSource<Boolean>>() {...}
接着再往回看,在ensure()方法中,我們可以看到在該方法中調用了request()方法之后,接着調用了buffer(permissions.length)方法,那么這個buffer()的作用是什么呢?它的作為是為了將一個序列的 Observable
接着就是使用 flatMap 操作符轉換成 Observable
@Override
public ObservableSource<Boolean> apply(List<Permission> permissionsx) {
...
return Observable.just(false); //我們可以根據實際需要進行自己的處理
}
}
這里就是返回給我們的最終結果了,整個流程也就到此結束。
2018-8-30
