android 拦截系统View javapoet+自定义Factory2 实现android无痕埋点
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源码