这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
Hook - View的点击事件
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "" + ((Button) v).getText(), Toast.LENGTH_SHORT).show();
}
});
上面代码很简单,定义button
组件,并且设置点击监听器,打印button
上的文字.现在的需求是不修改上面的代码情况下,通过Hook
把((Button) v).getText()
的内容修改.
View.java 首先要知道
setOnClickListener
设置的值传到哪里。
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
static class ListenerInfo {
//设置的OnClickListener,最后赋值在这里
public OnClickListener mOnClickListener;
}
通过阅读View
的源码可以知道,setOnClickListener
方法是把OnClickListener
参数的值设置给getListenerInfo()
返回对象的mOnClickListener
属性中.
要做到动态修改代码的功能,可以使用动态代理,或者ASM
技术。这里就使用动态代理
Class mViewClass = Class.forName("android.view.View");
Method getListenerInfoMethod = mViewClass.getDeclaredMethod("getListenerInfo");
getListenerInfoMethod.setAccessible(true); // 设置为public
// 执行方法
Object mListenerInfo = getListenerInfoMethod.invoke(view);
// 因为ListenerInfo是静态类
Class mListenerInfoClass = Class.forName("android.view.View$ListenerInfo");
Field mOnClickListenerField = mListenerInfoClass.getField("mOnClickListener");
final Object mOnClickListenerObj = mOnClickListenerField.get(mListenerInfo);
要实现动态代理,最重要的一步是拿到原来的mOnClickListener
,首先通过Class.forName("android.view.View")
,拿到ViewClass
,然后调用它的getListenerInfo
方法,因为它是私有的,所以必须setAccessible(true)
,然后反射获取到ListenerInfo
实例,因为ListenerInfo
是静态类,所以获取它的class
时,需要添加$
,如Class.forName("android.view.View$ListenerInfo")
,获取到ListenerInfo
的class
之后,就可以通过 mListenerInfoClass.getField("mOnClickListener")
获取到属性mOnClickListener
的值了。
通过上面几步,已经获取到当前button
设置的listener
了,现在要做的时在原来的listener
包装一层,狸猫换太子
Object mOnClickListenerProxy = Proxy.newProxyInstance(MainActivity.class.getClassLoader(), // 1加载器
new Class[]{View.OnClickListener.class}, // 2要监听的接口,监听什么接口,就返回什么接口
new InvocationHandler() { // 3监听接口方法里面的回调
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d("hook", "拦截到了 OnClickListener的方法了");
Button button = new Button(MainActivity.this);
button.setText("Rc在努力....");
// 让系统程序片段 --- 正常继续的执行下去
return method.invoke(mOnClickListenerObj, button);
}
});
通过动态代理可以拦截到实现OnClickListener
接口的所有方法,因为OnClickListener
接口只有一个方法onClick
,所以在这里不需要对是哪个方法进行判断,然后修改onClick
回调的参数,最后把method.invoke(mOnClickListenerObj, button);
的结果返回.
最后一步,需要把狸猫换太子的OnClickListener
进行替换.
mOnClickListenerField.set(mListenerInfo, mOnClickListenerProxy); // 替换的 我们自己的动态代理
最后贴上完整代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "" + ((Button) v).getText(), Toast.LENGTH_SHORT).show();
}
});
// 在不修改以上代码的情况下,通过Hook把 ((Button) v).getText() 内容给修改
try {
hook(button); // button就是View对象
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Hook失败" + e.toString(), Toast.LENGTH_SHORT).show();
}
}
private void hook(View view) throws Exception {
Class mViewClass = Class.forName("android.view.View");
Method getListenerInfoMethod = mViewClass.getDeclaredMethod("getListenerInfo");
getListenerInfoMethod.setAccessible(true);
// 执行方法
Object mListenerInfo = getListenerInfoMethod.invoke(view);
// 替 换 public OnClickListener mOnClickListener; 替换我们自己的
Class mListenerInfoClass = Class.forName("android.view.View$ListenerInfo");
Field mOnClickListenerField = mListenerInfoClass.getField("mOnClickListener");
final Object mOnClickListenerObj = mOnClickListenerField.get(mListenerInfo);
Object mOnClickListenerProxy = Proxy.newProxyInstance(MainActivity.class.getClassLoader(), // 1加载器
new Class[]{View.OnClickListener.class}, // 2要监听的接口,监听什么接口,就返回什么接口
new InvocationHandler() { // 3监听接口方法里面的回调
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 加入了自己逻辑
Log.d("hook", "拦截到了 OnClickListener的方法了");
Button button = new Button(MainActivity.this);
button.setText("Rc在努力....");
// 让系统程序片段 --- 正常继续的执行下去
return method.invoke(mOnClickListenerObj, button);
}
});
// 狸猫换太子 把系统的 mOnClickListener 换成 我们自己写的 动态代理
mOnClickListenerField.set(mListenerInfo, mOnClickListenerProxy); // 替换的 我们自己的动态代理
}
}
获取Application
在组件化中,在组件中获取application
还是相对困难的,通过反射就能获取全局Application
对象的方式相比于在Application#OnCreate
保存一份的方式更加通用了。
通过查看ActivityThread
方法,会发现currentApplication
是它的静态方法,这为反射调用获取Application
提供可能
ActivityThread.java
public static Application currentApplication() {
ActivityThread am = currentActivityThread();
return am != null ? am.mInitialApplication : null;
}
object AppGlobals {
private var application: Application? = null
fun get(): Application? {
if (application == null) {
try {
application = Class.forName("android.app.ActivityThread")
.getMethod("currentApplication")
.invoke(null) as Application
} catch (ex: Exception) {
ex.printStackTrace()
}
}
return application
}
}