背景
- 由于国家政策原因,需要应用或者游戏,提供隐私协议弹窗,并且在用户同意隐私协议之前,不得获取设备信息,比如MAC,AndroidID、SSID,WIFI信息等,当你的应用上架各大渠道的时候都需要去做这个功能,越往后面这个政策的执行力度会越大,刚开始的时候,随便做一个隐私弹框弹出来,渠道那边就能糊弄过去,后来渠道那边下通知告知隐私协议不合规,要整改,否则就下架处理,具体的整改内容就太多了,很多大渠道都提供了检测机制,先是机审后是人工审核。
隐私弹窗和协议界面实现
- 相信大多数应用或者游戏在实现隐私协议的时候都使用了WebView加载网页的方式,因为这个能尽可能做到动态加载不同的隐私协议地址,而且多个应用可以复用,前端可以动态去获取不同应用需要的协议,并展示给用户
WebView方式被检测到获取设备信息
- 突然有一天我们的应用被通报说有违规收集用户信息,后面把包拿去三方平台检测,居然没通过,说明如下:APP未见向用户明示SDK的收集使用规则,未经用户同意,SDK存在每30s读取一次位置信息等信息,非服务所必需且无合理应用场景,超出实现产品或服务的业务功能所必需的最低频率;刚开始有点懵,我们的应用肯定是用同意后才会去获取设备信息啊,咋个突然报了个这个,最后查到的罪魁祸首就是这个WebView了,当我们去加载隐私协议的时候它回去获取WIFI、位置等信息
- chromium SDK每2秒获取Wifi信息的堆栈信息如下
{"stackTrace": [{"className": "libcore.util.Janus", "level": 0, "fileName": "Janus.java", "methodName": "getData", "lineNumber": 831}, {"className": "android.net.wifi.WifiManager", "level": 0, "fileName": "WifiManager.java", "methodName": "getConnectionInfo", "lineNumber": 1676}, {"className": "org.chromium.net.NetworkChangeNotifierAutoDetect$WifiManagerDelegate", "level": 0, "fileName": "NetworkChangeNotifierAutoDetect.java", "methodName": "getWifiInfoLocked", "lineNumber": 28}, {"className": "org.chromium.net.NetworkChangeNotifierAutoDetect$WifiManagerDelegate", "level": 0, "fileName": "NetworkChangeNotifierAutoDetect.java", "methodName": "getWifiSsid", "lineNumber": 22}, {"className": "org.chromium.net.NetworkChangeNotifierAutoDetect", "level": 0, "fileName": "NetworkChangeNotifierAutoDetect.java", "methodName": "getCurrentNetworkState", "lineNumber": 67}, {"className": "org.chromium.net.NetworkChangeNotifierAutoDetect", "level": 0, "fileName": "NetworkChangeNotifierAutoDetect.java", "methodName": "<init>", "lineNumber": 21}, {"className": "org.chromium.content.browser.BackgroundSyncNetworkObserver", "level": 0, "fileName": "BackgroundSyncNetworkObserver.java", "methodName": "createObserver", "lineNumber": 15}, {"className": "org.chromium.base.SystemMessageHandler", "level": 0, "fileName": "SystemMessageHandler.java", "methodName": "nativeDoRunLoopOnce", "lineNumber": -2}, {"className": "org.chromium.base.SystemMessageHandler", "level": 0, "fileName": "SystemMessageHandler.java", "methodName": "handleMessage", "lineNumber": 9}, {"className": "android.os.Handler", "level": 0, "fileName": "Handler.java", "methodName": "dispatchMessage", "lineNumber": 106}, {"className": "android.os.Looper", "level": 0, "fileName": "Looper.java", "methodName": "loop", "lineNumber": 193}, {"className": "android.app.ActivityThread", "level": 0, "fileName": "ActivityThread.java", "methodName": "main", "lineNumber": 6754}, {"className": "java.lang.reflect.Method", "level": 0, "fileName": "Method.java", "methodName": "invoke", "lineNumber": -2}, {"className": "com.android.internal.os.RuntimeInit$MethodAndArgsCaller", "level": 0, "fileName": "RuntimeInit.java", "methodName": "run", "lineNumber": 506}, {"className": "com.android.internal.os.ZygoteInit", "level": 0, "fileName": "ZygoteInit.java", "methodName": "main", "lineNumber": 863}], "permisson_group": "", "permisson": "", "result": {"WifiInfo": "android.net.wifi.WifiInfo@abe5a02"}, "stack_txt": 2, "permisson_level": ""}
- chromium SDK每2秒获取ssid的堆栈信息如下
{"stackTrace": [{"className": "libcore.util.Janus", "level": 0, "fileName": "Janus.java", "methodName": "getData", "lineNumber": 831}, {"className": "android.net.wifi.WifiInfo", "level": 0, "fileName": "WifiInfo.java", "methodName": "getSSID", "lineNumber": 272}, {"className": "org.chromium.net.NetworkChangeNotifierAutoDetect$WifiManagerDelegate", "level": 0, "fileName": "NetworkChangeNotifierAutoDetect.java", "methodName": "getWifiSsid", "lineNumber": 24}, {"className": "org.chromium.net.NetworkChangeNotifierAutoDetect", "level": 0, "fileName": "NetworkChangeNotifierAutoDetect.java", "methodName": "getCurrentNetworkState", "lineNumber": 67}, {"className": "org.chromium.net.NetworkChangeNotifierAutoDetect", "level": 0, "fileName": "NetworkChangeNotifierAutoDetect.java", "methodName": "connectionTypeChanged", "lineNumber": 100}, {"className": "org.chromium.android_webview.AwNetworkChangeNotifierRegistrationPolicy", "level": 0, "fileName": "AwNetworkChangeNotifierRegistrationPolicy.java", "methodName": "onFirstWebViewCreated", "lineNumber": 13}, {"className": "org.chromium.android_webview.AwContentsLifecycleNotifier", "level": 0, "fileName": "AwContentsLifecycleNotifier.java", "methodName": "onWebViewCreated", "lineNumber": 5}, {"className": "org.chromium.android_webview.AwContents", "level": 0, "fileName": "AwContents.java", "methodName": "nativeInit", "lineNumber": -2}, {"className": "org.chromium.android_webview.AwContents", "level": 0, "fileName": "AwContents.java", "methodName": "<init>", "lineNumber": 80}, {"className": "com.android.webview.chromium.WebViewChromium$1", "level": 0, "fileName": "WebViewChromium.java", "methodName": "run", "lineNumber": 14}, {"className": "org.chromium.android_webview.WebViewChromiumRunQueue", "level": 0, "fileName": "WebViewChromiumRunQueue.java", "methodName": "drainQueue", "lineNumber": 13}, {"className": "org.chromium.android_webview.WebViewChromiumRunQueue$1", "level": 0, "fileName": "WebViewChromiumRunQueue.java", "methodName": "run", "lineNumber": 2}, {"className": "org.chromium.base.ThreadUtils", "level": 0, "fileName": "ThreadUtils.java", "methodName": "runOnUiThread", "lineNumber": 30}, {"className": "org.chromium.android_webview.WebViewChromiumRunQueue", "level": 0, "fileName": "WebViewChromiumRunQueue.java", "methodName": "addTask", "lineNumber": 7}, {"className": "com.android.webview.chromium.WebViewChromiumFactoryProvider", "level": 0, "fileName": "WebViewChromiumFactoryProvider.java", "methodName": "addTask", "lineNumber": 6}, {"className": "com.android.webview.chromium.WebViewChromium", "level": 0, "fileName": "WebViewChromium.java", "methodName": "init", "lineNumber": 88}, {"className": "android.webkit.WebView", "level": 0, "fileName": "WebView.java", "methodName": "<init>", "lineNumber": 427}, {"className": "android.webkit.WebView", "level": 0, "fileName": "WebView.java", "methodName": "<init>", "lineNumber": 353}, {"className": "android.webkit.WebView", "level": 0, "fileName": "WebView.java", "methodName": "<init>", "lineNumber": 336}, {"className": "android.webkit.WebView", "level": 0, "fileName": "WebView.java", "methodName": "<init>", "lineNumber": 323}, {"className": "java.lang.reflect.Constructor", "level": 0, "fileName": "Constructor.java", "methodName": "newInstance0", "lineNumber": -2}, {"className": "java.lang.reflect.Constructor", "level": 0, "fileName": "Constructor.java", "methodName": "newInstance", "lineNumber": 343}, {"className": "android.view.LayoutInflater", "level": 0, "fileName": "LayoutInflater.java", "methodName": "createView", "lineNumber": 647}, {"className": "com.android.internal.policy.PhoneLayoutInflater", "level": 0, "fileName": "PhoneLayoutInflater.java", "methodName": "onCreateView", "lineNumber": 58}, {"className": "android.view.LayoutInflater", "level": 0, "fileName": "LayoutInflater.java", "methodName": "onCreateView", "lineNumber": 720}, {"className": "android.view.LayoutInflater", "level": 0, "fileName": "LayoutInflater.java", "methodName": "createViewFromTag", "lineNumber": 788}, {"className": "android.view.LayoutInflater", "level": 0, "fileName": "LayoutInflater.java", "methodName": "createViewFromTag", "lineNumber": 730}, {"className": "android.view.LayoutInflater", "level": 0, "fileName": "LayoutInflater.java", "methodName": "rInflate", "lineNumber": 863}, {"className": "android.view.LayoutInflater", "level": 0, "fileName": "LayoutInflater.java", "methodName": "rInflateChildren", "lineNumber": 824}, {"className": "android.view.LayoutInflater", "level": 0, "fileName": "LayoutInflater.java", "methodName": "inflate", "lineNumber": 515}, {"className": "android.view.LayoutInflater", "level": 0, "fileName": "LayoutInflater.java", "methodName": "inflate", "lineNumber": 423}, {"className": "android.view.LayoutInflater", "level": 0, "fileName": "LayoutInflater.java", "methodName": "inflate", "lineNumber": 374}, {"className": "com.android.internal.policy.PhoneWindow", "level": 0, "fileName": "PhoneWindow.java", "methodName": "setContentView", "lineNumber": 436}, {"className": "android.app.Dialog", "level": 0, "fileName": "Dialog.java", "methodName": "setContentView", "lineNumber": 557}, {"className": "com.archly.asdk.privacy.PrivacyDialog", "level": 0, "fileName": "PrivacyDialog.java", "methodName": "onCreate", "lineNumber": 45}, {"className": "android.app.Dialog", "level": 0, "fileName": "Dialog.java", "methodName": "dispatchOnCreate", "lineNumber": 407}, {"className": "android.app.Dialog", "level": 0, "fileName": "Dialog.java", "methodName": "show", "lineNumber": 302}, {"className": "com.archly.asdk.privacy.PrivacyDialog", "level": 0, "fileName": "PrivacyDialog.java", "methodName": "show", "lineNumber": 110}, {"className": "com.archly.asdk.privacy.PrivacyHelper$1", "level": 0, "fileName": "PrivacyHelper.java", "methodName": "onCall", "lineNumber": 45}, {"className": "com.archly.asdk.privacy.PrivacyHelper$3$1", "level": 0, "fileName": "PrivacyHelper.java", "methodName": "run", "lineNumber": 96}, {"className": "android.os.Handler", "level": 0, "fileName": "Handler.java", "methodName": "handleCallback", "lineNumber": 873}, {"className": "android.os.Handler", "level": 0, "fileName": "Handler.java", "methodName": "dispatchMessage", "lineNumber": 99}, {"className": "android.os.Looper", "level": 0, "fileName": "Looper.java", "methodName": "loop", "lineNumber": 193}, {"className": "android.app.ActivityThread", "level": 0, "fileName": "ActivityThread.java", "methodName": "main", "lineNumber": 6754}, {"className": "java.lang.reflect.Method", "level": 0, "fileName": "Method.java", "methodName": "invoke", "lineNumber": -2}, {"className": "com.android.internal.os.RuntimeInit$MethodAndArgsCaller", "level": 0, "fileName": "RuntimeInit.java", "methodName": "run", "lineNumber": 506}, {"className": "com.android.internal.os.ZygoteInit", "level": 0, "fileName": "ZygoteInit.java", "methodName": "main", "lineNumber": 863}], "permisson_group": "", "permisson": "android.permission.ACCESS_WIFI_STATE", "result": {"String": "<unknown ssid>"}, "stack_txt": 2, "permisson_level": "normal"}
如何处理
- 网上有很多关于WebWiew实现隐私政策功能的解决方案,比如采用hook的方式,这个方式我们试过了,不得行
- 使用第三方开源框架去加载远端html文件,我们为了加载这个东西引入一个库,是在是没必要,最后也尝试了,不得行
- 后面我们直接考虑使用TextView加载富文本的方式进行处理,服务器的后端还是需要开发html界面,只不过这个界面的元素很少,而且是TextView能够识别的一些元素,服务器端提供一个api接口将htlm的内容返回给android前端,然后前端自己通过TextView去渲染富文本,只要弹窗里面固定几个隐私协议点击跳转到隐私界面就可以了,具体的html内容就不要加任何链接的东西了,TextView也不支持,一般隐私弹窗都是放到启动页的,启动页以及Application里面只要不去调用任何第三方SDK有获取设备信息的代码,就能保证在同意之前不去获取设备信息
重新检测终于通了
- 通过TextView的方式加载html出的包在第三方平台去检测,终于通过了,也证实了WebView加载html文件会去获取设备信息
- 如果您有其他的更好解决方案,欢迎提供哦