Sensor在Android中涵盖了很多功能强大的传感器,例如加速度、角速度、重力传感器等,利用这些传感器数据可以实现很多实用的功能。例如重力感应游戏、水平仪检测、微信的摇一摇加好友、还有烦人的开屏广告里的摇一摇触发(恼火)。

具体可参考以下文章

Android 传感器开发详解 – ()
Android Sensor 传感器总结 – 简书 (jianshu.com)

正常使用Sensor传感器的数据相对简单那么如何做到监听甚至修改已经注册的SensorEventListener接收到的数据呢 Hook

先贴代码(jvm)

private val sensorManagers = mutableListOf<WeakReference<SensorManager>>()
private lateinit var sensorListenersField: Field
private lateinit var listenerField: Field
@SuppressLint("BlockedPrivateApi", "SoonBlockedPrivateApi", "PrivateApi")
internal fun prepare(): Boolean {    try {
        val registryClz = Class.forName("android.app.SystemServiceRegistry")
        val fetchersField = registryClz.getDeclaredField("SYSTEM_SERVICE_FETCHERS").apply {
            isAccessible = true
        }
        val map = fetchersField.get(null) as? java.util.Map<String, Any>
        if (map == null) {
            return false
        }
        val sensorService = map[Context.SENSOR_SERVICE]
        val cacheClz = Class.forName(
            "android.app.SystemServiceRegistry\$CachedServiceFetcher"
        )
        val sysSensorMgrClz = Class.forName("android.hardware.SystemSensorManager")
        val sensorEventQueueClz =
            Class.forName("android.hardware.SystemSensorManager\$SensorEventQueue")
        sensorListenersField = sysSensorMgrClz.getDeclaredField("mSensorListeners").apply {
            isAccessible = true
        }
        listenerField = sensorEventQueueClz.getDeclaredField("mListener").apply {
            isAccessible = true
        }
        val proxyFetcher = Proxy.newProxyInstance(
            cacheClz.classLoader,
            cacheClz.interfaces
        ) { _, method, args ->
            val obj = method(sensorService, *args)
            if (method.name == "getService") {
                if (obj is SensorManager) {
                    // 检查目标对象是否已经存在在list中
                    traversSensorManagers { it == obj }
                        ?: sensorManagers.add(WeakReference(obj))
                }
            }
            obj
        } as Any
        map.put(Context.SENSOR_SERVICE, proxyFetcher)
        return true
    } catch (e: Exception) {
    }
    return false
}
internal fun traversSensorManagers(predicate: (mgr: SensorManager) -> Boolean): SensorManager? {
    sensorManagers.iterator().apply {
        while (hasNext()) {
            val weakRef = next()
            val sensorMgr = weakRef.get()
            if (sensorMgr == null) {
                remove()
                continue
            }
            if (predicate(sensorMgr)) {
                return sensorMgr
            }
        }
    }
    return null
}
private fun traversListener(predicate: (listener: SensorEventListener, eventQueue: Any?) -> Boolean) {
    traversSensorManagers { sensorMgr ->
        val listeners = sensorListenersField.get(sensorMgr) as Map<SensorEventListener, *>
        listeners.values.find { value ->
            val eventListener = listenerField.get(value) as SensorEventListener
            predicate(eventListener, value)
        } != null
    }
}
internal fun hookSensorEventListener(proxier: (listener: SensorEventListener) -> InvocationHandler) {
    traversListener { eventListener, eventQueue ->
        if (Proxy.isProxyClass(eventListener.javaClass)) {
            return@traversListener false
        }
        val proxyObj = Proxy.newProxyInstance(
            eventListener.javaClass.classLoader,
            arrayOf(SensorEventListener::class.java),
            proxier(eventListener)) as SensorEventListener
        listenerField.set(eventQueue, proxyObj)
        false
    }
}

实现方案和思路不算复杂,既然要hook SensorEventListener,而它又是一个接口,那么可以很直观的想到动态代理。那么接下来的核心目的就是想办法通过反射的方式来找到已注册的SensorEventListener实例了。

SensorEventListener时如何注册的(找实例)

 mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
    mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);  
  mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_NORMAL);

以上是简略的listener注册流程,所以SensorEventListener大概就是注册在SensorManager里的

