hook + aop 简单实现
1. View的层级
1639734894(1).jpg 1.jpgView 所有的Listener都存储在getListenerInfo()=ListenerInfo
所有hook getListenerInfo()拿到mOnClickListener代理到我们自己的WrapperOnClickListener里
1.hook setOnClickListener
fun hookViewOnClickListener(view: View) {
val listener = getOnClickListener(view)
if(listener !is WrapperViewOnClickListener){
view.setOnClickListener(WrapperViewOnClickListener(listener))
}
}
2.反射拿到getListenerInfo,通过ListenerInfo拿到mOnClickListener
private fun getOnClickListener(view: View): View.OnClickListener? {
val hasOnClick = view.hasOnClickListeners()
if (hasOnClick) {
try {
val viewClazz: Class<*> = View::class.java
val method: Method = viewClazz.getDeclaredMethod("getListenerInfo")
if (!method.isAccessible) {
method.isAccessible = true
}
val listenerInfoObj: Any = method.invoke(view)
val listenerInfoClazz = Class.forName("android.view.View\$ListenerInfo")
val field: Field = listenerInfoClazz.getDeclaredField("mOnClickListener")
if (!field.isAccessible) {
field.isAccessible = true
}
return field.get(listenerInfoObj) as View.OnClickListener
} catch (e: Exception) {
e.printStackTrace()
}
}
return null
}
}
3.WrapperViewOnClickListener
class WrapperViewOnClickListener(val listener: View.OnClickListener?) : View.OnClickListener {
override fun onClick(v: View?) {
Log.e("TAG","自动埋点")
listener?.onClick(v)
val path = ViewPath.getPath(v, StringBuilder())
Log.e("TAG", path)
}
}
获取当前View所在Activity的路径
注意RecyclerView 子item的位置索引
fun getPath(view: View?, path: StringBuilder): String? {
if (view != null) {
if (path.isEmpty()) path.insert(0, view.javaClass.simpleName)
} else {
return ""
}
if (view.parent is ViewGroup) {
if (view.parent is RecyclerView || view.parent is AdapterView<*>) {
val listView: View = view.parent as View
var index = 0
if (view.parent is RecyclerView) {
index = (view.parent as RecyclerView).getChildAdapterPosition(view)
} else if (view.parent is AdapterView<*>) {
index = (view.parent as AdapterView<*>).getPositionForView(view)
}
path.insert(0, view.parent.javaClass.simpleName + "[" + index + "]|")
} else {
path.insert(0, view.parent.javaClass.simpleName + "[" + (view.parent as ViewGroup).indexOfChild(view) + "]|")
}
}
//遇到PhoneWindow退出递归
return if (view.parent is View) {
//不统计统计DecorView遇到Decorview退出递归
if ((view.parent as View).id === android.R.id.content) {
path.insert(0, view.parent.javaClass.simpleName + "|")
path.toString()
} else {
getPath(view.parent as View, path)
}
} else {
path.toString()
}
}
效果:
TAG: 自动埋点
TAG: DecorView[0]|LinearLayout[1]|FrameLayout[0]|FitWindowsLinearLayout[1]|ContentFrameLayout[0]|LinearLayout[4]|FrameLayout[0]|FrameLayout[0]|FrameLayout[0]|FrameLayout[0]|FrameLayout[0]|FrameLayout[0]|FrameLayout[0]|AppCompatButton
or
ContentFrameLayout|ContentFrameLayout[0]|LinearLayout[4]|AppCompatButton
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/get"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="埋点" />
</FrameLayout>
</FrameLayout>
</FrameLayout>
</FrameLayout>
</FrameLayout>
</FrameLayout>
</FrameLayout>
</LinearLayout>
网友评论