diff --git a/blogs/AOP_AspectJ.md b/blogs/AOP_AspectJ.md index 6d00c15..08a0910 100644 --- a/blogs/AOP_AspectJ.md +++ b/blogs/AOP_AspectJ.md @@ -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 +``` + +权限申请: + +以上两个示例都是按照相似性统一切,而通过自定义注解修饰切人点,能够达到精确切。 + #### 参考 diff --git a/blogs/埋点.md b/blogs/埋点.md index c3c1362..d955eb9 100644 --- a/blogs/埋点.md +++ b/blogs/埋点.md @@ -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) \ No newline at end of file +《神策数据 Android 全埋点技术白皮书》 + +[https://github.com/wenmingvs/AndroidProcess](https://github.com/wenmingvs/AndroidProcess) + diff --git a/blogs/埋点之代理 View.OnClickListener.md b/blogs/埋点之代理 View.OnClickListener.md new file mode 100644 index 0000000..8ae3b24 --- /dev/null +++ b/blogs/埋点之代理 View.OnClickListener.md @@ -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) \ No newline at end of file diff --git a/images/AOP_AspectJ.png b/images/AOP_AspectJ.png new file mode 100644 index 0000000..904fb66 Binary files /dev/null and b/images/AOP_AspectJ.png differ