本文是《Android开发之深度项目设计探索》系列的第三篇,主要介绍的是 基于最新RxPermissions 类库的使用及源码分析,本系列历史文章:
《Android开发之深度项目设计探索(一)》
《Android开发之深度项目设计探索(二)》
Permission,这个单词翻译过来的意思有:允许、许可、权限。我们Android开发亲切的将其称为权限。
权限是一种安全机制。Android权限机制主要用于:限制应用程序内部某些具有限制特性的功能使用以及应用程序之间的组件访问。
在Android系统6.0版本(也就是 SdkVersion = 23)之前,权限的声明仅需要在清单配置文件中,通过标签uses-permission来声明应用,也就意味所需要的权限,如:
<uses-permission android:name="android.permission.CAMERA"/>
这行配置代码的意思意味着该应用允许使用CAMERA(照相机)权限;
由于时代的发展以及各种因素,谷歌Android技术团队出于安全角度这一原则设计考虑,在Android系统6.0版本开始,之后的版本提出了一些新概念,整理下来有以下几点:
概念一:权限分为两种、一种是普通权限;还有一种是危险权限
概念二:普通权限,直接在清单文件中配置声明即可
概念三:危险权限,谷歌觉得部分权限涉及到用户的隐私,因此必须明确告知用户应用需要那些权限,但是这样的危险权限需要用户手动授权
既然谷歌在新版本给我们带来了新概念和需要解决的问题,那么就需要解决这个权限问题:
- 解决方式一:暂时不适配Android6.0系统版本,也就是将targetSdkVersion降到23以上(这种法子算曲线救国)
- 解决方式二:直接适配危险权限,主动告知用户打开权限
那么这篇文章介绍的 RxPermissions-官方文档,这是一款基于Rxjava2的运行时权限解决方案,
这个库的minSdkVersion 最低不能低于11,也就是 "use this library your minSdkVersion must be >= 11"
在gradle导入最新版本的RxPermissions依赖:" implementation 'com.github.tbruyelle:rxpermissions:0.10.2' "
集成和使用步骤如下:
- 首先:创建RxPermissions实例:
//这里的this就是Activity or Fragment instance
RxPermissions rxPermissions = new RxPermissions(this);
- 接着:代码使用,需搭配Rxjava2使用,写法如下:
A:获取单个权限
RxPermissions rxPermissions = new RxPermissions(this);
// 申请单个权限
rxPermissions.request(Manifest.permission.CAMERA).subscribe(new Consumer<Boolean>() {
@Override
public void accept(Boolean aBoolean) throws Exception {
if (aBoolean == true){
// 用户已经同意该权限
Log.i(TAG, "accept: CAMERA 权限成功");
}else {
// 用户拒绝权限
Log.i(TAG, "accept: CAMERA 权限失败");
}
}
});
B:获取多个权限
获取多个权限方式有两种写法:
- 写法一:
// 用户同时申请多个权限,方式一:
// 如果用户全部申请成功,才会返回true
// 如果用户有一个权限拒绝申请,那么就会返回失败
rxPermissions.requestEach(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_CALENDAR,
Manifest.permission.READ_PHONE_STATE).
subscribe(new Consumer<Permission>() {
@Override
public void accept(Permission permission) throws Exception {
if (permission.granted) {
// 用户已经同意该权限
Log.d(TAG, permission.name + "用户授予权限");
} else if (permission.shouldShowRequestPermissionRationale) {
// 用户拒绝了该权限,没有选中『不再询问』(Never ask again),那么下次再次启动时,还会提示请求权限的对话框
Log.d(TAG, permission.name + "用户拒绝权限——下次启动可以继续申请");
} else {
// !!!注意!!!
// 用户拒绝了该权限,并且选中了『不再询问』
// 需要去 APP设置 里面去打开对应的申请权限
Log.d(TAG, permission.name + "用户拒绝权限——勾选了不在询问——提示用户后续去手动申请");
}
}
});
- 写法二:
// 用户同时申请多个权限,方式二:
// 对应每个权限申请的操作
// 思考:
// 我们可以在应用启动之前也打开获取权限,然后记录每个权限是否申请成功,记录的方式可以通过sp存储状态
// 在需要权限的时候,首先判断SP存储的状态是否为权限授权成功,如果没有授权成功,那么就再次请求授权
rxPermissions.requestEachCombined(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_CALENDAR,
Manifest.permission.READ_PHONE_STATE).
subscribe(new Consumer<Permission>() {
@Override
public void accept(Permission permission) throws Exception {
// 判断具体的对应权限
if (permission.name.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
if (permission.granted){
Log.i(TAG, "WRITE_EXTERNAL_STORAGE_accept: 权限申请成功");
}else {
Log.i(TAG, "WRITE_EXTERNAL_STORAGE_accept: 权限授权失败");
}
}
if (permission.name.equals(Manifest.permission.READ_PHONE_STATE)) {
if (permission.granted){
Log.i(TAG, "READ_PHONE_STATE: 权限申请成功");
}else {
Log.i(TAG, "READ_PHONE_STATE: 权限授权失败");
}
}
}
});
嗯,没错,基本上,这款框架的使用就已经介绍完了,是不是很简单优雅。
但是有一些细节我们需要注意,如该库的作者不建议我们在 onResume()这个生命周期里面进行申请权限, RxPermissions-官方文档 也给了详细说明,
下面是源码解读
Rxpermissions源码分析:
使用一:
我们知道,Rxpermissions使用的第一步是在Activity中创建RxPermissions这个实例化对象。实际上,RxPermission采用的方式是利用了一个隐形的Fragment来请求权限,然后在回调中用RxJava进行数据的组装和转化,最后变成了布尔类型的数据回调回来。这个隐形的Fragment,就是类库中的RxPermissionsFragment,
public class RxPermissionsFragment extends Fragment {
private static final int PERMISSIONS_REQUEST_CODE = 42;
// Contains all the current permission requests.
// Once granted or denied, they are removed from it.
private Map<String, PublishSubject<Permission>> mSubjects = new HashMap<>();
private boolean mLogging;
public RxPermissionsFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@TargetApi(Build.VERSION_CODES.M)
void requestPermissions(@NonNull String[] permissions) {
requestPermissions(permissions, PERMISSIONS_REQUEST_CODE);
}
@TargetApi(Build.VERSION_CODES.M)
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++) {
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
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;
}
mSubjects.remove(permissions[i]);
boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
subject.onNext(new Permission(permissions[i], granted, shouldShowRequestPermissionRationale[i]));
subject.onComplete();
}
}
@TargetApi(Build.VERSION_CODES.M)
boolean isGranted(String permission) {
final FragmentActivity fragmentActivity = getActivity();
if (fragmentActivity == null) {
throw new IllegalStateException("This fragment must be attached to an activity.");
}
return fragmentActivity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
}
@TargetApi(Build.VERSION_CODES.M)
boolean isRevoked(String permission) {
final FragmentActivity fragmentActivity = getActivity();
if (fragmentActivity == null) {
throw new IllegalStateException("This fragment must be attached to an activity.");
}
return fragmentActivity.getPackageManager().isPermissionRevokedByPolicy(permission, getActivity().getPackageName());
}
public void setLogging(boolean logging) {
mLogging = logging;
}
public PublishSubject<Permission> getSubjectByPermission(@NonNull String permission) {
return mSubjects.get(permission);
}
public boolean containsByPermission(@NonNull String permission) {
return mSubjects.containsKey(permission);
}
public void setSubjectForPermission(@NonNull String permission, @NonNull PublishSubject<Permission> subject) {
mSubjects.put(permission, subject);
}
void log(String message) {
if (mLogging) {
Log.d(RxPermissions.TAG, message);
}
}
}
可以看到,这个类继承了Fragment。然后在onCreate中并没有创建具体的UI布局,我们知道。Fragment具有属性retainInstance,默认值为false。 当设备旋转时,fragment会随托管activity一起销毁并重建。解决办法是:调用setRetainInstance(true)方法可保留fragment不会重新创建(例如旋转屏幕)
你可能会问,那这个隐形的Fragment又是在那里创建的?答案:创建的时机,是在创建RxPermissions的对象中,顺带创建了这个实例对象(通过构造方法实现):下面是Rxpermissions这个类的构造方法以及部分函数源码:
public class RxPermissions {
static final String TAG = RxPermissions.class.getSimpleName();
static final Object TRIGGER = new Object();
@VisibleForTesting
Lazy<RxPermissionsFragment> mRxPermissionsFragment;
public RxPermissions(@NonNull final FragmentActivity activity) {
mRxPermissionsFragment = getLazySingleton(activity.getSupportFragmentManager());
}
public RxPermissions(@NonNull final Fragment fragment) {
mRxPermissionsFragment = getLazySingleton(fragment.getChildFragmentManager());
}
@NonNull
private Lazy<RxPermissionsFragment> getLazySingleton(@NonNull final FragmentManager fragmentManager) {
return new Lazy<RxPermissionsFragment>() {
private RxPermissionsFragment rxPermissionsFragment;
@Override
public synchronized RxPermissionsFragment get() {
if (rxPermissionsFragment == null) {
rxPermissionsFragment = getRxPermissionsFragment(fragmentManager);
}
return rxPermissionsFragment;
}
};
}
private RxPermissionsFragment getRxPermissionsFragment(@NonNull final FragmentManager fragmentManager) {
RxPermissionsFragment rxPermissionsFragment = findRxPermissionsFragment(fragmentManager);
boolean isNewInstance = rxPermissionsFragment == null;
if (isNewInstance) {
rxPermissionsFragment = new RxPermissionsFragment();
fragmentManager
.beginTransaction()
.add(rxPermissionsFragment, TAG)
.commitNow();
}
return rxPermissionsFragment;
}
//.............省略部分源码................
}
这个隐形的Fragment很重要,因为权限的申请与申请结果的回调都是在Fragment中完成的。基于此,开发人员才不需要为申请结果重写回调方法。
使用二:
接着,是Rxpermissions基本使用的代码,也就是rxPermissions.request......,对应的源码如下:
/**
* Request permissions immediately, <b>must be invoked during initialization phase
* of your application</b>.
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public Observable<Boolean> request(final String... permissions) {
return Observable.just(TRIGGER).compose(ensure(permissions));
}
/**
* Request permissions immediately, <b>must be invoked during initialization phase
* of your application</b>.
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public Observable<Permission> requestEach(final String... permissions) {
return Observable.just(TRIGGER).compose(ensureEach(permissions));
}
/**
* Request permissions immediately, <b>must be invoked during initialization phase
* of your application</b>.
*/
public Observable<Permission> requestEachCombined(final String... permissions) {
return Observable.just(TRIGGER).compose(ensureEachCombined(permissions));
}
从中可以看到,上面使用的三种写法,最终是调到了
Observable.just(TRIGGER).compose(ensureEach(permissions));
- 步骤一:
这里的 Just 操作符,简单点理解就是可以将某个对象转化为Observable对象。Just操作符,是RxJava中非常快捷的创建Observable对象的方法。如果通过just操作符创建了一个Observable,继续使用subscriber订阅则会依次调用其onNext()和onCompleted()方法。这里的TRIGGER,源码里面就是一个Object对象。
static final Object TRIGGER = new Object();
通过源码可以看到,根据just操作符创建完Observable对象之后紧接着调用了compose()方法。
compose()操作符主要是将一个Observable对象(具体的数据类型)转换成另一个Observable(对应的数据类型)对象,我们发现此方法最终调用的是ensure(permissions)这个方法
- 步骤二:
ensure(permissions)这个方法的源码如下:
public <T> ObservableTransformer<T, Boolean> ensure(final String... permissions) {
ObservableTransformer<T, Boolean> observableTransformer = new ObservableTransformer<T, Boolean>() {
@Override
public ObservableSource<Boolean> apply(Observable<T> o) {
Observable<Boolean> booleanObservable = 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) {
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();
}
// Return true if all permissions are granted.
for (Permission p : permissions) {
if (!p.granted) {
return Observable.just(false);
}
}
return Observable.just(true);
}
});
return booleanObservable;
}
};
return observableTransformer;
}
这个buffer()的操作符作用是什么呢,它的作用是为了将一个序列的Observable<Permission>对象转换成Observable<List<Permission>>对象,
抛开Rxjava2常规的操作符除外,首先看下这段来自RxPermissions类库部分源码提供的自定义方法有:request(o, permissions);源码还自定义了一个类:Permission,其中p.granted这种写法貌似就是类名.成员变量的操作,那我们就先看一眼Permission这个最基本的Java类
Permission源码如下:
public class Permission {
public final String name;
public final boolean granted;
public final boolean shouldShowRequestPermissionRationale;
public Permission(String name, boolean granted) {
this(name, granted, false);
}
public Permission(String name, boolean granted, boolean shouldShowRequestPermissionRationale) {
this.name = name;
this.granted = granted;
this.shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale;
}
public Permission(List<Permission> permissions) {
name = combineName(permissions);
granted = combineGranted(permissions);
shouldShowRequestPermissionRationale = combineShouldShowRequestPermissionRationale(permissions);
}
@Override
@SuppressWarnings("SimplifiableIfStatement")
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Permission that = (Permission) o;
if (granted != that.granted) return false;
if (shouldShowRequestPermissionRationale != that.shouldShowRequestPermissionRationale)
return false;
return name.equals(that.name);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + (granted ? 1 : 0);
result = 31 * result + (shouldShowRequestPermissionRationale ? 1 : 0);
return result;
}
@Override
public String toString() {
return "Permission{" +
"name='" + name + '\'' +
", granted=" + granted +
", shouldShowRequestPermissionRationale=" + shouldShowRequestPermissionRationale +
'}';
}
private String combineName(List<Permission> permissions) {
return Observable.fromIterable(permissions)
.map(new Function<Permission, String>() {
@Override
public String apply(Permission permission) throws Exception {
return permission.name;
}
}).collectInto(new StringBuilder(), new BiConsumer<StringBuilder, String>() {
@Override
public void accept(StringBuilder s, String s2) throws Exception {
if (s.length() == 0) {
s.append(s2);
} else {
s.append(", ").append(s2);
}
}
}).blockingGet().toString();
}
private Boolean combineGranted(List<Permission> permissions) {
return Observable.fromIterable(permissions)
.all(new Predicate<Permission>() {
@Override
public boolean test(Permission permission) throws Exception {
return permission.granted;
}
}).blockingGet();
}
private Boolean combineShouldShowRequestPermissionRationale(List<Permission> permissions) {
return Observable.fromIterable(permissions)
.any(new Predicate<Permission>() {
@Override
public boolean test(Permission permission) throws Exception {
return permission.shouldShowRequestPermissionRationale;
}
}).blockingGet();
}
}
这个类我们先放在这里,在看一眼类库自定义的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");
}
Observable<Permission> permissionObservable =
oneOf(trigger, pending(permissions))
.flatMap(new Function<Object, Observable<Permission>>() {
@Override
public Observable<Permission> apply(Object o) {
return requestImplementation(permissions);
}
});
return permissionObservable;
}
可以看到这个方法返回的对象是一个Observable< Permission >,内部逻辑首先判断permissions 是否为null,长度是否大于0,如果满足其中一个条件就抛异常;如果permissions 不为null,长度且大于0,在调用oneOf方法和pending( )方法来创建合并Observable对象。其中,oneOf()和pending( )方法的函数如下:
private Observable<?> oneOf(Observable<?> trigger, Observable<?> pending) {
if (trigger == null) {
return Observable.just(TRIGGER);
}
return Observable.merge(trigger, pending);
}
private Observable<?> pending(final String... permissions) {
for (String p : permissions) {
if (!mRxPermissionsFragment.get().containsByPermission(p)) {
return Observable.empty();
}
}
return Observable.just(TRIGGER);
}
pending(final String... permissions)这个函数的主要功能有:
- 首先循环遍历,查询该权限是否已经在申请过了
- 如果列表中有一个权限没有在RxPermissionsFragment的HashMap集合中保存,
就返回Observeble.empty(),返回的这个Observable对象会调用onComplete()方法,所以并不会进入flatMap
oneOf(Observable<?> trigger, Observable<?> pending)这个函数的主要是:
- 首先判断trigger对象是否为null,如果为空,则通过Just操作符创建一个Observable
- 最后返回合并的Observable对象。
我们知道,request(o, permissions)最终是调用了requestImplementation(permissions);这个方法,下面就看下requestImplementation(permissions);方法的内部源码:
@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.get().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.get().getSubjectByPermission(permission);
// Create a new subject if not exists
if (subject == null) {
unrequestedPermissions.add(permission);
subject = PublishSubject.create();
mRxPermissionsFragment.get().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));
}
这段代码的主要意思有以下几个方面(按照顺序):
A:创建第一个集合(也就是源码的List<Observable<Permission>> list)保存已经申请的权限
B:创建第二个集合(也就是List<String> unrequestedPermissions)用来保存没有请求成功的权限
C:对具体申请的权限列表进行循环遍历:
- 如果权限已经申请过了,则直接保存到集合中(if (isGranted(permission)) {...})
- 如果权限被撤销,则将其作为申请被拒绝的权限保存到集合中:if (isRevoked(permission)) {...}
D:在RxPermissionsFragment中,寻找是否已经存在对应的权限,如果不存在(subject == null),则创建PublishSubject对象,将其添加到unrequestedPermissions集合中,然后将subject对象保存在第一个集合中,
E:如果有未申请的权限,则进行权限的申请操作,也就是requestPermissionsFromFragment(unrequestedPermissionsArray);这个函数
F:利用第一个集合创建Observable对象,用concat操作符进行链接,最终返回一个Observable<Permission>对象
结论:
这个方法主要的操作就是,定义集合保存一次申请的所有权限。无论这个权限是已经申请,还是被撤销,还是未申请,最终都会保存到list这个集合中。在后续的操作中,才可以进行转换。
同时,定义集合用于保存未申请的权限,然后在循环结束之后进行未申请权限的申请。
因此,现在的关键就是requestPermissionsFromFragment(unrequestedPermissionsArray);这个函数,源码继续跟进:
@TargetApi(Build.VERSION_CODES.M)
void requestPermissionsFromFragment(String[] permissions) {
mRxPermissionsFragment.get().log("requestPermissionsFromFragment " + TextUtils.join(", ", permissions));
mRxPermissionsFragment.get().requestPermissions(permissions);
}
继续跟进:
@TargetApi(Build.VERSION_CODES.M)
void requestPermissions(@NonNull String[] permissions) {
requestPermissions(permissions, PERMISSIONS_REQUEST_CODE);
}
看到这里,最终是调用了requestPermissions(permissions, PERMISSIONS_REQUEST_CODE);,这个PERMISSIONS_REQUEST_CODE,的值是42,源码里面有。这个方法是Fragment提供的系统方法,那么回调的处理结果理所当然在RxPermissionsFragment中:
@TargetApi(Build.VERSION_CODES.M)
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++) {
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
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;
}
mSubjects.remove(permissions[i]);
boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
subject.onNext(new Permission(permissions[i], granted, shouldShowRequestPermissionRationale[i]));
subject.onComplete();
}
}
这段回调处理的源码意思主要有:
A:首先判断请求码,如果请求码不等于42,则直接返回
B:以权限列表的长度为size,创建一个boolean数组
C:循环遍历,看权限是否被永久拒绝了。这里会调用Fragment中的shouldShowRequestPermissionRationale()方法。这个方法如果权限申请成功会返回true;用户点击了不在提醒,且拒绝权限时,会返回false。
D:调用下面的重载方法
进入重载方法以后的操作有:
A:对权限列表进行循环操作。寻找相对应的:subject,也就是( PublishSubject<Permission> subject = mSubjects.get(permissions[i]);)
B:如果没有对应的subject,则直接返回
C:如果有对应的subject,首先将集合中的permission的PublishSubject对象进行移除;接着判断是否申请成功;最后执行onNext( )返回相应的Permission对象
基本的源码分析就到这了。
评语:可以看到,RxPermissions这个库的主要逻辑就是在通过隐形的Fragment以及Rxjava2的操作符来进行实践的,通过合理搭配才让这个库使用起来比较方便。
如果这篇文章对你有帮助,希望各位看官留下宝贵的star,谢谢。
Ps:著作权归作者所有,转载请注明作者, 商业转载请联系作者获得授权,非商业转载请注明出处(开头或结尾请添加转载出处,添加原文url地址),文章请勿滥用,也希望大家尊重笔者的劳动成果。