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 下期见。