android 拦截系统View javapoet+自定义Factory2 实现android无痕埋点

github源码传送门

1.前序

  主要是介绍怎么拦截所有点击事件,具体数据打点之类的可以参考上一篇      

上篇是通过aop 来实现拦截所有点击 事件,如果开发的时候我们到处乱写setOnClickListener,用切面可能就要扫描所有类,可能比较影响编译效率

定义一个简单的activity,包含button

我们通过studio->Tools-Layout Inspector 可以看到 , button 被替换成了AppCompatButton

引用了V7 或者androidX  在setContentView的时候会自动帮我们的View替换成系统V7 或者androidX的View

通过 AppCompatViewInflater createView 可以看到 系统view 都被替换成了AppCompatXX 

     switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                verifyNotNull(view, name);
                break;

 

2.拦截View

 在Activity onCreate  的时候可以通过 LayoutInflaterCompat.setFactory2 自定义factory,下面就是自定义View的最简单实现,

我们可以自定义View来定义所有字体,或者背景之类的, 我们每拦截一个view,就得重写一个view, 虽然说就这么几个TextView,Button,

 LayoutInflaterCompat.setFactory2(
            LayoutInflater.from(this),
            object : LayoutInflater.Factory2 {
                override fun onCreateView(name: String?, context: Context?, attrs: AttributeSet?): View? {
                    return null
                }
                override fun onCreateView(parent: View?, name: String?, context: Context?, attrs: AttributeSet?): View? {
                   if("Button".equals(name)){
                        return MyButton(context!!,attrs)
                    }
                    val delegate = delegate
                    if(parent!=null){
                        return delegate.createView(parent, name, context!!, attrs!!)
                    }
                    else{
                        return null;
                    }
                }
            }
        )

3.Javapoet

javapoet是一个可以动态生成java代码的开源框架 传送

这里我们用javapoet给我们动态生成所有需要自定义的view,当然,写起来肯定比复制黏贴,重写复杂多了,这里主要是为了展示优雅的切面

javapoet可以用在安卓中自定义路由(类似Arount),自定义注解(类似Dagger,ButterKnift)

我们首先需要 自定义一个注解

@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoView {
    Class[] values();
    Class[] types();
}

这里有两个方法 values  就是所有需要重写的view,  types用于传一些方法, javapoet在安卓里本身是一个javalibrary 这里为了方便直接把一些class传进去

@AutoView(values ={Button.class, TextView.class, LinearLayout.class},
types = {Context.class, AttributeSet.class})
public class SelftView {

}

注解解析需要继承 AbstractProcessor

这里主要是完成两部, 拿到所有@AutoView   注解过的类,

自动生成 我们需要的view, 下面写的有点烦,主要直接通过 element.getAnnotation(BindView.class).value();

取会报javax.lang.model.type.MirroredTypeException

这里没啥逻辑,就是拿到注解类的包名,类名

        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(AutoView.class);
        List<MyViewInfo> list =new ArrayList<MyViewInfo>();
        MyViewParam myViewParam =new MyViewParam();
        MyViewInfo info;
        for (Element element : elements) {
            Set<ExecutableElement> keys = (Set<ExecutableElement>)element.getAnnotationMirrors().get(0).getElementValues().keySet();
            for(ExecutableElement e: keys){
                AnnotationValue value = element.getAnnotationMirrors().get(0).getElementValues().get(e);
                if(e.getSimpleName().toString().equals("values")){
                    String[] values = value.getValue().toString().split(",");

                    for(int i=0;i<values.length;i++){
                        String mValue = values[i].substring(0,values[i].lastIndexOf("."));
                        info =new MyViewInfo();
                        info.name = mValue.substring(mValue.lastIndexOf(".")+1);
                        info.superClass =mValue;
                        list.add(info);
                    }
                }
                else if(e.getSimpleName().toString().equals("types")){
                    String[] values = value.getValue().toString().split(",");

                    for(int i=0;i<values.length;i++){
                        String mValue = values[i].substring(0,values[i].lastIndexOf("."));
                        if(i==0){
                            myViewParam.contextClass = mValue;
                        }
                        else if(i==1){
                            myViewParam.attributeClass = mValue;
                        }
                    }
                }

            }
            System.out.println("name22="+list);
        }

 

下面介绍几个主要方法

            TypeName  contextType =ClassName.bestGuess(myViewParam.contextClass);

通过完整类名获取类对应的TypeName, javapoet里面类型可以用TypeName包装

                MethodSpec.Builder constructor1 = MethodSpec.constructorBuilder()
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(ParameterSpec.builder(contextType, "context").build());
                constructor1.addStatement("super($L)","context");

定义一个构造函数, 并且传入一个参数context 可以对照源码

  public MyButton(Context context)
  {
    super(context);
  }

重写View的onClickListener, 并且包装一个自定义的OnClickListener ,

       ClassName clickClass = ClassName.get("com.lin.annotationclick", "MyClickListener");
                TypeName  clickType =ClassName.bestGuess("android.view.View.OnClickListener");
                MethodSpec.Builder onClickListener= MethodSpec.methodBuilder("setOnClickListener")
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(ParameterSpec.builder(clickType, "l").build());
                onClickListener.addStatement("super.setOnClickListener(new $T(l))",clickClass);

这样我们就可以在自定义的OnClickListener里面进行通用事件处理

 public void setOnClickListener(View.OnClickListener l)
  {
    super.setOnClickListener(new MyClickListener(l));
  }

具体可以参考上面git源码

 

posted @ 2019-06-10 19:33  dikeboyR  阅读(754)  评论(0编辑  收藏  举报