電量優化 - 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章