電量優(yōu)化 - Hook 系統(tǒng)服務(wù)

上一篇文章《電量優(yōu)化 - 電量的統(tǒng)計(jì)原理與監(jiān)控》已經(jīng)講到了 Android App 電量的計(jì)算方式,也分析了系統(tǒng)源碼 Android 是怎么統(tǒng)計(jì)電量的磨总。那么現(xiàn)在我們可以開始給自己的 App 開發(fā)電量異常檢測功能了哄啄,實(shí)現(xiàn)的方案就是用系統(tǒng)源碼類似的計(jì)算方案粗截,在 App 內(nèi)部進(jìn)行電量統(tǒng)計(jì)墙牌,主要也就兩個部分:線程監(jiān)控與系統(tǒng)服務(wù)調(diào)用監(jiān)控很澄。如果大家覺得麻煩的話可以嘗試一下我們的開源方案 matrix-battery-canary 乳讥,這套方案在我們的項(xiàng)目項(xiàng)目中全量運(yùn)行了一年多聪舒,期間發(fā)現(xiàn)了很多電量問題昆雀。如果大家感興趣辱志,這期文章我先帶大家來實(shí)現(xiàn)系統(tǒng)服務(wù)調(diào)用監(jiān)控,后面再帶大家實(shí)現(xiàn)線程監(jiān)控狞膘。

如何 Hook 系統(tǒng)服務(wù)的調(diào)用荸频?主流上一般有三種方案:字節(jié)碼插樁,動態(tài)代理客冈,Native Hook旭从。這三種方案我們都有講過也有用過,這里我們用動態(tài)代理來實(shí)現(xiàn)场仲,大家可以自己先去試著實(shí)現(xiàn)下和悦。套路印象中至少應(yīng)該講了十次,第一步肯定首先是要看源碼流程了渠缕,第二步找單例和接口切入點(diǎn)鸽素,第三步就是設(shè)計(jì)實(shí)現(xiàn)類。源碼我就簡單貼了亦鳞,因?yàn)樵?《Framework 源碼分析》中都講到過了:

// 調(diào)用一般都是通過 context 獲取系統(tǒng)服務(wù)
WifiManager mWifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mWifi.startScan();

對應(yīng)找到 /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);
    }

看到這里第二步的方案已經(jīng)出來了馍忽,單例就是 WifiManager 而接口對象就是 WifiManager 中的 mService 對象棒坏,只要 Hook 住 mService 就可以了,在 《Android 源碼分析實(shí)戰(zhàn) - 授權(quán)時攔截 QQ 用戶名和密碼》一文中就是用的這種方案遭笋。這里我們再分析一個切入點(diǎn)坝冕,我們接著往 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 方法返回一個代理對象即可瓦呼。最后一步就是設(shè)計(jì)實(shí)現(xiàn)類了:

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 創(chuàng)建代理對象
            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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喂窟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子央串,更是在濱河造成了極大的恐慌磨澡,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件质和,死亡現(xiàn)場離奇詭異稳摄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)饲宿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門厦酬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人褒傅,你說我怎么就攤上這事弃锐。” “怎么了殿托?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵霹菊,是天一觀的道長。 經(jīng)常有香客問我支竹,道長旋廷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任礼搁,我火速辦了婚禮饶碘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘馒吴。我一直安慰自己扎运,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布饮戳。 她就那樣靜靜地躺著豪治,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扯罐。 梳的紋絲不亂的頭發(fā)上负拟,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機(jī)與錄音歹河,去河邊找鬼掩浙。 笑死花吟,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的厨姚。 我是一名探鬼主播衅澈,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼遣蚀!你這毒婦竟也來了矾麻?” 一聲冷哼從身側(cè)響起纱耻,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤芭梯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后弄喘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玖喘,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年蘑志,在試婚紗的時候發(fā)現(xiàn)自己被綠了累奈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡急但,死狀恐怖澎媒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情波桩,我是刑警寧澤戒努,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站镐躲,受9級特大地震影響储玫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜萤皂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一撒穷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧裆熙,春花似錦端礼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纷跛,卻和暖如春喻括,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贫奠。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工唬血, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留望蜡,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓拷恨,卻偏偏與公主長得像脖律,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子腕侄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容