Android危险权限如何最小切面化管控

1,574 阅读4分钟

背景

在开发一个App时我们常常会申请权限或是用于位置获取、或是用于存储等等,而Android 6.0 以后运行时权限的加入让权限申请这个行为风险逐渐扩大。但这些权限往往由不同的部门维护的库进行申请,如何去监控甚至管控这些敏感权限的申请成为我们的一大痛点。

权限申请调用链路分析

注:现在市面上所流行的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);
    }
}

从调用链路上可以看出ActivityCompatrequestPermissions 方法最终会调用到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赋值时机,关于这方面的讨论等全部论证完再回头来看。

Untitled.png

/**
 * 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的注入

因为只是一个小问题,所以大概说一下注入的路径把~

  1. FragmentActivity.mFragments 中创建HostCallbacks对象,在FragmentActivity.onCreate()时发生调用。
  2. FragmentController.attachHost() → FragmentManagerImpl.attachController(),到这一步时FragmentActivity中的 HostCallbacks 对象就已经传递到了FragmentManagerImpl中的mHost中
  3. 当新的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 {
    // 监控上报
}

注:权限的申请存在版本限制,所以字段下发的过程中同时需要考虑其权限引入的版本

是否还有更多的权限申请方式?

请尽情地再评论区留言吧~