Android性能优化系列-腾讯matrix-电量优化之Wifi耗电监控-WifiMonitorFeature源码分析

这是性能优化系列之matrix框架的第19篇文章,我将在性能优化专栏中对matrix apm框架做一个全面的代码分析,性能优化是Android高级工程师必知必会的点,也是面试过程中的高频题目,对性能优化感兴趣的读者可以去我主页查看所有关于matrix的分享。

前言

在上一篇Android性能优化系列-腾讯matrix-电量优化之BatteryMonitorPlugin源码分析中我们对matrix电量监控进行了初步分析,BatteryMonitorPlugin的工作机制大致可以分为两种,一种是通过各个feature类进行的不同场景下的监控;另一种是通过监听如电量、亮屏灭屏等广播来监控电量信息。在上一篇文中我们对第二种机制已经有了了解,接下来我们的任务是对BatteryMonitorPlugin中的feature进行逐个分析,以达到对BatteryMonitorPlugin有一个全面认识的目的。BatteryMonitorPlugin中的feature如下,今天我们从WifiMonitorFeature的源码开始。

  • WifiMonitorFeature 用于记录wifi相关的操作
  • BlueToothMonitorFeature 用于记录蓝牙相关的操作
  • WakeLockMonitorFeature 用于记录亮屏相关的操作
  • TrafficMonitorFeature 用于记录流量相关的操作
  • NotificationMonitorFeature 用于记录通知相关的操作
  • LooperTaskMonitorFeature 用于记录主线程消息队列相关的操作
  • LocationMonitorFeature 用于记录定位相关的操作
  • JiffiesMonitorFeature 用于记录jiffies相关的操作
  • InternalMonitorFeature 用于记录内部状态相关的操作
  • DeviceStatMonitorFeature 用于记录设备状态相关的操作
  • CpuStatFeature 用于记录cpu状态相关的操作
  • AppStatMonitorFeature 用于记录app状态相关的操作
  • AlarmMonitorFeature 用于记录闹钟相关的操作
  • BatteryStatsFeature 用于记录电池温度,电池剩余电量等信息
  • CompositeMonitors 非AbsMonitorFeature子类,但是也比较关键

WifiMonitorFeature继承自AbsMonitorFeature,AbsMonitorFeature实现了接口MonitorFeature,所以我们就从接口定义的几个关键方法入手:

  • configure
  • onTurnOn
  • onTurnOff
  • onForeground
  • onBackgroundCheck
  • weight

configure

configure方法就是接收了BatteryMonitorCore引用并保存在变量this.mCore中,此方法定义在父类AbsMonitorFeature中,所以每个feature都持有BatteryMonitorCore的引用。

less 复制代码
@CallSuper
@Override
public void configure(BatteryMonitorCore monitor) {
    MatrixLog.i(getTag(), "#configure");
    this.mCore = monitor;
}

onTurnOn

onTurnOn表示开启,在BatteryMonitorPlugin启动的时候,它会一次性启动所有配置的feature,也就是调用它的onTurnOn方法。方法在开启了ams hook的条件下,才会真正执行,执行时通过WifiManagerServiceHooker调用addListener方法将当前listener添加到监听队列中,从而可以拿到两个回调,onStartScan和onGetScanResults,这两个方法分别对应了WifiManager中的startScan和getScanResults方法,也就是说当WifiManager的这两个方法被调用时,就会回调到这里。

它是怎么做到的?我们先留一个疑问。

当两个方法调用时,会获取当前堆栈信息存入mTracing对象中,mTracing对象的类是WifiTracing,用于记录wifi信息,包括扫描次数,查询次数,和当前堆栈。

scss 复制代码
@Override
public void onTurnOn() {
    super.onTurnOn();
    if (mCore.getConfig().isAmsHookEnabled) {
        mListener = new WifiManagerServiceHooker.IListener() {
            @Override
            public void onStartScan() {
                //如果开启了记录,则获取到当前堆栈信息,设置到mTracing对象中
                String stack = shouldTracing() ? mCore.getConfig().callStackCollector.collectCurr() : "";
                mTracing.setStack(stack);
                mTracing.onStartScan();
            }

            @Override
            public void onGetScanResults() {
                //同样,记录当前堆栈信息到mTracing对象中,注意这些信息都是实时的,onGetScanResults
                //调用时的堆栈会覆盖掉onStartScan时的堆栈
                String stack = shouldTracing() ? mCore.getConfig().callStackCollector.collectCurr() : "";
                mTracing.setStack(stack);
                mTracing.onGetScanResults();

            }
        };
        WifiManagerServiceHooker.addListener(mListener);
    }
}

