有时我们的项目会遇见如下所示的崩溃堆栈:
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2101)
at android.os.Handler.dispatchMessage(Handler.java:108)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7425)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
这种堆栈没有我们业务层代码的信息,所以我们一时间会不知所措,想try-catch捕获都捕获不了,因为都是系统层的崩溃。
那么这就引出一个问题,我们能不能捕获系统层的代码异常或普通逻辑呢?答案是可以的,我们可以用代理的方法来实现。
一.手动代理
1.溯源
这里我们以上述代码为例,尝试捕获系统层的主线程Handler抛出的异常。
首先我们根据崩溃堆栈来看系统层的实现。
public final class ActivityThread extends ClientTransactionHandler {
final H mH = new H();
class H extends Handler {
//...
}
}
一个app进程的入口就在ActivityThread中,这个类中有一个叫做H,继承自Handler的类,且在ActivityThread中,创建了一个该对象叫做mH,这个Handler对象,就是上面堆栈中的那个handleMessage()方法的调用者。
再来看Handler里是如何调用handleMessage()方法的。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {//Runnable
handleCallback(msg);
} else {
if (mCallback != null) {//Handler的callback对象
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);//重写该方法
}
}
当Looper将Message对象,传递到Handler的dispatchMessage()方法中时,首先会看Message的callback对象是否存在,存在的话直接回调,这个callback其实是Runnable对象,是我们经常使用的handler.post({})等方法传入的回调参数,在这里由于堆栈显示,走的是Handler的handleMessage()方法,所以显然这里的callback为null,走else分支。
在else分支里,还有一个处理优先级较高的mCallback,如果该mCallback处理结果为true则直接结束处理,否则交由Handler自身的dispatchMessage()方法处理。
public class Handler {
final Callback mCallback;
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
//...
mCallback = callback;
}
}
可见,Handler的callback,是构造函数构造时传入的,而ActivityThread中创建mH时,是直接走的默认构造函数,所以这个mCallback为null,所以上面说的else分支里,会直接调用Handler的dispatchMessage()方法,我们再来看dispatchMessage()方法。
class H extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
//...
case SCHEDULE_CRASH://flag=134
throw new RemoteServiceException((String)msg.obj);
}
}
}
在该方法里,如果Message的what是134的话,就会将Message的信息,抛出一个RemoteServiceException异常,也就是我们上面的堆栈信息来源。
2.手动代理
找到了源头,我们怎么解决呢?由于抛出异常的地方是系统层代码,我们无法直接在某个代码处try-catch住,于是我们想到了代理的方法,如果能找到一个可代理的对象,可以hook住系统层的mH的handleMessage()方法就好了。
于是我们想起了上面说的Handler的dispatchMessage()方法执行过程中,有一个Handler的callback可以加以利用。
public static class HookHandlerCallback implements Handler.Callback {
private final Handler.Callback delegate;
public HookHandlerCallback(Handler.Callback delegate) {
this.delegate = delegate;
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 134 && msg.obj instanceof String && ((String) msg.obj).contains("Context.startForegroundService() did not then call Service.startForeground()")) {
//..log
return true;//直接拦截不抛出异常
}
return delegate != null ? delegate.handleMessage(msg) : false;
}
}
如果我们能将ActivityThread的mH里面的mCallback替换为一个我们自定义的Callback,并代理原始mCallback,这样一来,处理Message时会优先交由我们的的callback处理,我们处理后,交由原始mCallback处理,现在mCallback为null,直接返回false,交由mH自身的handleMessage()处理即可,就可以实现代理的功能。
现在的问题就是如何获取原始的mCallback和如何改变mH的mCallback的指向,这个问题很简单,就是用反射。
我们先来看看系统源码。
public final class ActivityThread extends ClientTransactionHandler {
final H mH = new H();
private static volatile ActivityThread sCurrentActivityThread;
private void attach(boolean system, long startSeq) {
sCurrentActivityThread = this;
//...
}
}
sCurrentActivityThread在进程启动时就被赋值为当前进程主线程的ActivityThread对象。
再来看看如何获取mCallback。
// 获取全局的 ActivityThread 对象
Class<?> forName = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = forName.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
Object currentActivityThread = currentActivityThreadField.get(null);
// 获取ActivityThread 里面的 mH 对象
Field handlerField = forName.getDeclaredField("mH");
handlerField.setAccessible(true);
Handler handlerObject = (Handler) handlerField.get(currentActivityThread);
// 获取mH里面的 原始 mCallback 对象
Field callBackField = Handler.class.getDeclaredField("mCallback");
callBackField.setAccessible(true);
Handler.Callback previousCallback = (Handler.Callback) (callBackField.get(handlerObject));
//将mH的mCallback 替换成 自定义Callback,并代理原始mCallback对象
callBackField.set(handlerObject, new HookHandlerCallback(previousCallback));
至此,我们就完成了hook拦截系统代码的功能,主旨就是反射+代理。
二.动态代理
1. 溯源
上面的例子有一个特点,就是需要代理的对象,或者说类,都是public且不是@hide的,也就是公开的api,所以我们可以显式使用代码创建一个对象使用,那么还有一种情况是相反的,先看下面的例子。
class ContextImpl extends Context {
//检查权限
public int checkPermission(String permission, int pid, int uid) {
final IActivityManager am = ActivityManager.getService();
return am.checkPermission(permission, pid, uid);
}
//发送广播
public void sendBroadcast(Intent intent) {
ActivityManager.getService().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
getUserId());
}
//开启服务
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user) {
ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
getContentResolver()), requireForeground,
getOpPackageName(), user.getIdentifier());
}
}
我们经常使用context进行的诸如sendBroadcast()、startService()、checkPermission()等方法,都调用了ActivityManager里面的方法,而ActivityManager是实现Activity一切行为的类,那么有时我们就会希望拦截这些方法,就可以在Activity相关api被调用时可以搞一些事情。
那如何来做呢?想来看下ActivityManager实现。
public class ActivityManager {
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
}
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
-
ActivityManager的调用其实是交给IActivityManagerSingleton的get()方法返回的对象进行
-
Singleton其实是个懒加载类,只返回第一次调用时,create()方法返回的对象
-
IActivityManagerSingleton的get()方法,返回的是系统进程中,ActivityManager的接口,IActivityManager的Binder对象,即系统进程中ActivityManagerService的Binder对象,有关Binder和跨进程通信,可以参考这篇文章
现在,就可以参照上面说的方法,试着实现代理:
-
反射拿到IActivityManagerSingleton里面的mInstance对象
-
实现一个实现了IActivityManager接口的子类,并代理mInstance
-
通过反射,将mInstance对象赋值为这个代理对象
这里的问题出在第二步,这个IActivityManager接口,是系统层api,我们根本无法用代码实现,那怎么办呢?可以用动态代理的方法。
2. 动态代理
我们先来简单复习一下java的动态代理机制。
public static void main(String[] args) {
Jack jack = new Jack();
Star star = new StarProxy(jack).getProxy();
star.doAction("sing");
star.doAction("dance");
}
static class Jack implements Star {
@Override
public void doAction(String action) {
System.out.println(action);
}
}
interface Star {
void doAction(String action);
}
static class StarProxy implements InvocationHandler {
private final Star star;
public StarProxy(Star star) {
this.star = star;
}
public Star getProxy() {
return (Star) Proxy.newProxyInstance(star.getClass().getClassLoader(), star.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy prepare");
return method.invoke(star, args);
}
}
-
Star是一个接口,有一个doAction的方法
-
Jack实现了该接口,并实现了doAction方法,输出action参数
-
StarProxy类代理任意一个Star对象,并有一个getProxy方法用于获取代理对象
-
getProxy()方法中,使用Proxy.newProxyInstance()方法创建了一个,实现了Star接口的动态代理对象,并使用StarProxy自身作为InvocationHandler回调
-
InvocationHandler的invoke方法是代理对象被调用其实现的接口的方法时,被调用的方法,在调用被代理对象的相应方法前,会输出一个log
-
输出结果:在"sing"和"dance"输出前,分别会先输出一个"proxy prepare"
由此可见,动态代理Proxy的api的要点:
-
需要一个classLoader用来加载接口类
-
需要动态代理对象实现的接口(一定要注意只有接口才可以哦)类class对象
-
需要一个InvocationHandler
第一点好说,一般任意一个类的classLoader即可;第三点创建的对象,需要我们自己将被代理的原始对象传入维护即可;主要是第二点,由于IActivityManager类是系统api,我们无法直接获取类对象,那么我们可以用反射的方法来获取class对象:
Class iActivityManagerInterface = Class.forName("android.app.IActivityManager");
于是乎,我们就可以用动态代理的方法实现hook。
//获取ActivityManager类对象
val activityManagerClass = Class.forName("android.app.ActivityManager")
//获取IActivityManagerSingleton对象
val singletonDefaultField = activityManagerClass.getDeclaredField("IActivityManagerSingleton")
singletonDefaultField.isAccessible = true
val singletonDefault = singletonDefaultField.get(null)
//获取Singleton的原始mInstance对象
val singleton = Class.forName("android.util.Singleton")
val mInstanceField = singleton.getDeclaredField("mInstance")
mInstanceField.isAccessible = true
val rawIActivityManager = mInstanceField.get(singletonDefault)
//获取IActivityManager接口的类对象
val iActivityManagerInterface = Class.forName("android.app.IActivityManager")
//创建动态代理对象,并赋值给mInstance
val proxy = Proxy.newProxyInstance(activityManagerClass::class.java.classLoader,
arrayOf(iActivityManagerInterface), IActivityManagerHandler(rawIActivityManager))
mInstanceField.set(singletonDefault, proxy)
private class IActivityManagerHandler(private val rawIActivityManager: Any?) : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
//do something...
return if (args == null) {
method?.invoke(rawIActivityManager)
} else {
method?.invoke(rawIActivityManager, *args)
}
}
}
至此,我们就可以在任何IActivityManager相关的接口方法调用时,进行拦截了~