前言

看了一圈各大网站目前对于隐衷合规检测的分享,发现大家简直都是本人写一套动静代理、Hook或者ASM来实现代码拦挡从而检测不合规的代码堆栈,尽管这也是一个很好的办法,然而对于一些很紧急的工作又或者是刚入门的开发者来说,无疑是很耗时间的,所以笔者在这里提供一种目前 Android 11 反对的API思路。官网文档传送门

最近被工信部下架的利用太多了,隐衷合规检测特地严,明明把所有能看失去的隐衷合规问题都解决了,然而工信部就是说你没解决。Ok,fine! 你说没解决就没解决吧(欲哭无泪)

尽管咱们是把“看失去”的隐衷问题都解决了,然而那些看不到的呢?比如说依赖的第三方 SDK,它们会不会在你不晓得的状况下偷偷调用了一些隐衷权限呢?置信个别的公司我的项目都是比拟大型的,依赖了各种各样的第三方SDK吧(如果是大佬的话就当我没说,毕竟大佬们都喜爱本人造轮子~)

检测办法

为了让利用及其依赖项拜访用户私密数据的过程更加通明,Android 11 引入了数据拜访审核性能。

笔者应用的这种形式,是针对Android 11及以上的,因为这是Android 11的新个性,具体能够看官网文档。想要检测利用应用的隐衷权限的话,须要先临时把targetSdkVersion升到30,而后等检测完不合规的中央后再还原回本来应用的 targetSdkVersion 版本。

其实当 Android 11 的新个性进去之后,开发者们必定或多或少的对AppOpsManager.OnOpNotedCallback有印象,然而真到了须要用的时候,可能就想不起这个回调了,在 App 中注册了这个回调之后,当利用每次产生以下任一事件时都执行相应操作:

  • 利用的代码拜访私密数据
  • 依赖库或 SDK 中的代码拜访私密数据

注:此博客所讲的例子是基于定位权限的,其余隐衷权限同理。

简略应用

以下代码段是用于数据拜访时的AppOpsManager.OnOpNotedCallback回调:

