上一篇文章《電量優(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