Skip to content

Commit

Permalink
优化框架中的一些重复代码
Browse files Browse the repository at this point in the history
优化框架中的一些代码逻辑
优化框架文档及相关注释
优化屏幕旋转反方向的逻辑
提升读取清单文件的成功率
修改读取清单文件中权限的方式
  • Loading branch information
getActivity committed Oct 24, 2021
1 parent 6e8acde commit 1e6ed55
Show file tree
Hide file tree
Showing 17 changed files with 510 additions and 428 deletions.
17 changes: 3 additions & 14 deletions HelpDoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ android
* 最后直接调用下面这句代码

```java
XXPermissions.with(this)
// 不适配 Android 11 可以这样写
XXPermissions.with(MainActivity.this)
// 适配 Android 11 分区存储这样写
//.permission(Permission.Group.STORAGE)
// 适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE
// 不适配 Android 11 分区存储这样写
.permission(Permission.MANAGE_EXTERNAL_STORAGE)
.request(new OnPermissionCallback() {

Expand All @@ -82,17 +82,6 @@ XXPermissions.with(this)
toast("获取存储权限成功");
}
}

@Override
public void onDenied(List<String> permissions, boolean never) {
if (never) {
toast("被永久拒绝授权,请手动授予存储权限");
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(MainActivity.this, permissions);
} else {
toast("获取存储权限失败");
}
}
});
```

Expand Down
40 changes: 27 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

* 博文地址:[一句代码搞定权限请求,从未如此简单](https://www.jianshu.com/p/c69ff8a445ed)

* 点击此处 [下载 Demo](XXPermissions.apk) 进行演示或者测试
* 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](XXPermissions.apk)

![](picture/demo_code.png)

