电量优化 - Hook 系统服务

Posted HongChengDarren

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了电量优化 - Hook 系统服务相关的知识,希望对你有一定的参考价值。

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

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

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

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

    @Override
    public Object getSystemService(String name) 
        return SystemServiceRegistry.getSystemService(this, name);
    

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

    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new HashMap<String, ServiceFetcher<?>>();
    
    static 
        registerService(Context.WIFI_SERVICE, WifiManager.class,
                new CachedServiceFetcher<WifiManager>() 
            @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 <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) 
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    

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

    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

    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);
        
    

再往 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);
    

看到这里我们就有了第二种方案了,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<String, IBinder> sCache = (Map<String, IBinder>) 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;
        
    

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

以上是关于电量优化 - Hook 系统服务的主要内容,如果未能解决你的问题,请参考以下文章

电量优化 - Hook 系统服务

电量优化 - Hook 系统服务

电量优化 - Hook 系统服务

电量优化 - Hook 系统服务

android电量优化之Battery Historian工具使用

Android系统篇之----免root实现Hook系统服务拦截方法