onTurnOff

onTurnOff表示监控被关闭,当方法调用时,会从WifiManagerServiceHooker中移除mListener监听,并清空mTracing中的数据。

scss 复制代码
@Override
public void onTurnOff() {
    super.onTurnOff();
    WifiManagerServiceHooker.removeListener(mListener);
    mTracing.onClear();
}

onForeground

WifiMonitorFeature并没有实现这个方法,所以不必关注。

onBackgroundCheck

WifiMonitorFeature并没有实现这个方法,所以不必关注。

weight

weight是一个数值,表示当前feature的权重,BatteryMonitorPlugin创建时会根据这个值对所有feature进行排序。

csharp 复制代码
@Override
public int weight() {
    return Integer.MIN_VALUE;
}

至此,我们就把WifiMonitorFeature第一层的实现分析完了,可以直观的了解到,WifiMonitorFeature就是通过hook WifiManager的startScan和getScanResults两个方法,来监听到wifi scan和query两个操作的次数,以及操作时的堆栈调用信息,那么具体hook是怎么实现的,我们就要到WifiManagerServiceHooker中看一下了。

WifiManagerServiceHooker

前边提到WifiManagerServiceHooker通过调用addListener方法将当前listener加入监听队列,我们看看addListener的实现。

addListener

可以看到,添加listener的同时,还会调用doHook,这个应该就是真正进行hook操作的地方了。

scss 复制代码
public synchronized static void addListener(IListener listener) {
    sListeners.add(listener);
    checkHook();
}
ini 复制代码
private static void checkHook() {
    if (sTryHook) {
        return;
    }
    boolean hookRet = sHookHelper.doHook();
    sTryHook = true;
}

doHook是SystemServiceBinderHooker中的方法,SystemServiceBinderHooker是WifiManagerServiceHooker中的一个静态对象,所以在类加载阶段,它就会被创建,注意它传入的两个参数,WIFI_SERVICE就是"wifi",然后是"android.net.wifi.IWifiManager"。

java 复制代码
private static SystemServiceBinderHooker sHookHelper = new SystemServiceBinderHooker(Context.WIFI_SERVICE, "android.net.wifi.IWifiManager", sHookCallback);

doHook

ini 复制代码
public boolean doHook() {
    //第一步
    BinderProxyHandler binderProxyHandler = new BinderProxyHandler(mServiceName, mServiceClass, mHookCallback);
    //第二步
    IBinder delegateBinder = binderProxyHandler.createProxyBinder();
    //第三步
    Class<?> serviceManagerCls = Class.forName("android.os.ServiceManager");
    Field cacheField = serviceManagerCls.getDeclaredField("sCache");
    cacheField.setAccessible(true);
    Map<String, IBinder> cache = (Map) cacheField.get(null);
    cache.put(mServiceName, delegateBinder);

    mDelegateServiceBinder = delegateBinder;
    mOriginServiceBinder = binderProxyHandler.getOriginBinder();
    return true;
}

第一步

上边的代码我们一行一行来看,首先创建BinderProxyHandler,传入的serviceName就是"wifi",serviceClass为"android.net.wifi.IWifiManager"。

scss 复制代码
BinderProxyHandler(String serviceName, String serviceClass, HookCallback callback) throws Exception {
    //获取到wifi服务的binder对象,保存在BinderProxyHandler的mOriginBinder变量上
    mOriginBinder = getCurrentBinder(serviceName);
    mServiceManagerProxy = createServiceManagerProxy(serviceClass, mOriginBinder, callback);
}

看看getCurrentBinder的实现,通过反射获取到ServiceManager类中的Method-getService,然后invoke获取到wifi服务的binder对象(ServiceManager中map集合保存了所有服务的binder)。

arduino 复制代码
static IBinder getCurrentBinder(String serviceName) throws Exception {
    Class<?> serviceManagerCls = Class.forName("android.os.ServiceManager");
    Method getService = serviceManagerCls.getDeclaredMethod("getService", String.class);
    return  (IBinder) getService.invoke(null, serviceName);
}