* 另外想对 Android 6.0 权限需要深入了解的,可以看这篇文章[Android 6.0 运行权限解析](https://www.jianshu.com/p/6a4dff744031)

Expand Down Expand Up @@ -43,7 +45,7 @@ android {
dependencies {
// 权限请求框架:https://github.com/getActivity/XXPermissions
implementation 'com.github.getActivity:XXPermissions:12.5'
implementation 'com.github.getActivity:XXPermissions:12.6'
}
```

Expand Down Expand Up @@ -175,7 +177,7 @@ XXPermissions.startPermissionActivity(Fragment fragment, String... permissions);

| 功能及细节 | [XXPermissions](https://github.com/getActivity/XXPermissions) | [AndPermission](https://github.com/yanzhenjie/AndPermission) | [PermissionX](https://github.com/guolindev/PermissionX) | [AndroidUtilCode](https://github.com/Blankj/AndroidUtilCode) | [RxPermissions](https://github.com/tbruyelle/RxPermissions) | [PermissionsDispatcher](https://github.com/permissions-dispatcher/PermissionsDispatcher) | [EasyPermissions](https://github.com/googlesamples/easypermissions) |
| :--------: | :------------: | :------------: | :------------: | :------------: | :------------: | :------------: | :------------: |
| 对应版本 | 12.5 | 2.0.3 | 1.6.0 | 1.30.6 | 0.12 | 4.9.1 | 3.0.0 |
| 对应版本 | 12.6 | 2.0.3 | 1.6.1 | 1.30.6 | 0.12 | 4.9.1 | 3.0.0 |
| issues 数 | [![](https://img.shields.io/github/issues/getActivity/XXPermissions.svg)](https://github.com/getActivity/XXPermissions/issues) | [![](https://img.shields.io/github/issues/yanzhenjie/AndPermission.svg)](https://github.com/yanzhenjie/AndPermission/issues) | [![](https://img.shields.io/github/issues/guolindev/PermissionX.svg)](https://github.com/guolindev/PermissionX/issues) | [![](https://img.shields.io/github/issues/Blankj/AndroidUtilCode.svg)](https://github.com/Blankj/AndroidUtilCode/issues) | [![](https://img.shields.io/github/issues/tbruyelle/RxPermissions.svg)](https://github.com/tbruyelle/RxPermissions/issues) | [![](https://img.shields.io/github/issues/permissions-dispatcher/PermissionsDispatcher.svg)](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues) | [![](https://img.shields.io/github/issues/googlesamples/easypermissions.svg)](https://github.com/googlesamples/easypermissions/issues) |
| 框架体积 | 28 KB | 127 KB | 78 KB | 500 KB | 28 KB | 91 KB | 48 KB |
| 安装包权限 ||||||||
Expand All @@ -190,7 +192,7 @@ XXPermissions.startPermissionActivity(Fragment fragment, String... permissions);
| 新权限自动兼容旧设备 ||||||||
| 屏幕方向旋转场景适配 ||||||||
| 后台申请权限场景适配 ||||||||
| 低级错误检测机制 ||||||||
| 错误检测机制 ||||||||

#### 新权限自动兼容旧设备介绍

Expand All @@ -206,25 +208,37 @@ XXPermissions.startPermissionActivity(Fragment fragment, String... permissions);

* 当系统权限申请对话框弹出后对 Activity 进行屏幕旋转,会导致权限申请回调失效,因为屏幕旋转会导致框架中的 Fragment 销毁重建,这样会导致里面的回调对象直接被回收,最终导致回调不正常。解决方案有几种,一是在清单文件中添加 `android:configChanges="orientation"` 属性,这样屏幕旋转时不会导致 Activity 和 Fragment 销毁重建,二是直接在清单文件中固定 Activity 显示的方向,但是以上两种方案都要使用框架的人处理,这样显然是不够灵活的,解铃还须系铃人,框架的问题应当由框架来解决,而 **RxPermissions** 的解决方式是给 PermissionFragment 对象设置 `fragment.setRetainInstance(true)`,这样就算屏幕旋转了,Activity 对象会销毁重建,而 Fragment 也不会跟着销毁重建,还是复用着之前那个对象,但是存在一个问题,如果 Activity 重写了 **onSaveInstanceState** 方法会直接导致这种方式失效,这样做显然只是治标不治本,而 **XXPermissions** 的方式会更直接点,在 **PermissionFragment** 绑定到 Activity 上面时,把当前 Activity 的**屏幕方向固定住**,在权限申请结束后再把**屏幕方向还原回去**。

* 在所有的权限请求框架中,只要使用了 Fragment 申请权限都会出现这个问题,而 AndPermission 其实是通过创建新的 Activity 来申请权限,所以不会出现这个问题,PermissionsDispatcher 则是采用了 APT 生成代码的形式来申请权限,所以也没有这个问题,而 PermissionX 则是直接借鉴了 XXPermissions 的解决方案,详情请见 [XXPermissions/issues/49](https://github.com/getActivity/XXPermissions/issues/49)[PermissionX/issues/51](https://github.com/guolindev/PermissionX/issues/51)

#### 后台申请权限场景介绍

* 当我们做耗时操作之后申请权限(例如在闪屏页获取隐私协议再申请权限),在网络请求的过程中将 Activity 返回桌面去(退到后台),然后会导致权限请求是在后台状态中进行,在这个时机上就可能会导致权限申请不正常,表现为不会显示授权对话框,处理不当的还会导致崩溃,例如 [RxPeremission/issues/249](https://github.com/tbruyelle/RxPermissions/issues/249)。原因在于框架中的 PermissionFragment 在 **commit / commitNow** 到 Activity 的时候会做一个检测,如果 Activity 的状态是不可见时则会抛出异常,而 **RxPeremission** 正是使用了 **commitNow** 才会导致崩溃 ,使用 **commitAllowingStateLoss / commitNowAllowingStateLoss** 则可以避开这个检测,虽然这样可以避免崩溃,但是会出现另外一个问题,系统提供的 **requestPermissions** API 在 Activity 不可见时调用也不会弹出授权对话框,**XXPermissions** 的解决方式是将 **requestPermissions** 时机从 **create** 转移到了 **resume**,因为 Activity 和 Fragment 的生命周期方法是捆绑在一起的,如果 Activity 是不可见的,那么就算创建了 Fragment 也只会调用 **onCreate** 方法,而不会去调用它的 **onResume** 方法,最后当 Activity 从后台返回到前台时,不仅会触发 **Activity.onResume** 方法,同时也会触发 **PermissionFragment** 的 **onResume** 方法,在这个方法申请权限就可以保证最终 **requestPermissions** 申请的时机是在 Activity **处于可见状态的情况**下。

#### 低级错误检测机制介绍
#### 错误检测机制介绍

* 在框架的日常维护中,有很多人跟我反馈过框架有 Bug,但是经过排查和定位发现,这其中有 95% 的问题来自于调用者一些不规范操作导致的,这不仅对我造成很大的困扰,同时也极大浪费了很多小伙伴的时间和精力,于是我在框架中加入了很多审查元素,在 **debug 模式****debug 模式****debug 模式** 下,一旦有某些操作不符合规范,那么框架会直接抛出异常给调用者,并在异常信息中正确指引调用者纠正错误,例如:

* 传入的 Context 实例不是 Activity 对象,框架会抛出异常,又或者传入的 Activity 的状态异常(已经 **Finishing** 或者 **Destroyed**),这种情况一般是在异步申请权限导致的,框架也会抛出异常,请在合适的时机申请权限,如果申请的时机无法预估,请在外层做好 Activity 状态判断再进行权限申请。

* 如果调用者没有传入任何权限就申请权限的话,框架会抛出异常,又或者如果调用者传入的权限不是危险权限或者特殊权限,框架也会抛出异常,因为有的人会把普通权限当做危险权限传给框架,系统会直接拒绝。

* 如果当前项目在没有适配分区存储的情况下,申请 `READ_EXTERNAL_STORAGE``WRITE_EXTERNAL_STORAGE` 权限

* 当项目的 `targetSdkVersion >= 29` 时,需要在清单文件中注册 `android:requestLegacyExternalStorage="true"` 属性,否则框架会抛出异常,如果不加会导致一个问题,明明已经获取到存储权限,但是无法在 Android 10 的设备上面正常读写外部存储上的文件。

* 在框架的日常维护中,有很多人跟我反馈过框架有 Bug,但是经过排查和定位发现,这其中有 95% 的问题来自于调用者一些不规范操作导致的,这不仅对我造成很大的困扰,同时也极大浪费了很多小伙伴的时间和精力,于是我在框架中加入了很多审查元素,在 **Debug** 模式下,一旦有某些操作不符合规范,那么框架会直接抛出异常给调用者,并在异常信息中正确指引调用者纠正错误,例如:
* 当项目的 `targetSdkVersion >= 30` 时,则不能申请 `READ_EXTERNAL_STORAGE``WRITE_EXTERNAL_STORAGE` 权限,而是应该申请 `MANAGE_EXTERNAL_STORAGE` 权限

1. 传入的 Context 实例不是 Activity 对象,框架会抛出异常,又或者传入的 Activity 的状态异常(已经 **Finishing** 或者 **Destroyed**),这种情况一般是在异步申请权限导致的,框架也会抛出异常,请在合适的时机申请权限,如果申请的时机无法预估,请在外层做好 Activity 状态判断再进行权限申请。
* 如果当前项目已经适配了分区存储,那么只需要在清单文件中注册一个 meta-data 属性即可: `<meta-data android:name="ScopedStorage" android:value="true" />`

2. 如果调用者没有传入任何权限就申请权限的话,框架会抛出异常,如果调用者传入的权限不是危险权限或者特殊权限,框架也会抛出异常,因为有的人会把普通权限当做危险权限传给框架,系统会直接拒绝
* 如果申请的权限中包含后台定位权限, 那么这里面则不能包含和定位无关的权限,否则框架会抛出异常,因为 `ACCESS_BACKGROUND_LOCATION` 和其他非定位权限定位掺杂在一起申请,在 Android 11 上会出现不申请直接被拒绝的情况

3. 如果在没有适配分区存储的情况下申请外部存储权限,必须在清单文件中注册 `android:requestLegacyExternalStorage="true"`,否则框架会抛出异常,如果不加会导致获取到存储权限,但是无法在 Android 10 的设备上面正常读写外部存储上的文件
* 如果申请的权限和项目中的 **targetSdkVersion** 对不上,框架会抛出异常,是因为 **targetSdkVersion** 代表着项目适配到哪个 Android 版本,系统会自动做向下兼容,假设申请的权限是 Android 11 才出现的,但是 **targetSdkVersion** 还停留在 29,那么在某些机型上的申请,会出现授权异常的情况,也就是用户明明授权了,但是系统返回的始终是 false

4. 如果申请的权限中包含后台定位权限, 那么这里面则不能包含和定位无关的权限,否则框架会抛出异常,因为 `ACCESS_BACKGROUND_LOCATION` 和其他非定位权限定位掺和在一起申请,在 Android 11 上会出现不申请直接被拒绝的情况
* 如果动态申请的权限没有在 `AndroidManifest.xml` 中进行注册,框架会抛出异常,因为如果不这么做,是可以进行申请权限,但是不会出现授权弹窗,直接被系统拒绝,并且系统不会给出任何弹窗和提示,并且这个问题在每个机型上面都是**必现的**

5. 如果申请的权限和项目中的 **targetSdkVersion** 对不上,框架会抛出异常,是因为 **targetSdkVersion** 代表着项目适配到哪个 Android 版本,系统会自动做向下兼容,假设申请的权限是 Android 11 才出现的,但是 **targetSdkVersion** 还停留在 29,那么在某些机型上的申请,会出现授权异常的情况,也就是用户明明授权了,但是系统返回的始终是 false
* 如果动态申请的权限有在 `AndroidManifest.xml` 中进行注册,但是设定了不恰当的 `android:maxSdkVersion` 属性值,框架会抛出异常,举个例子:`<uses-permission android:name="xxxx" android:maxSdkVersion="29" />`,这样的设定会导致在 Android 11 `Build.VERSION.SDK_INT >= 30`)及以上的设备申请权限,系统会认为这个权限没有在清单文件中注册,直接拒绝本次的权限申请,并且也是不会给出任何弹窗和提示,这个问题也是必现的

6. 如果动态申请的权限没有进行在清单文件中注册,那么框架会抛出异常,因为如果不这么做,是可以进行申请权限,但是不会出现授权弹窗,直接被系统拒绝,并且系统不会给出任何弹窗和提示,并且这个问题在每个机型上面都是**必现的**
* 如果你不需要上面这些检测,可通过设置 `XXPermissions.setDebugMode(false)` 来关闭,但是需要注意的是,我并不建议你去关闭这个检测,因为在 **release 模式** 时它是关闭状态,不需要你手动关闭,而它只在 **debug 模式** 下才会触发这些检测

* 出现这些问题的原因是,我们对这些机制不太熟悉,而如果框架不加以限制,那么引发各种奇奇怪怪的问题出现,作为框架的作者,表示不仅你们很痛苦,作为框架作者表示也很受伤。因为这些问题不是框架导致的,而是调用者的某些操作不规范导致的。我觉得这个问题最好的解决方式是,由框架做统一的检查,因为我是框架的作者,对权限申请这块知识点有**较强的专业能力和足够的经验**,知道什么该做,什么不该做,这样就可以对这些骚操作进行一一拦截。

Expand All @@ -244,7 +258,7 @@ XXPermissions.startPermissionActivity(Fragment fragment, String... permissions);

* 向下兼容属性:新权限在旧系统可以正常申请,框架会做自动适配,无需调用者适配

* 自动检测错误:如果出现低级错误框架会主动抛出异常给调用者(仅在 Debug 下判断,把 Bug 扼杀在摇篮中)
* 自动检测错误:如果出现错误框架会主动抛出异常给调用者(仅在 Debug 下判断,把 Bug 扼杀在摇篮中)

#### 作者的其他开源项目

Expand Down
Binary file modified XXPermissions.apk
Binary file not shown.
6 changes: 3 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ android {
applicationId "com.hjq.permissions.demo"
minSdkVersion 14
targetSdkVersion 31
versionCode 1250
versionName "12.5"
versionCode 1260
versionName "12.6"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

Expand Down Expand Up @@ -62,7 +62,7 @@ dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'

// 吐司框架:https://github.com/getActivity/ToastUtils
implementation 'com.github.getActivity:ToastUtils:9.5'
implementation 'com.github.getActivity:ToastUtils:9.6'

// 内存泄漏检测:https://github.com/square/leakcanary
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<uses-permission android:name="android.permission.WRITE_SETTINGS" tools:ignore="ProtectedPermissions" />

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

<uses-permission android:name="android.permission.RECORD_AUDIO" />
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/java/com/hjq/permissions/demo/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* author : Android 轮子哥
* github : https://github.com/getActivity/XXPermissions
* time : 2018/06/15
* desc : Demo 演示
* desc : 权限申请演示
*/
public final class MainActivity extends AppCompatActivity implements View.OnClickListener {

Expand Down Expand Up @@ -129,9 +129,9 @@ public void onGranted(List<String> permissions, boolean all) {
@Override
public void run() {
XXPermissions.with(MainActivity.this)
// 不适配 Android 11 可以这样写
// 适配 Android 11 分区存储这样写
//.permission(Permission.Group.STORAGE)
// 适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE
// 不适配 Android 11 分区存储这样写
.permission(Permission.MANAGE_EXTERNAL_STORAGE)
.request(new OnPermissionCallback() {

Expand Down
Loading

0 comments on commit 1e6ed55

Please sign in to comment.