最近在小米开放平台上线时,驳回报告中说在用户同意之前获取了设备ID。
一般来说,合规政策推出这么久了,又上线了这么多次,正常业务流程是不会再出现这种问题,就是怕一些第三方库在ContentProvider做初始化,然后调用了TelephonyManager的getDeviceId或getImei方法,这可能会导致未经用户同意就获取设备ID的情况。解决办法首先就是要找到调用这2个方法的代码位置,然后再避免在用户同意之前获取设备ID。
要找出这样的代码,或者要实现一些类似判断app是否调用了某些方法的情况,靠肉眼和逻辑分析可能会很难受,还需要借助工具来帮我们判断。
现在我要说的工具就是epic库中的DexposedBridge类,这个是它的中文说明文档:epic/README_cn.md at master · tiann/epic · GitHub 。这个库类在本文论述中的作用就是检测app中是否调用了某些方法。
下面我们直接上使用步骤。
一、库的引入
1、在工程级的build.gradle中添加:
allprojects { repositories { ... maven { url 'https://jitpack.io' } } }
2、在主模块的build.gradle中添加:
implementation 'com.github.tiann:epic:0.11.2'
implementation 'com.github.tiann:epic:0.11.2@aar'
3、确保主模块的build.gradle中配置的CPU架构仅仅是:
abiFilters "arm64-v8a"
为什么只是它?因为上面那个链接里面有说:
4、确保你运行的设备的是 Android 5.0 ~ 11,原因见上图。
二、准备要监听的方法配置表
在主模块的assets目录新建一个叫privacy.json的文件,文件内容如下:
{
"methods": [
{
"className": "android.telephony.TelephonyManager",
"method": "getDeviceId"
},
{
"className": "android.telephony.TelephonyManager",
"method": "getImei"
}
]
}
这个代码的意思很明显了,就是要监听className这个类当中的method方法。在本例中,就是要检查下app是否调用了TelephonyManager的getDeviceId和getImei方法。
三、实现对方法的监听(专业点叫Hook)
import android.content.Context;
import android.util.Log;
import com.lancoo.homework.util.JsonUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import de.robv.android.xposed.DexposedBridge;
import de.robv.android.xposed.XC_MethodHook;
public class MyHook {
public static void initPrivacy(Context context) {
try {
InputStream is = context.getAssets().open("privacy.json");
Privacy privacy = JsonUtil.parseJson(getJson(is), Privacy.class);
if (privacy == null) {
Log.e("My", "没发现privacy.json,或者privacy.json里面没正确的东西");
return;
}
ArrayList<PrivacyMethod> methods = privacy.methods;
for (PrivacyMethod method : methods) {
hookMethod(method.className, method.method);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void hookMethod(String className, String method) {
Class<?> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
Log.e("My", "没发现privacy.json里面的className: " + className);
return;
}
DexposedBridge.findAndHookMethod(clazz, method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Log.e("My", "beforeHookedMethod: this:" + param.thisObject, new RuntimeException("stack"));
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
}
});
}
private static String getJson(InputStream is) {
StringBuilder sb = new StringBuilder();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String s;
while ((s = reader.readLine()) != null) {
sb.append(s);
sb.append("\n");
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
public static class Privacy {
private ArrayList<PrivacyMethod> methods;
}
public static class PrivacyMethod {
String className;
String method;
}
}
四、监听调用
在你的Application的onCreate方法的super.onCreate()下一行代码打上MyHook.initPrivacy(this)。
class App : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
MyHook.initPrivacy(this)
// ...
}
}
这样你就可以监听到所有调用被监听方法的地方了。
五、查看监听反馈
我们先看MyHook中的下面这句代码:
Log.e("My", "beforeHookedMethod: this:" + param.thisObject, new RuntimeException("stack"));
这句代码的意思就是,在app的业务代码中,如果有地方调用 TelephonyManager.getDeviceId 或getImei,都会被epic的beforeHookedMethod拦截到,我们只需要在beforeHookedMethod打印出堆栈,即可看到是谁调用的。打印堆栈也很简单,我们就让他抛出一个异常即可在LogCat中看到。
六、@#¥%……&*
说个秘密:这个思路我是从App 隐私合规代码排查思路!获得的,只不过我把坑都列出来了,这样一来,小伙子们只要原原本本按照我这个步骤就可以搞定了。