override fun onCreate(savedInstanceState: Bundle?) {    val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() {        private fun logPrivateDataAccess(opCode: String, trace: String) {            Log.i("youzi", "Private data accessed. Operation: $opCode\n Stack Trace:\n $trace")        }        override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {            logPrivateDataAccess(syncNotedAppOp.op, Throwable().stackTrace.toString())        }        override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {            logPrivateDataAccess(syncNotedAppOp.op, Throwable().stackTrace.toString())        }        override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {            logPrivateDataAccess(asyncNotedAppOp.op, asyncNotedAppOp.message)        }    }    // 创立AppOpsManager实例并增加下面定义的回调    val appOpsManager = getSystemService(AppOpsManager::class.java) as AppOpsManager    appOpsManager.setOnOpNotedCallback(mainExecutor, appOpsCallback)}

回调里须要重写的几个办法笔者就不多赘述了,官网文档里讲得曾经很具体了。这里的logPrivateDataAccess办法是本人写的,名字可随便定义,用于打印应用到的权限名字,还有应用到的代码堆栈,不便大家看到在哪里调用了某个权限。

这里给个笔者检测时打印进去的日志示例:
![
](https://upload-images.jianshu...)

能够看到,打印出的日志当中会有应用到的定位权限名字fine_location,还有Stack Trace上面的堆栈信息,此堆栈示意在DebugDB.initialize()中调用了定位权限,此时咱们能够点击前面括号中的的信息跳转到该办法,而后就可以看失去此办法是属于哪个SDK的了。

(笔者悲催的发现这个 SDK 是没有中央应用的,属于被废除掉的了,如果要一个个去查所有应用到的 SDK 里是否调用了权限,那将是十分大的工作量,大家也能够趁机看一下本人我的项目里哪些不再须要的 SDK ,早删早轻松)

按归因标记应用

  1. 当只须要检测某个页面中是否调用了隐衷权限的话,只须要在须要检测的Activity中的onCreate注册该回调。
  2. 当须要在利用中检测所有页面是否调用了隐衷权限的话,须要在利用的Application中的onCreate中注册该回调。
  3. 当须要检测特定的权限时,能够应用按归因标记审核数据拜访,艰深点来说就是创立专属的Tag标记。

这里的场景一跟场景二下面曾经阐明了,区别在于在不同的中央注册而已,当初次要是要讲一下场景三,援用一下官网文档的一句话:

如果您在某个 Activity 中拜访数据(例如申请地位信息或拜访用户的联系人列表),请在该 Activity 的 onCreate()办法中调用 createAttributionContext(),并传入您心愿与利用的一部分相关联的归因标记。

举个例子,也就是说如果你须要在某个Activity中调用LocationManager,你须要先创立一个与之关联的 Tag 标记,如下代码段所示:

    private lateinit var attributionContext: Context    private lateinit var locationManager: LocationManager        override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)                // 创立归因标记,简称Tag        attributionContext = createAttributionContext("findLocation")        // 通过下面的attributionContext来创立locationManager实例,关联归因标记        locationManager = attributionContext.getSystemService(LocationManager::class.java) as LocationManager    }

下面能够看到咱们通过createAttributionContext("findLocation")创立了一个 Tag,而后依据这个 context 来创立LocationManager,在当前须要调用定位的中央就能够间接应用这个与 Tag 相关联的 locationManager 了,接下来咱们只须要略微批改一下刚刚所应用的AppOpsManager.OnOpNotedCallback即可:

    private lateinit var attributionContext: Context    private lateinit var locationManager: LocationManager        override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)                // 创立归因标记,简称Tag        attributionContext = createAttributionContext("findLocation")        // 通过下面的attributionContext来创立locationManager实例,关联归因标记        locationManager = attributionContext.getSystemService(LocationManager::class.java) as LocationManager                val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() {            // 新增attributionTag参数            private fun logPrivateDataAccess(opCode: String, attributionTag: String, trace: String) {                Log.i("youzi", "Private data accessed. Operation: $opCode\n Attribution Tag:$attributionTag\n Stack Trace:\n $trace")            }            override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {                syncNotedAppOp.attributionTag?.let {                    logPrivateDataAccess(syncNotedAppOp.op, it, Throwable().stackTrace.toString())                }            }            override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {                syncNotedAppOp.attributionTag?.let {                    logPrivateDataAccess(syncNotedAppOp.op, it, Throwable().stackTrace.toString())                }            }            override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {                asyncNotedAppOp.attributionTag?.let {                    logPrivateDataAccess(syncNotedAppOp.op, it, Throwable().stackTrace.toString())                }            }        }        // 创立AppOpsManager实例并增加下面定义的回调        val appOpsManager = getSystemService(AppOpsManager::class.java) as AppOpsManager        appOpsManager.setOnOpNotedCallback(mainExecutor, appOpsCallback)    }

咱们通过为刚刚的logPrivateDataAccess新增了一个须要传的参数attributionTag,而后在重写AppOpsManager.OnOpNotedCallback()中的三个办法时,判断一下 attributionTag 是否为 null ,如果为 null 的话则示意与咱们须要检测的权限无关联,此时就不须要打印。

如果在利用中给多种隐衷权限设置了Tag,则能够在回调中拿到attributionTag的时候,判断一下是否是本人想要检测的那个Tag~

这个例子是写在 Activity 中的,如果须要全局应用的话,能够本人定义一个 LocationManager 的单例,而后对立设置 Tag,以便当前须要检测的时候能够应用归因标记形式。

总结

顺便总结一下,在应用上面这段代码的时候,居然不晓得wifiManager.connectionInfo这个办法会调用定位,当初“粗浅”的记住了。

    val wifiManager = applicationContext.getSystemService(WIFI_SERVICE) as WifiManager    val wifiInfo = wifiManager.connectionInfo