使用动态代理能够达到AOP编程的效果。现在流行的插件化框架也广泛使用了动态代理。本文主要讲解动态代理的Hook
机制
代理 代理可以实现方法增强,主要分为静态代理和动态代理。
静态代理 所有的原始类,代理类都提供好了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 interface DoSth { fun doSomething (sth: Long ) : Array<Any> } class DoSthImpl : DoSth { override fun doSomething (sth: Long ) : Array<Any> { println("DoSthImpl " + sth) return arrayOf("a" , "b" , "c" ); } } class ProxyDoSth (val base: DoSth) : DoSth { override fun doSomething (sth: Long ) : Array<Any> { println("ProxyDoSth " + (sth * 10 )) val sth = base.doSomething(sth) sth[0 ] = "d" return sth } } val sthImpl = DoSthImpl()val proxyDoSth = ProxyDoSth(sthImpl)println(Arrays.toString(proxyDoSth.doSomething(10 )))
代理对象实现将目标对象的实现行为改变了,将传入的值扩大10倍,并将返回的数组内容改变。因此代理模式是可以改变目标对象的行为的
动态代理 静态代理使用简单,但是比较繁琐。当需要代理的类较多时,会很麻烦。JDK提供了动态代理,运行时生成对应的代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class InvocationHandlerImpl (val base: Any) : InvocationHandler { override fun invoke (p0: Any?, p1: Method?, p2: Array<out Any>?) : Any { if ("doSomething" == (p1!!.name)) { val value: Long = p2!![0 ] as Long val doSthValue = value * 5 println(doSthValue) val invoke: Array<Any> = p1.invoke(base, doSthValue) as Array<Any> invoke[0 ] = "d" return invoke } return Unit } } val sthImpl = DoSthImpl()val result = Proxy.newProxyInstance(DoSth::class.java.classLoader, sthImpl.javaClass.interfaces, InvocationHandlerImpl(sthImpl))val ds = (result as DoSth).doSomething(10 )
代理模式,运行时生成对应代理接口的代理实现类,当目标方法调用时,触发Invocation.invoke
.
代理Hook Hook之前,得先找到Hook
点,一般是静态变量或者单例,因为这些对象不会经常变化。下面我们来分析如何Hook Activity
。都知道Activity
的启动都是通过Instrumentation
。而其ActivityThread
内部,一个进程只有一个ActivityThread
。因此只要Hook
住这个对象那么就可以操作Instrumentation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @SuppressLint("PrivateApi" ) class HookHelper { companion object { @Throws(Exception::class) fun attachContext () { val activityThreadClass = Class.forName("android.app.ActivityThread" ) val currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread" ) currentActivityThreadMethod.isAccessible = true val currentActivityThread = currentActivityThreadMethod.invoke(null ) val instrumentationField = activityThreadClass.getDeclaredField("mInstrumentation" ) instrumentationField.isAccessible = true val instrumentation = instrumentationField.get (currentActivityThread) as Instrumentation val proxyInstrumentation = ProxyInstrumentation(instrumentation) instrumentationField.set (currentActivityThread, proxyInstrumentation) } } }
HookHelper
通过反射获取当前进程的ActivityThread
,然后替换内部的Instrumentation
字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class ProxyInstrumentation (val instrumentation: Instrumentation) : Instrumentation() { companion object { val TAG = ProxyInstrumentation::class .java.canonicalName } fun execStartActivity ( who: Context , contextThread: IBinder ?, token: IBinder ?, target: Activity ?, intent: Intent , requestCode: Int , options: Bundle ?) : Instrumentation.ActivityResult? { Log.d(TAG, "\nstartActivity, who = [" + who + "], " + "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " + "\ntarget = [" + target + "], \nintent = [" + intent + "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]" ) try { val execStartActivity = Instrumentation::class .java.getDeclaredMethod("execStartActivity" , Context::class .java, IBinder::class .java, IBinder::class .java, Activity::class .java, Intent::class .java, Int ::class .javaPrimitiveType, Bundle::class .java) execStartActivity.isAccessible = true return execStartActivity.invoke(instrumentation, who, contextThread, token, target, intent, requestCode, options) as Instrumentation.ActivityResult? } catch (e: Exception) { throw RuntimeException("don't support hook" ) } } }
ProxyInstrumentation
继承自Instrumentation
,用于增强Instrumentation
。在每个Activity
启动之前打印日志
在App
启动的时候修改替换Instrumentation
1 2 3 4 5 6 7 8 9 10 class MyApp : Application () { override fun attachBaseContext (base: Context ?) { super .attachBaseContext(base) try { HookHelper.attachContext() } catch (e: Exception) { e.printStackTrace() } } }
这个时候启动App
,然后重新启动一个Activity
,那么会打印日志
1 startActivity, who = [cn.binea.pluginframeworkdemo.MyApp@3cc96c5], contextThread = [android.app.ActivityThread$ApplicationThread@56a681a], token = [null], target = [null], intent = [Intent { act=android.intent.action.VIEW dat=http://www.baidu.com/... flg=0x10000000 }], requestCode = [-1], options = [null]
至此,这个例子简单的Hook
了Activity
的启动,使其有了别的功能。