继续再看下createServiceManagerProxy方法,传入的serviceClassName就是上边提到的"android.net.wifi.IWifiManager",它本身是一个aidl文件,编译后会生成对应的Java文件,所以这里是获取到aidl生成的Java类中的静态内部类Stub,然后反射得到它的Method-asInterface,再调用asInterface传入getCurrentBinder方法中获取到的binder对象,拿到的object对象是谁?就是WifiManager对象(这块逻辑不太清楚的读者,可以回顾一下实用aidl进行进程间通信的实现流程)。拿到WifiManager对象之后对IWifiManager等接口做动态代理,所以就可以拦截到其方法的执行,于是下一步就进入前边传入的callback了。

dart 复制代码
private static Object createServiceManagerProxy(String serviceClassName, IBinder originBinder, final HookCallback callback) throws Exception  {
    Class<?> serviceManagerCls = Class.forName(serviceClassName);
    Class<?> serviceManagerStubCls = Class.forName(serviceClassName + "$Stub");
    ClassLoader classLoader = serviceManagerStubCls.getClassLoader();
    if (classLoader == null) {
        throw new IllegalStateException("get service manager ClassLoader fail!");
    }
    Method asInterfaceMethod = serviceManagerStubCls.getDeclaredMethod("asInterface", IBinder.class);
    //originManagerService就是WifiManager对象,拿到WifiManager对象之后对IWifiManager等接口做动态代理
    final Object originManagerService = asInterfaceMethod.invoke(null, originBinder);
    return Proxy.newProxyInstance(classLoader,
            new Class[]{IBinder.class, IInterface.class, serviceManagerCls},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    if (callback != null) {
                        //拦截到相关方法
                        callback.onServiceMethodInvoke(method, args);
                        Object result = callback.onServiceMethodIntercept(originManagerService, method, args);
                        if (result != null) {
                            return result;
                        }
                    }
                    return method.invoke(originManagerService, args);
                }
            }
    );
}

BinderProxyHandler的构造方法就做了这些,反射获取到WifiManager相关的信息,并动态代理了IWifiManager接口,从而可以拦截到startScan,getScanResults等方法。

第二步

下一步,创建BinderProxyHandler对象之后,调用binderProxyHandler.createProxyBinder(),又对IBinder做了动态代理。

csharp 复制代码
public IBinder createProxyBinder() throws Exception  {
    Class<?> serviceManagerCls = Class.forName("android.os.ServiceManager");
    ClassLoader classLoader = serviceManagerCls.getClassLoader();
    if (classLoader == null) {
        throw new IllegalStateException("Can not get ClassLoader of " + serviceManagerCls.getName());
    }
    return (IBinder) Proxy.newProxyInstance(
            classLoader,
            new Class<?>[]{IBinder.class},
            this
    );
}

从它的invoke方法可以看出,它是想拦截queryLocalInterface方法,当queryLocalInterface方法被调用时,直接返回前边创建的mServiceManagerProxy对象,也就是WifiManager的代理对象。

typescript 复制代码
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if ("queryLocalInterface".equals(method.getName())) {
        return mServiceManagerProxy;
    }
    return method.invoke(mOriginBinder, args);
}

第三步

接下来的代码,通过再次反射ServiceManager拿到它的成员变量sCache,sCache是一个HashMap集合,内部存储着所有的Android服务的binder对象,所以这里sCache拿到这个Map集合,然后将上一步获取到的代理binder对象添加到Map集合中,也就是用代理对象覆盖掉原来的binder对象,

ini 复制代码
Class<?> serviceManagerCls = Class.forName("android.os.ServiceManager");
Field cacheField = serviceManagerCls.getDeclaredField("sCache");
cacheField.setAccessible(true);
Map<String, IBinder> cache = (Map) cacheField.get(null);
cache.put(mServiceName, delegateBinder);
//保存代理对象和源wifi服务的binder对象,用于后边恢复
mDelegateServiceBinder = delegateBinder;
mOriginServiceBinder = binderProxyHandler.getOriginBinder();

总结一下以上三步的内容,一共创建了两个代理对象,一个是远程wifi服务的Binder代理对象,通过对IBinder接口做动态代理,并将代理对象设置到ServiceManager中,这样一来,wifi的binder对象被调用时,都会走到这里。

