master
Omooo 6 years ago
parent 7428e29dfc
commit cc01b41a9b
  1. 66
      blogs/AOP_AspectJ.md
  2. 14
      blogs/埋点.md
  3. 166
      blogs/埋点之代理 View.OnClickListener.md
  4. BIN
      images/AOP_AspectJ.png

@ -13,13 +13,17 @@ AOP 之 AspectJ
- 引入
- 注解定义
- 切点表达式
- ProceedingJoinPoint、JoinPoint
- 示例
- 全局点击事件拦截
- 方法统计耗时
- 权限申请
4. 参考
#### 思维导图
![](https://i.loli.net/2018/12/17/5c17b43b9662f.png)
#### AOP
##### 概念
@ -114,10 +118,36 @@ AOP 和 OOP 都只是一种编程思想,实现 AOP 的方式有以下几种:
```java
execution(<@注解类型模式>? <修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
within(<@注解类型模式>? <修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
```
其中注解类型模式、修饰符模式、异常模式都是可选的。
其中 execution() 是方法切点函数,表示目标类中满足某个匹配模式的方法连接点。而 winth() 是目标类切点函数,表示满足某个匹配模式的特定域中的类的所有连接点,比如某个包下所有的类。
##### ProceedingJoinPoint、JoinPoint
ProceedingJoinPoint 用于环绕通知,它是 JointPoint 的子类,与其不同的是 ProceedingJoinPoint 提供了 proceed() 方法用于执行切点方法。
```java
@Around("execution( * cn.* (..))")
public void test(ProceedingJoinPoint joinPoint) throws Throwable {
//在方法执行之前执行
int i = 0;
joinPoint.proceed();
//在方法执行之后执行
i++;
//获取签名信息
MethodSignature signature= (MethodSignature) joinPoint.getSignature();
signature.getName(); //方法名
signature.getDeclaringTypeName(); //类名
signature.getReturnType(); //返回类型
signature.getParameterNames(); //方法参数名
signature.getParameterTypes(); //方法参数类型
}
```
##### 示例
全局事件拦截:
@ -136,6 +166,42 @@ execution(<@注解类型模式>? <修饰符模式>? <返回类型模式> <方法
但是我们发现其实切点表达式是一样的,那就就可以抽成一个 Pointcut 来复用,即:
```java
@Before(value = "onViewClick()")
public void onViewClickBefore(JoinPoint joinPoint) {
Log.i(TAG, "onViewClickBefore: 点击事件之前执行");
}
@After(value = "onViewClick()")
public void onViewClickAfter(JoinPoint joinPoint) {
Log.i(TAG, "onViewClickAfter: 点击事件之后执行");
}
@Pointcut("execution(* android.view.View.OnClickListener.onClick(android.view.View))")
public void onViewClick(){
}
```
方法统计耗时:
```java
@Around("execution( * com.example.omooo.demoproject.MainActivity.* (..))")
public void bootTracker(ProceedingJoinPoint joinPoint) throws Throwable {
long beginTime = SystemClock.currentThreadTimeMillis();
joinPoint.proceed();
long endTime = SystemClock.currentThreadTimeMillis();
long dx = endTime - beginTime;
Log.i(TAG, joinPoint.getSignature().getDeclaringType().getName() + "#" + joinPoint.getSignature().getName()
+ " " + dx + " ms");
}
// com.example.omooo.demoproject.MainActivity#playAnimation 2 ms
```
权限申请:
以上两个示例都是按照相似性统一切,而通过自定义注解修饰切人点,能够达到精确切。
#### 参考

@ -106,37 +106,30 @@ public class MyApplication extends Application {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
Log.i("2333","onActivityCreated");
}
@Override
public void onActivityStarted(Activity activity) {
Log.i("2333","onActivityStarted");
}
@Override
public void onActivityResumed(Activity activity) {
Log.i("2333","onActivityResumed");
}
@Override
public void onActivityPaused(Activity activity) {
Log.i("2333","onActivityPaused");
}
@Override
public void onActivityStopped(Activity activity) {
Log.i("2333","onActivityStopped");
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
Log.i("2333","onActivitySaveInstanceState");
}
@Override
public void onActivityDestroyed(Activity activity) {
Log.i("2333","onActivityDestroyed");
}
}
}
@ -159,8 +152,13 @@ public class MyApplication extends Application {
#### $AppClick 全埋点实现
[AspectJ 实现方式](https://github.com/Omooo/Android-Notes/blob/master/blogs/AOP_AspectJ.md)
[View]
#### 参考
[https://github.com/wenmingvs/AndroidProcess](https://github.com/wenmingvs/AndroidProcess)
《神策数据 Android 全埋点技术白皮书》
[https://github.com/wenmingvs/AndroidProcess](https://github.com/wenmingvs/AndroidProcess)

@ -0,0 +1,166 @@
---
埋点之代理 View.OnClickListener
---
#### 目录
1. 思维导图
2. 原理
3. 实现
4. 优缺点
5. 参考
#### 思维导图
#### 原理
在注册的 Application.ActivityLifecycleCallbacks 的 onActivityResumed(Activity activity) 回调方法中,通过 activity.getWindow().getDecorView 获取 DecorView,然后遍历 DecorView,判断当前 View 是否设置了 onClickListener,如果已设置并且不是我们的 OnClickListenerWrapper,就用自定义的 OnClickListenerWrapper 替代当前 View.onClickListener。WrapperOnClickListener 的 onClick 里会先调用原有的 OnClickListener 处理逻辑,然后再调用埋点代码,从而达到自动埋点的效果。
之所以选择 DecorView 而不是 ContentView(Activity#setContentView),主要是考虑到了 MenuItem 的点击事件。
但是,这样并不能拦截到动态添加的 View,所以还需要引入 ViewTreeObserver.OnGlobalLayoutListener 来监听视图树变化,而且要在 Activity#onStop 中 removeOnGlobalLayoutListener。
#### 实现
1. 首先创建代理类
```java
public class ClickListenerWrapper implements View.OnClickListener {
private static final String TAG = "HookedClickListenerProx";
private View.OnClickListener origin;
public ClickListenerWrapper(View.OnClickListener origin) {
this.origin = origin;
}
@Override
public void onClick(View v) {
if (origin != null) {
Log.i(TAG, "onClick: 前");
origin.onClick(v);
Log.i(TAG, "onClick: 后");
}
}
}
```
2. 代理原 OnClickListener 事件
```java
public class HookOnClickHelper {
private static final String TAG = "HookOnClickHelper";
public static void hook(View view) throws Exception {
//第一步:反射得到 ListenerInfo 对象
Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");
getListenerInfo.setAccessible(true);
Object listenerInfo = getListenerInfo.invoke(view);
//第二步:得到原始的 OnClickListener 事件方法
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
mOnClickListener.setAccessible(true);
View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);
if (originOnClickListener == null || originOnClickListener instanceof ClickListenerWrapper) {
//如果没有设置点击事件或者已经代理过了,则跳过
return;
}
//第三步:替换
ClickListenerWrapper proxy = new ClickListenerWrapper(originOnClickListener);
mOnClickListener.set(listenerInfo, proxy);
}
}
```
3. 在 Activity 完全显示的时候遍历 View 动态代理 OnClickListener
```java
public class MyApplication extends Application {
private static final String TAG = "MyApplication";
@Override
public void onCreate() {
super.onCreate();
this.registerActivityLifecycleCallbacks(new MyActivityLifecycleCallback());
}
static class MyActivityLifecycleCallback implements ActivityLifecycleCallbacks {
private View mDecorView;
private ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener=new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
setAllViewsProxy((ViewGroup) mDecorView);
}
};
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
mDecorView = activity.getWindow().getDecorView();
setAllViewsProxy((ViewGroup) mDecorView);
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
mDecorView.getViewTreeObserver().removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
private void setAllViewsProxy(ViewGroup viewGroup) {
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = viewGroup.getChildAt(i);
if (view instanceof ViewGroup) {
setAllViewsProxy((ViewGroup) view);
} else {
try {
if (view.hasOnClickListeners()) {
HookOnClickHelper.hook(view);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
```
#### 优缺点
优点:无
缺点:
1. 使用反射,对 APP 整体性能有影响,也可能会引入兼容性问题
2. 无法采集游离于 Activity 之上的 View 的点击,比如 Dialog、PopupWindow等等
3. OnGlobalLayoutListener API 16+
#### 参考
《神策数据 Android 全埋点技术白皮书》
[Android Hook 机制之简单实战](https://www.jianshu.com/p/c431ad21f071)

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Loading…
Cancel
Save