在App开发过程中,埋点都要花费很多时间,而且还有遗漏没有埋的情况,能不能做到服务器下发哪些控件要埋,然后客户端自动埋。 前几天想出来了一个方案,今天实现了一个Demo,大概过程就是:遍历界面上所有View --> 如果View设置了OnClickListener,并且View的ID在配置文件中,那就替换OnClickListener为DelegentOnClickListener -->DelegentOnClickListener先调用埋点,然后再调用原先View的ClickListener。
1.首先获取到控件的OnClickListener,Android 系统中没有相对应的API,查看View的源码,发现可以通过反射的方式获取到,Android系统将View相关Listener放到了一个内部类ListenerInfo中,从View 的mListenerInfo中可以获取到OnClickListener,大概的代码是这样的:
private View.OnClickListener getOnClickListener(View view) {
boolean hasOnClick = view.hasOnClickListeners();
if (hasOnClick) {
try {
Method method = View.class.getDeclaredMethod("getListenerInfo", null);
if (null != method) {
method.setAccessible(true);
Object object = method.invoke(view, new Object[]{});
if (null != object) {
Class listenerInfoClazz = object.getClass();
Field mOnClickListener = listenerInfoClazz.getDeclaredField("mOnClickListener");
if (null != mOnClickListener) {
mOnClickListener.setAccessible(true);
Object listener = mOnClickListener.get(object);
if (null != listener && listener instanceof View.OnClickListener) {
return (View.OnClickListener) listener;
}
}
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
return null;
}
2.遍历界面中的所有View,然后替换在配置文件中的View的OnClickListener,实现代码:
private void setClickListener(final View view) {
if (null != view) {
boolean hasOnClick = view.hasOnClickListeners();
if (hasOnClick) {
final View.OnClickListener listener = getOnClickListener(view);
int id = view.getId();
String name = getResources().getResourceName(id);
//AdapterView不能设置OnClickListener
if (!(view instanceof AdapterView)) {
//检查是否在配置文件中,请求OnClickListener没替换过
if (!(listener instanceof DelegentOnClickListener) &&
mTraceManager.isNeedTrace(this.getClass().getName(), name)) {
view.setOnClickListener(new DelegentOnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "new clickListener");
if (null != listener) {
listener.onClick(v);
}
Log.e(TAG, v.getTag(v.getId()) + "");
}
});
}
}
}
if (view instanceof ViewGroup) {
final ViewGroup viewGroup = (ViewGroup) view;
int childCount = viewGroup.getChildCount();
if (childCount > 0) {
for (int i = 0; i < childCount; i++) {
View childView = viewGroup.getChildAt(i);
setClickListener(childView);
}
}
}
}
}
3.替换界面中所有的View的OnClickListener,实现代码: setClickListener(findViewById(android.R.id.content));
4.在Assets的配置文件中添加要追踪的Activity和View id。 这个Demo实现了大概的原理验证,距离真实的产品,还有很大的距离,包括替换所有View的OnClickListener的时机怎么选择,ListView,RecycleView的Item监听,还有OnTouchListenr,OnLongClickListener实现,通过设置DelegentTouchListener应该还可以实现界面的热力图,配置文件可以从服务器端下发,做到动态埋点,View可以有意识的setTag(id,object),然后埋点反序列化这个object,可以做到埋入一些参数信息,配置文件还可以扩展一下,有控件名称之类的。 配图很梦幻,如果实现了自动埋点,对运营和开发来说也是梦幻的。
项目地址:https://github.com/fengcunhan/AutoTrace