• 电量优化 - Hook 系统服务


    上一篇文章《电量优化 - 电量的统计原理与监控》已经讲到了 Android App 电量的计算方式,也分析了系统源码 Android 是怎么统计电量的。那么现在我们可以开始给自己的 App 开发电量异常检测功能了,实现的方案就是用系统源码类似的计算方案,在 App 内部进行电量统计,主要也就两个部分:线程监控与系统服务调用监控。如果大家觉得麻烦的话可以尝试一下我们的开源方案 matrix-battery-canary ,这套方案在我们的项目项目中全量运行了一年多,期间发现了很多电量问题。如果大家感兴趣,这期文章我先带大家来实现系统服务调用监控,后面再带大家实现线程监控。

    如何 Hook 系统服务的调用?主流上一般有三种方案:字节码插桩,动态代理,Native Hook。这三种方案我们都有讲过也有用过,这里我们用动态代理来实现,大家可以自己先去试着实现下。套路印象中至少应该讲了十次,第一步肯定首先是要看源码流程了,第二步找单例和接口切入点,第三步就是设计实现类。源码我就简单贴了,因为在 《Framework 源码分析》中都讲到过了:

    // 调用一般都是通过 context 获取系统服务
    WifiManager mWifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
    mWifi.startScan();
    
    • 1
    • 2
    • 3

    对应找到 /frameworks/base/core/java/android/app/ContextImpl.java 中的 getSystemService 方法

        @Override
        public Object getSystemService(String name) {
            return SystemServiceRegistry.getSystemService(this, name);
        }
    
    • 1
    • 2
    • 3
    • 4

    再找到 /frameworks/base/core/java/android/app/SystemServiceRegistry.java 中的 getSystemService 方法

        private static final HashMap> SYSTEM_SERVICE_FETCHERS = new HashMap>();
        
        static {
            registerService(Context.WIFI_SERVICE, WifiManager.class,
                    new CachedServiceFetcher() {
                @Override
                public WifiManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                    IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_SERVICE);
                    IWifiManager service = IWifiManager.Stub.asInterface(b);
                    return new WifiManager(ctx.getOuterContext(), service,
                            ConnectivityThread.getInstanceLooper());
                }});
        }
    
        /**
         * Gets a system service from a given context.
         */
        public static Object getSystemService(ContextImpl ctx, String name) {
            ServiceFetcher fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
            return fetcher != null ? fetcher.getService(ctx) : null;
        }
    
        private static  void registerService(String serviceName, Class serviceClass,
                ServiceFetcher serviceFetcher) {
            SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
            SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    看到这里第二步的方案已经出来了,单例就是 WifiManager 而接口对象就是 WifiManager 中的 mService 对象,只要 Hook 住 mService 就可以了,在 《Android 源码分析实战 - 授权时拦截 QQ 用户名和密码》一文中就是用的这种方案。这里我们再分析一个切入点,我们接着往 ServiceManager.getServiceOrThrow 中看:

        private static HashMap sCache = new HashMap();
    
        public static IBinder getService(String name) {
            try {
                IBinder service = sCache.get(name);
                if (service != null) {
                    return service;
                } else {
                    return Binder.allowBlocking(rawGetService(name));
                }
            } catch (RemoteException e) {
                Log.e(TAG, "error in getService", e);
            }
            return null;
        }
    
        public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
            final IBinder binder = getService(name);
            if (binder != null) {
                return binder;
            } else {
                throw new ServiceNotFoundException(name);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    再往 IWifiManager.Stub.asInterface 中看:

    public static IWifiManager asInterface(android.os.IBinder obj)
        {
          if ((obj==null)) {
            return null;
          }
          android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
          if (((iin!=null)&&(iin instanceof IWifiManager))) {
            return ((IWifiManager)iin);
          }
          return new IWifiManager.Stub.Proxy(obj);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    看到这里我们就有了第二种方案了,Hook 住 Binder 对象的 queryLocalInterface 方法返回一个代理对象即可。最后一步就是设计实现类了:

    public class SystemServiceBinderHooker {
        public interface HookCallback {
            void onServiceMethodInvoke(Method method, Object[] args);
    
            Object onServiceMethodIntercept(Object receiver, Method method, Object[] args) throws Throwable;
        }
    
        private String mServiceName;
        private String mServiceClassName;
        private HookCallback mHookCallback;
    
        public SystemServiceBinderHooker(String serviceName, String serviceClassName, HookCallback hookCallback){
            this.mServiceName = serviceName;
            this.mServiceClassName = serviceClassName;
            this.mHookCallback = hookCallback;
        }
    
        public boolean hook(){
            try {
                // 1. 先获取 origin 的 IBinder 对象
                Class serviceManagerClass = Class.forName("android.os.ServiceManager");
    
                Method getServiceMethod = serviceManagerClass.getDeclaredMethod("getService",String.class);
    
                getServiceMethod.setAccessible(true);
    
                final IBinder serviceBinder = (IBinder) getServiceMethod.invoke(null,mServiceName);
    
                // 2. hook 住 serviceBinder 创建代理对象
                IBinder proxyServiceBinder = (IBinder) Proxy.newProxyInstance(serviceManagerClass.getClassLoader(), new Class[]{IBinder.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if (TextUtils.equals(method.getName(), "queryLocalInterface")) {
                            return createServiceProxy(serviceBinder);
                        }
                        return method.invoke(serviceBinder, args);
                    }
                });
    
                // 3. 把代理对象塞到 ServiceManager 中的 sCache
                Field sCacheField = serviceManagerClass.getDeclaredField("sCache");
                sCacheField.setAccessible(true);
                Map sCache = (Map) sCacheField.get(null);
                sCache.put(mServiceName, proxyServiceBinder);
    
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        private Object createServiceProxy(IBinder serviceBinder) {
            try {
                // new IWifiManager.Stub.Proxy
                Class serviceProxyClass = Class.forName(mServiceClassName + "$Stub$Proxy");
                Constructor serviceProxyConstructor = serviceProxyClass.getDeclaredConstructor(IBinder.class);
                serviceProxyConstructor.setAccessible(true);
                final Object originServiceProxy = serviceProxyConstructor.newInstance(serviceBinder);
    
                // hook serviceProxy
                Object serviceProxyHooker = Proxy.newProxyInstance(serviceProxyClass.getClassLoader(), new Class[]{IBinder.class, IInterface.class, Class.forName(mServiceClassName)}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if (mHookCallback != null) {
                            mHookCallback.onServiceMethodInvoke(method, args);
                            Object result = mHookCallback.onServiceMethodIntercept(originServiceProxy, method, args);
                            if (result != null) {
                                return result;
                            }
                        }
                        return method.invoke(originServiceProxy, args);
                    }
                });
                return serviceProxyHooker;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    视频链接:https://pan.baidu.com/s/164aJyOYlXm-JCOC0_HVBtQ
    视频密码:vsfu

  • 相关阅读:
    RTKLIB算法优化之北斗伪距多路径代码
    Dataset 的一些 Java api 操作
    全局引入的js如何只让部分页面有效
    JVM基础06_StringTable
    Kafka三种认证模式,Kafka 安全认证及权限控制详细配置与搭建
    python中如何打印日志信息
    浅谈Python中列表元素的修改以及列表的统计与排序
    Arduino WIFI智能小车 无线视频遥控小车(论文+程序+原理图+驱动+安装手册等)
    <C++>解密 构造函数和析构函数
    软件开发详解:同城O2O与外卖跑腿系统源码的架构与开发要点
  • 原文地址:https://blog.csdn.net/z240336124/article/details/126688341