Skip to content

Commit 36e3eee

Browse files
committed
重构框架代码设计
新增支持申请 VPN 权限 优化特殊权限授权后的等待时长 修复 Android 12 内存泄漏问题
1 parent aa11943 commit 36e3eee

27 files changed

+1498
-924
lines changed

.github/ISSUE_TEMPLATE/issue_template_bug.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,31 @@ assignees: getActivity
88

99
## 问题描述
1010

11-
* 框架版本:XXX
11+
* 框架版本【必填】:XXX
1212

13-
* 问题描述:XXX
13+
* 问题描述【必填】:XXX
1414

15-
* 复现步骤:XXX
15+
* 复现步骤【必填】:XXX
1616

17-
* 是否必现:填是/否
17+
* 是否必现【必填】:填是/否
1818

19-
* 出现问题的手机信息:请填写出现问题的品牌和机型
19+
* 出现问题的手机信息【必填】:请填写出现问题的品牌和机型
2020

21-
* 出现问题的安卓版本:请填写出现问题的 Android 版本
21+
* 出现问题的安卓版本【必填】:请填写出现问题的 Android 版本
2222

2323
## 请回答
2424

25-
* 是部分机型还是所有机型都会出现:部分/全部(例如:某为,某 Android 版本会出现)
25+
* 是部分机型还是所有机型都会出现【必答】:部分/全部(例如:某为,某 Android 版本会出现)
2626

27-
* 框架最新的版本是否存在这个问题:是/否(如果用的是旧版本的话,建议升级看问题是否还存在)
27+
* 框架最新的版本是否存在这个问题【必答】:是/否(如果用的是旧版本的话,建议升级看问题是否还存在)
2828

29-
* 是否已经查阅框架文档还未能解决的:是/否(文档会提供最常见的问题解答,可以看看是否有自己想要的)
29+
* 是否已经查阅框架文档还未能解决的【必答】:是/否(文档会提供最常见的问题解答,可以看看是否有自己想要的)
3030

31-
* issue 是否有人曾提过类似的问题:是/否(看看曾经有人提过类似的问题,先参考一下别人是怎么解决的)
31+
* issue 是否有人曾提过类似的问题【必答】:是/否(看看曾经有人提过类似的问题,先参考一下别人是怎么解决的)
3232

33-
* 是否可以通过 Demo 来复现该问题:是/否(排查一下是不是自己的项目代码写得有问题导致的)
33+
* 是否可以通过 Demo 来复现该问题【必答】:是/否(排查一下是不是自己的项目代码写得有问题导致的)
3434

35-
* 使用原生的权限 API 是否会出现该问题:是/否(排查一下是不是框架的代码存在问题导致的)
35+
* 使用原生的权限 API 是否会出现该问题【必答】:是/否(排查一下是不是框架的代码存在问题导致的)
3636

3737
## 其他
3838

.github/ISSUE_TEMPLATE/issue_template_suggest.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ assignees: getActivity
88

99
## 建议收集
1010

11-
* issue 是否有人曾提过类似的问题?(必答项,一旦出现重复提问我将不会再次解答)
11+
* issue 是否有人曾提过类似的问题?【必答】(一旦出现重复提问我将不会再次解答)
1212

13-
* 你觉得框架有什么不足之处?(必答项,你可以描述框架有什么令你不满意的地方)
13+
* 你觉得框架有什么不足之处?【必答】(你可以描述框架有什么令你不满意的地方)
1414

15-
* 你觉得该怎么去完善会比较好?(非必答项,你可以提供一下自己的想法或者做法供作者参考)
15+
* 你觉得该怎么去完善会比较好?【非必答】(你可以提供一下自己的想法或者做法供作者参考)

README.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

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

9-
* 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/XXPermissions/releases/download/13.6/XXPermissions.apk)
9+
* 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/XXPermissions/releases/download/15.0/XXPermissions.apk)
1010

1111
![](picture/demo_code.png)
1212

