Android应用隐私合规修改细则

摘要

原来app只上架play,现在要求上架国内应用商店,需要对隐私合规等方面进行审查修改
根据小米应用商店的评测报告,在GitHub上找了两个工具评测,根据评测结果来进行项目修改

使用工具

结果对比

camille 732项减为0
AppScan 网络行为从92次请求减为0 关注列表从2422次减为0

数据对比 camille中hook到的数据为732项 AppScan中检测到的网络请求为92次,获取信息为2422次

camille对比
AppScan对比

修改过程

1. 去除不必要权限

权限图片
对比代码中使用的权限,发现存在报告中存在一些没有使用过的权限,对于这些由引用的sdk申请的权限,我们可以直接移除,在清单文件中使用node="remove"去除。

<manifest
    <uses-permission
        android:name="android.permission.RECEIVE_BOOT_COMPLETED"
        tools:node="remove" />
    <uses-permission
        android:name="android.permission.READ_CONTACTS"
        tools:node="remove" />
    <uses-permission
        android:name="android.permission.ACCESS_MEDIA_LOCATION"
        tools:node="remove" />
    <uses-permission
        android:name="android.permission.GET_TASKS"
        tools:node="remove" />
        ...
</manifest>

这一步通过adb获取应用权限详情,然后再根据权限对照用途也是一样的
adb shell dumpsys package 包名

2. 在启动页添加隐私弹窗

写弹窗时需要让用户可以点击隐私政策和用户协议,这个不难实现,使用SpannableString简简单单

文案示例

SpannableString Code
private void addText() {
    String userPrivacyText = "全部文案";
    SpannableString spannableString = new SpannableString(userPrivacyText);
    String privacyPolicy = "隐私政策";
    String userAgreement = "用户协议";
    String[] terms = {privacyPolicy, userAgreement};
    for (String term : terms) {
        int startIndex = 0;
        while (startIndex != -1) {
            startIndex = userPrivacyText.indexOf(term, startIndex);
            if (startIndex != -1) {
                int endIndex = startIndex + term.length();
                ClickableSpan clickableSpan = new ClickableSpan() {
                    @Override
public void onClick(@NonNull View widget) {
                        String url = privacyPolicy.equals(term) ? "隐私政策url" : "用户协议url";
                        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                        startActivity(intent);
                    }

                    @Override
public void updateDrawState(@NonNull TextPaint ds) {
                        super.updateDrawState(ds);
                        ds.setColor(getResources().getColor(R.color.red));
                        ds.setUnderlineText(false);
                    }
                };
                spannableString.setSpan(clickableSpan, startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                startIndex = endIndex;
            }
        }
    }
    text.setText(spannableString);
    // 需要设置这个点击才有反应
    text.setMovementMethod(LinkMovementMethod.getInstance());
}

3. 延迟初始化sdk

将所有的sdk延迟到用户点击同意获取隐私权限后再进行初始化,项目内的sdk初始化都是在application类中的onCreate()中进行,将里面所有会涉及到读取用户数据/写入sd卡/请求网络/获取位置的sdk初始化干掉,放到用户点击同意权限后再进行初始化

当我确保所有的sdk都已经延迟初始化之后,camille还是检测到有sdk出现权限调用行为:workManager/gms/FacebookSDk

额外权限调用行为

额外权限
额外权限
额外权限

当我一顿排查后发现是由于对应sdk的main清单文件中存在声明了provider组件。该组件的作用可以点击链接查看

<provider
    android:name="com.facebook.internal.FacebookInitProvider"
    android:authorities="${applicationId}.FacebookInitProvider"
    android:exported="false" />

解决方案: 在自己的清单文件中移除掉该声明

<application>
    <provider
        android:name="com.facebook.internal.FacebookInitProvider"
        android:authorities="${applicationId}.FacebookInitProvider"
        android:exported="false"
        tools:node="remove" />
    <provider
        android:name="com.google.firebase.provider.FirebaseInitProvider"
        android:authorities="${applicationId}.firebaseinitprovider"
        android:exported="false"
        tools:node="remove" />
    <provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <!-- If you are using androidx.startup to initialize other components -->
        <meta-data
            android:name="androidx.work.WorkManagerInitializer"
            android:value="androidx.startup"
            tools:node="remove" />
    </provider>
    ...
</application>

这样sdk就不会自动初始化sdk了,但是需要必须手动初始化。
示例WorkManager的手动初始化方法为:

Configuration myConfig = new Configuration.Builder().setMinimumLoggingLevel(android.util.Log.INFO).build();
WorkManager.initialize(getApplication(), myConfig);

4. 在申请权限之前需要向用户明示收集规则,并且用户拒绝后不得重复申请

审核失败
这部分改的就比较多了,项目中现有的权限申请都是直接向系统拿的,如果拿不到就跳转到app设置页面让用户手动开,但这点在小米商城上架不了。需要对app的权限申请流程全都改一遍。这一步走下来发现用户隐私方面play上架的要求远没有国内严格。

项目中权限申请用的是轮子哥的权限框架,作者给出了申请权限对话框demo
具体实现部分还是要看审核失败的原因,为了改造旧项目,画了脑图进行改造

脑图
大概讲解一下.弹窗有两种,一个是用户没有拒绝时告诉用户获取这个权限的用途,第二个是用户拒绝过之后仍要用这个功能弹出来告诉用户这个功能必须要开启相应权限.用户同意用途弹窗后再请求权限,如果该权限不能通过系统请求获取则跳转到app设置页面引导用户手动同意.一些特殊权限(gps/蓝牙)在没有开启但用户同意权限之后,需要手动判断该设置是否开启,没有开启则跳转到对应设置页让用户手动开启
照着这个逻辑重写PermissionInterceptor就很简单了


以上就完成了已上架play的应用对国内应用商店合规审查需要做的修改,愉快过审~
过审图