背景
在开发一个App时我们常常会申请权限或是用于位置获取、或是用于存储等等,而Android 6.0 以后运行时权限的加入让权限申请这个行为风险逐渐扩大。但这些权限往往由不同的部门维护的库进行申请,如何去监控甚至管控这些敏感权限的申请成为我们的一大痛点。
-
- 有哪些权限属于运行时权限(Dangerous Permissions):developer.android.com/reference/a…
权限申请调用链路分析
注:现在市面上所流行的App基本都是基于
Andoridx
构建的,这里也就不会再对Andorid
库进行分析。
android.app.Activity
作为系统级的API,在线上我们其实没有一个很好的办法对其进行监控,可以先放置在一边,对其它的权限申请先进行一轮探索。
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null); // 向底层申请权限
}
复制代码
androidx.core.app.ActivityCompat
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
// Android 6.0 以后才加入了动态权限申请
if (Build.VERSION.SDK_INT>= 23) {
// 动态权限申请
activity.requestPermissions(permissions, requestCode);
}
}
复制代码
从调用链路上可以看出ActivityCompat
的requestPermissions
方法最终会调用到Activity.requestPermissions
,也就回到了前一个 Activity
的权限申请内容中。
androidx.fragment.app.Fragment
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
mHost.onRequestPermissionsFromFragment(this, permissions, requestCode);
}
复制代码
能看到在Fragment中使用了 onRequestPermissionsFromFragment
这样的一个回调,而mHost
也就是 Fragment.HostCallback
这个类只有在FragmentActivity
中发生了继承,显然这又要探讨mHost赋值时机,关于这方面的讨论等全部论证完再回头来看。
/**
* Requests permissions from the given fragment.
* See {@linkFragmentActivity#requestPermissions(String[], int)}
*/
public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
@NonNull String[] permissions, int requestCode) {}
复制代码
既然是一个回调函数,那我们只需要跳转到onRequestPermissionsFromFragment
的具体实现即可(即HostCallbacks.onRequestPermissionsFromFragment
)
public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
@NonNull String[] permissions, int requestCode) {
FragmentActivity.this.requestPermissionsFromFragment(fragment, permissions,
requestCode); // 1-->
}
// 1-->
void requestPermissionsFromFragment(@NonNull Fragment fragment, @NonNull String[] permissions,
int requestCode) {
if (requestCode == -1) {
// 实现动态权限申请
ActivityCompat.requestPermissions(this, permissions, requestCode);
return;
}
// 实现动态权限申请
ActivityCompat.requestPermissions(this, permissions, ((requestIndex + 1) << 16) + (requestCode & 0xffff));
}
复制代码
从调用链路中分析其实可以得知,还是会走到ActivityCompat
的权限申请,复用ActivityCompat
小节的结论,其实最后还是会走到Activity.requestPermissions
中申请权限。
mHost的注入
因为只是一个小问题,所以大概说一下注入的路径把~
- FragmentActivity.mFragments 中创建HostCallbacks对象,在FragmentActivity.onCreate()时发生调用。
FragmentController.attachHost() → FragmentManagerImpl.attachController()
,到这一步时FragmentActivity中的 HostCallbacks 对象就已经传递到了FragmentManagerImpl中的mHost中- 当新的Fragment 发生注册,在初始化阶段即Fragment.INITIALIZING,就会发生Fragment.performAttach() 中就会发生mHost的绑定操作,其最终实现其实还是来源于HostCallbacks
RequestMultiplePermissions / RequestPermission
简单的案例
作为一个还算比较新的玩意儿,还是得讲一下如何使用。
// 不同的版本可能会使用prepareCall来注册Launcher
val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGrant ->
if (isGrant) {
// Do Something
} else {
// Do Something
}
}
// 如果是RequestMultiplePermissions,使用列表即可
requestPermissionLauncher.launch(WRITE_EXTERNAL_STORAGE)
复制代码
分析
public void launch(@SuppressLint("UnknownNullness") I input) {
launch(input, null);// 1-->
}
// 1-->
public abstract void launch(@SuppressLint("UnknownNullness") I input,
@Nullable ActivityOptionsCompat options);
复制代码
直接从launch函数向下分析时能够发现,他是一个抽象函数,是谁将他进行了实现呢?那就需要registerForActivityResult()
函数中进行分析,分析其调用链时能够发现这样的一段函数
public final <I, O> ActivityResultLauncher<I> register(
@NonNull final String key,
@NonNull final LifecycleOwner lifecycleOwner,
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultCallback<O> callback) {
return new ActivityResultLauncher<I>() {
@Override
public void launch(I input, @Nullable ActivityOptionsCompat options) {
onLaunch(requestCode, contract, input, options);
}
// ...
};
}
复制代码
onLaunch方法向下调用时发现又是一个抽象函数,但onLaunch
方式在ComponentActivity
类中早已经有过实现,可以参考如下的代码。
private final ActivityResultRegistry mActivityResultRegistry = new ActivityResultRegistry() {
@Override
public <I, O> void onLaunch(
final int requestCode,
@NonNull ActivityResultContract<I, O> contract,
I input,
@Nullable ActivityOptionsCompat options) {
// 创建Intent
// Contract也就是ActivityResultContracts.RequestPermission(),关注其初始化函数中createIntent函数就可以找到Action参数的传递
// ActivityResultContracts.RequestMultiplePermissions()链路相同不再赘述
Intent intent = contract.createIntent(activity, input);
// 判断Intent中的Action是否为ACTION_REQUEST_PERMISSIONS
if (ACTION_REQUEST_PERMISSIONS.equals(intent.getAction())) {
// 获取权限集合
String[] permissions = intent.getStringArrayExtra(EXTRA_PERMISSIONS);
// 申请权限
ActivityCompat.requestPermissions(activity, permissions, requestCode);
}
}
};
复制代码
从调用链路上分析,能够发现通过Launcher
实现的权限获取最终还是调用到ActivityCompat.requestPermissions
,继承原本已经有的结论,能够发现最终还是会调用到Activity.requestPermissions
中申请权限。
小结
能够发现最终权限申请的路径是收敛的,他们最终都会走到Activity.requestPermission
中去进行权限的申请,所以管控的范围就不再需要发散只需要在Activity.requestPermissions
附近做手脚即可。
如何监控甚至管控权限动态权限呢?
对过去已经申请的权限其实不再我们的管控范围内,该方案更关注未来权限申请时,我们能够有效的管控
看了上面的小结,我想读者都已经有了对动态权限管控的方案了,用伪代码来表示无非就是这样。
// 服务端下发管控权限列表 block-permissions
if(permissions !in block-permissions) {
Activity.requestPermissions(permissions, 1)
} else {
// 监控上报
}
复制代码
注:权限的申请存在版本限制,所以字段下发的过程中同时需要考虑其权限引入的版本
是否还有更多的权限申请方式?
请尽情地再评论区留言吧~