vbnet 复制代码
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if ("queryLocalInterface".equals(method.getName())) {
        return mServiceManagerProxy;
    }
    return method.invoke(mOriginBinder, args);
}

另一个是WifiManager本地服务的代理对象,通过对IWifiManager接口做动态代理,从而拦截到WifiManager的所有接口调用,两个动态代理衔接起来,调用binder的queryLocalInterface接口时,返回WifiManager本地服务的代理对象,这样一来,所有WifiManager的接口调用就都在监控范围之内了。

invoke

hook的实现我们分析完了,接下来回到WifiMonitorFeature的逻辑中,当WifiManager动态代理拦截到接口执行时,会通过onServiceMethodInvoke和onServiceMethodIntercept方法回调到调用方,我们看下WifiMonitorFeature中的这两个方法。

sql 复制代码
if (callback != null) {
    callback.onServiceMethodInvoke(method, args);
    Object result = callback.onServiceMethodIntercept(originManagerService, method, args);
    if (result != null) {
        return result;
    }
}
return method.invoke(originManagerService, args);

onServiceMethodInvoke

可以看到,WifiMonitorFeature只处理了onServiceMethodInvoke方法,当方法执行时,如果执行的方法是startScan和getScanResults,WifiMonitorFeature会做处理将事件向下分发。

typescript 复制代码
public void onServiceMethodInvoke(Method method, Object[] args) {
    if ("startScan".equals(method.getName())) {
        WifiManagerServiceHooker.dispatchStartScan();
    } else if ("getScanResults".equals(method.getName())) {
        WifiManagerServiceHooker.dispatchGetScanResults();
    }

}

@Nullable
public Object onServiceMethodIntercept(Object receiver, Method method, Object[] args) {
    return null;
}

事件分发后来到这两个方法中,看到这里,终于和我们在文章开头的分析形成了闭环,WifiMonitorFeature通过hook WifiManager的方式,拦截到startScan和getScanResults两个方法,并在方法被调用时,记录调用的次数和调用时方法的堆栈信息,实时存储在mTracing对象中。外界就可以实时的拿到wifi执行的信息了。

arduino 复制代码
public void onStartScan() {
    String stack = WifiMonitorFeature.this.shouldTracing() ? WifiMonitorFeature.this.mCore.getConfig().callStackCollector.collectCurr() : "";
    WifiMonitorFeature.this.mTracing.setStack(stack);
    WifiMonitorFeature.this.mTracing.onStartScan();
}

public void onGetScanResults() {
    String stack = WifiMonitorFeature.this.shouldTracing() ? WifiMonitorFeature.this.mCore.getConfig().callStackCollector.collectCurr() : "";
    WifiMonitorFeature.this.mTracing.setStack(stack);
    WifiMonitorFeature.this.mTracing.onGetScanResults();
}

总结

经过今天的分析,我们知道了,WifiMonitorFeature作为matrix电量监控中的一环,它的实现是通过hook WifiManager从而拦截到wifi扫描和获取wifi列表这两个方法的执行,并在执行时记录执行的次数和执行时方法调用的堆栈,将信息实时的记录到对象中,供外界取用的。那为什么matrix针对wifi的电量消耗只针对这两个执行方法呢?其实也很简单,因为这两个方法的执行就是相对来说,最耗电的。

相关推荐
小杨40435 分钟前
高级并发编程系列七(锁入门)
java·后端·性能优化
guoruijun_2012_41 小时前
hyperf 配置步骤
android
500了1 小时前
Android和Java的发布/订阅事件总线EventBus
android·java·开发语言
诸神黄昏EX1 小时前
Android 常用命令和工具解析之Trace相关
android
明天再做行么3 小时前
PHP8解析php技术10个新特性
android·php
Ting丶丶3 小时前
安卓应用安装过程学习
android·学习·安全·web安全·网络安全
kingdawin4 小时前
Android系统开发-判断相机是否在使用
android
美狐美颜sdk5 小时前
从零开始:如何使用第三方视频美颜SDK开发实时直播美颜平台
人工智能·计算机视觉·性能优化·美颜sdk·第三方美颜sdk·美颜api
恋猫de小郭5 小时前
IntelliJ IDEA 2024.3 K2 模式已发布稳定版,Android Studio Meerkat 预览也正式支持
android·android studio
找藉口是失败者的习惯9 小时前
Jetpack Compose 如何布局解析
android·xml·ui