Android实现无痕埋点具体实现-采用代码插桩的方式

本文介绍了如何通过Android的View.AccessibilityDelegate和gradle插件字节码插装技术实现无痕埋点。详细讨论了采用View.AccessibilityDelegate方式的原因,gradle插件字节码插装的流程,包括apk编译过程、插件模块编写、字节码修改,以及事件处理模块的编写。这种方法减少了手动埋点的工作,但也有局限性,如无法为Dialog和PopWindow埋点,且可能影响性能。
摘要由CSDN通过智能技术生成

目录

1.View.AccessibilityDelegate

1.1先看下为什么采用View.AccessibilityDelegate方式,通过View源码如何执行点击事件:

1.2完成替换View中AccessibilityDelegate类,实现sendAccessibilityEvent(this, eventType)方法

2.gradle插件字节码插装

2.1Android下apk编译流程

2.2编写gradle插件模块(groovy文件实现)

2.2.1工程下新建buildSrc模块(系统保留名称)

2.2.3编写Transform

2.2.4字节码修改

2.3编写事件处理模块(Java)

2.3.1Activity及Fragment相关hook接受,点击事件接受

3.保证View的唯一路径

4.总结


手动无埋点的方式,效率低,成本高,见效慢,故开发一套sdk自动采集pv,click等事件;

实现无埋点主流方案有几下几种:

1.View.AccessibilityDelegate

1.1先看下为什么采用View.AccessibilityDelegate方式,通过View源码如何执行点击事件:

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

第一步:调用View设置的点击事件实例onClick()回调方法,View接受到点击事件回调执行登录,注册,播放视频等操作;

ListenerInfo.mOnClickListener.onClick(this);

第二步:发送给定类型的可访问性事件,AccessibilityEvent定义了可访问的事件类型例如:点击事件-TYPE_VIEW_CLICKED,长按事件-TYPE_VIEW_LONG_CLICKED等;

sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

public void sendAccessibilityEvent(int eventType) {
        if (mAccessibilityDelegate != null) {
            mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);
        } else {
            sendAccessibilityEventInternal(eventType);
        }
    }

通过如上代码我们发现最后调用AccessibilityDelegate实例对象方法sendAccessibilityEvent(this, eventType),那么是否可以重写AccessibilityDelegate类和sendAccessibilityEvent(this, eventType)来完成埋点操作呢?

 public void setAccessibilityDelegate(@Nullable AccessibilityDelegate delegate) {
        mAccessibilityDelegate = delegate;
    }

我们发现可以自己手动设置AccessibilityDelegate实例替换View中现有AccessibilityDelegate实例对象,在自定义AccessibilityDelegate类sendAccessibilityEvent(this, eventType)完成埋点操作;

1.2完成替换View中AccessibilityDelegate类,实现sendAccessibilityEvent(this, eventType)方法

在Application提供了监听Activity生命周期的方法registerActivityLifecycleCallbacks(),在生命周期回调onActivityResumed()方法中为RootView下的所有视图设置自己实现AccessibilityDelegate类进行埋点;

public void initActivityLifeCycle(){
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityResumed(Activity activity) {
                sendLog(activity, "onActivityResumed");
                ViewGroup viewGroup = (ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content);
            mOnGlobalLayoutListener = new MOnGlobalLayoutListener(viewGroup); 
               viewGroup.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);             

            }


            @Override
            public void onActivityPaused(Activity activity) {
                sendLog(activity, "onActivityPaused");
                ViewGroup viewGroup = (ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content);
                viewGroup.getViewTreeObserver().removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
                mOnGlobalLayoutListener = null;
            }

        });
    }



//遍历设置AccessibilityDelegate实现类
    public void setAccessibilityDelegate(ViewGroup viewGroup){
        int childCount = viewGroup.getChildCount();
        for(int i=0; i<childCount; i++){
            if(viewGroup.getChildAt(i) instanceof ViewGroup){
                setAccessibilityDelegate((ViewGroup) viewGroup.getChildAt(i));
            }else{
                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
                if(viewGroup.getChildAt(i).hasOnClickListeners()){
                    BuryingPointAccessibilityDelegate accessibilityDelegate = new BuryingPointAccessibilityDelegate();
                    viewGroup.getChildAt(i).setAccessibilityDelegate(accessibilityDelegate);
                }
              }
            }
        }
    }

    private class MOnGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener{
        private ViewGroup viewGroup;

        public MOnGlobalLayoutListener(ViewGroup viewGroup){
            this.viewGroup = viewGroup;
        }

        @Override
        public void onGlobalLayout() {
            setAccessibilityDelegate(viewGroup);
        }
    }

    private MOnGlobalLayoutListener mOnGlobalLayoutListener;

埋点实现:

/**
 * AccessibilityDelegate实现类,完成埋点操作
 */

public class BuryingPointAccessibilityDelegate extends View.AccessibilityDelegate {
    @Override
    public void sendAccessibilityEvent(View host, int eventType) {
        super.sendAccessibilityEvent(host, eventType);
        //埋点
        sendLog(host, eventType);
    }

    private void sendLog(View host, int eventType) {
        if(eventType == AccessibilityEvent.TYPE_VIEW_CLICKED){
            Log.d(ViewPathUtil.getViewPath(host), "AccessibilityEvent.TYPE_VIEW_CLICKED");
        }
        //...
    }
}

MainActivity[OneFragment]:LinearLayout/FrameLayout[0]/LinearLayout[0]/AppCompatTextView[0] 

AccessibilityEvent.TYPE_VIEW_CLICKED

注意事项:

a.跟视图是指的android.R.id.content,activity.getWindow().getDecorView().findViewById(android.R.id.content)࿱

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值