registerListener 最终调用到SensorManager的 registerListenerImpl方法

而SensorManger本身是抽象类,它的具体逻辑是由 SystemSensorManager 实现的。

 /** @hide */
    @Override
    protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
            int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags) {
        if (listener == null || sensor == null) {
            Log.e(TAG, "sensor or listener is null");
            return false;
        }
        // Trigger Sensors should use the requestTriggerSensor call.
        if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
            Log.e(TAG, "Trigger Sensors should use the requestTriggerSensor.");
            return false;
        }
        if (maxBatchReportLatencyUs < 0 || delayUs < 0) {
            Log.e(TAG, "maxBatchReportLatencyUs and delayUs should be non-negative");
            return false;
        }
        if (mSensorListeners.size() >= MAX_LISTENER_COUNT) {
            throw new IllegalStateException("register failed, "
                + "the sensor listeners size has exceeded the maximum limit "
                + MAX_LISTENER_COUNT);
        }
        // Invariants to preserve:
        // - one Looper per SensorEventListener
        // - one Looper per SensorEventQueue
        // We map SensorEventListener to a SensorEventQueue, which holds the looper
        synchronized (mSensorListeners) {
            SensorEventQueue queue = mSensorListeners.get(listener);
            if (queue == null) {
                Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
                final String fullClassName =
                        listener.getClass().getEnclosingClass() != null
                            ? listener.getClass().getEnclosingClass().getName()
                            : listener.getClass().getName();
                queue = new SensorEventQueue(listener, looper, this, fullClassName);
                if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) {
                    queue.dispose();
                    return false;
                }
                mSensorListeners.put(listener, queue);
                return true;
            } else {
                return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs);
            }
        }
    }

大致步骤

1.安全性校验

只接收单次触发的sensor不应使用这种注册方式
maxBatchReportLatencyUs and delayUs的参数设置也必须为非负数
SensorEventListener的注册有数量限制(128),无法无节制使用。

实测如果对回调频率不加限制(SensorManager.SENSOR_DELAY_FASTEST)例如角速度、重力加速度等listener的回调每秒会触发很多次,这可能也是有注册数量限制的一个原因。

2.如果此SensorEventListener没有被注册过

在主线程或用户指定线程创建SensorEventQueue

3.给SensorEventQueue注册新的sensor(监听新的SensorType)

此处SensorEventQueue和SensorEventListener为一一对应关系

每个SensorEventListener只能在单一线程注册

SensorEventQueue和SensorEventListener被维护在一个mSensorListeners中(map)

private final HashMap<SensorEventListener, SensorEventQueue> mSensorListeners =        new HashMap<SensorEventListener, SensorEventQueue>();

如此我们已经找到了SensorEventListener的实例。

如何替换?

mSensorListeners和SensorEventQueue都持有了SensorEventListener

而mSensorListeners中SensorEventListener以key的形式持有,上面代码也可以看出后续新类型的注册也依赖于此key,所以定然不能替换这个

在SensorEventQueue中真正调用了android.hardware.SensorEventListener#onSensorChanged

那么就是它了。

只要反射SensorEventQueue中的mListener对象就可以将listener替换为我们的proxy

接下来的步骤就是一步一步反射拿到我们想要的对象了。

获取SensorManager

sensor的获取由SystemServiceRegistry负责

由SystemServiceRegistry.ServiceFetcher#getService获取最终的SensorManager对象

此fetcher被维护在SYSTEM_SERVICE_FETCHERS(Map中)

private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =        new ArrayMap<String, ServiceFetcher<?>>();

那么我们只需要在较早的时机hook这个接口即可或得到后续调用getService的全部SensorManager对象

完成了这一步就可以按照开头贴的代码完成SensorHook了

HiddenApi

不过需要注意的是例如SystemSensorManager等类是不向App开放的,在Android P及以上版本,如果不处理在执行上述步骤时会抛出NoSuchFieldException异常

网上也有很解决多方案至少截止 API 33还可使用

存在的问题 – 动态代理

这本身就是问题,由动态代理替换的接口会直接提现在调用栈上,被hook的接口显而易见地增加了一层调用。

如何解决? native hook 下期见。