@@ -57,7 +57,7 @@ android {
5757
5858
dependencies {
5959
// 权限请求框架:https://github.com/getActivity/XXPermissions
60-
implementation 'com.github.getActivity:XXPermissions:13.6'
60+
implementation 'com.github.getActivity:XXPermissions:15.0'
6161
}
6262
```
6363

@@ -211,9 +211,9 @@ XXPermissions.setInterceptor(new IPermissionInterceptor() {});
211211

212212
| 适配细节 | [XXPermissions](https://github.com/getActivity/XXPermissions) | [AndPermission](https://github.com/yanzhenjie/AndPermission) | [PermissionX](https://github.com/guolindev/PermissionX) | [AndroidUtilCode](https://github.com/Blankj/AndroidUtilCode) | [PermissionsDispatcher](https://github.com/permissions-dispatcher/PermissionsDispatcher) | [RxPermissions](https://github.com/tbruyelle/RxPermissions) | [EasyPermissions](https://github.com/googlesamples/easypermissions) |
213213
| :--------: | :------------: | :------------: | :------------: | :------------: | :------------: | :------------: | :------------: |
214-
| 对应版本 | 13.6 | 2.0.3 | 1.6.3 | 1.31.0 | 4.9.2 | 0.12 | 3.0.0 |
214+
| 对应版本 | 15.0 | 2.0.3 | 1.6.4 | 1.31.0 | 4.9.2 | 0.12 | 3.0.0 |
215215
| 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/permissions-dispatcher/PermissionsDispatcher.svg)](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues) | [![](https://img.shields.io/github/issues/tbruyelle/RxPermissions.svg)](https://github.com/tbruyelle/RxPermissions/issues) | [![](https://img.shields.io/github/issues/googlesamples/easypermissions.svg)](https://github.com/googlesamples/easypermissions/issues) |
216-
| 框架体积 | 38 KB | 127 KB | 90 KB | 500 KB | 99 KB | 28 KB | 48 KB |
216+
| 框架体积 | 45 KB | 127 KB | 90 KB | 500 KB | 99 KB | 28 KB | 48 KB |
217217
| 闹钟提醒权限 ||||||||
218218
| 所有文件管理权限 ||||||||
219219
| 安装包权限 ||||||||
@@ -224,6 +224,7 @@ XXPermissions.setInterceptor(new IPermissionInterceptor() {});
224224
| 勿扰权限 ||||||||
225225
| 忽略电池优化权限 ||||||||
226226
| 查看应用使用情况权限 ||||||||
227+
| VPN 权限 ||||||||
227228
| Android 12 危险权限 ||||||||
228229
| Android 11 危险权限 ||||||||
229230
| Android 10 危险权限 ||||||||
@@ -232,6 +233,7 @@ XXPermissions.setInterceptor(new IPermissionInterceptor() {});
232233
| 新权限自动兼容旧设备 ||||||||
233234
| 屏幕方向旋转场景适配 ||||||||
234235
| 后台申请权限场景适配 ||||||||
236+
| Android 12 内存泄漏问题修复 ||||||||
235237
| 错误检测机制 ||||||||
236238

237239
#### 新权限自动兼容旧设备介绍
@@ -254,6 +256,36 @@ XXPermissions.setInterceptor(new IPermissionInterceptor() {});
254256

255257
* 当我们做耗时操作之后申请权限(例如在闪屏页获取隐私协议再申请权限),在网络请求的过程中将 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 **处于可见状态的情况**下。
256258

259+
#### Android 12 内存泄漏问题修复介绍
260+
261+
* 最近有人跟我提了一个内存泄漏的问题 [XXPermissions/issues/133](https://github.com/getActivity/XXPermissions/issues/133) ,我经过实践后确认这个问题真实存在,但是通过查看代码堆栈,发现这个问题是系统的代码引起的,引发这个问题需要以下几个条件:
262+
263+
1. 在 Android 12 及以上的设备上使用
264+
265+
2. 调用了 `Activity.shouldShowRequestPermissionRationale`
266+
267+
3. 在这之后又主动在代码调用了 activity.finish 方法
268+
269+
* 排查的过程:经过对代码的追踪,发现代码调用栈是这样的
270+
271+
* Activity.shouldShowRequestPermissionRationale
272+
273+
* PackageManager.shouldShowRequestPermissionRationale(实现对象为 ApplicationPackageManager)
274+
275+
* PermissionManager.shouldShowRequestPermissionRationale
276+
277+
* new PermissionManager(Context context)
278+
279+
* new PermissionUsageHelper(Context context)
280+
281+
* AppOpsManager.startWatchingStarted
282+
283+
* 罪魁祸首其实是 **PermissionUsageHelper** 将 Context 对象作为字段持有着,并在构造函数中调用 `AppOpsManager.startWatchingStarted` 开启监听,这样 PermissionUsageHelper 对象就会被添加进 `AppOpsManager#mStartedWatchers` 集合中,这样导致在 Activity 主动调用 finish 的时候,并没有使用 stopWatchingStarted 来移除监听,导致 Activity 对象一直被 `AppOpsManager#mStartedWatchers` 集合中持有着,所以间接导致了 Activity 对象无法被系统回收。
284+
285+
* 针对这个问题处理也很简单粗暴,就是将在外层传入的 **Context** 参数从 **Activity** 对象给替换成 **Application** 对象即可,有人可能会说了,Activity 里面才有 `shouldShowRequestPermissionRationale` 方法,而 Application 里面没有这个方法怎么办?看了一下这个方法的实现,其实那个方法最终会调用 `PackageManager.shouldShowRequestPermissionRationale` 方法(**隐藏 API,但是并不在黑名单中**)里面去,所以只要能获取到 **PackageManager** 对象即可,最后再使用反射去执行这个方法,这样就能避免出现内存泄漏。
286+
287+
* 幸好 Google 没有将 PackageManager.shouldShowRequestPermissionRationale 列入到反射黑名单中,否则这次想给 Google 擦屁股都没有办法了,要不然只能用修改系统源码实现的方式,但这种方式只能等谷歌在后续的 Android 版本上面修复了。
288+
257289
#### 错误检测机制介绍
258290

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

app/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ android {
77
applicationId "com.hjq.permissions.demo"
88
minSdkVersion 14
99
targetSdkVersion 32
10-
versionCode 1360
11-
versionName "13.6"
10+
versionCode 1500
11+
versionName "15.0"
1212
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
1313
}
1414

@@ -61,7 +61,7 @@ dependencies {
6161
implementation 'com.android.support:appcompat-v7:28.0.0'
6262

6363
// 吐司框架:https://github.com/getActivity/ToastUtils
64-
implementation 'com.github.getActivity:ToastUtils:10.3'
64+
implementation 'com.github.getActivity:ToastUtils:10.5'
6565

6666
// 内存泄漏检测:https://github.com/square/leakcanary
6767
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
<uses-permission android:name="android.permission.WRITE_SETTINGS" tools:ignore="ProtectedPermissions" />
2020

21+
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
22+
2123
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
2224
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2325
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
@@ -39,7 +41,7 @@
3941
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
4042
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
4143

42-
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
44+
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" tools:targetApi="s" />
4345
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
4446
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
4547

0 commit comments

Comments
 (0)