android 使用apt(编译时注解) 自动生成第三方的狗皮膏药代码

来源:互联网 发布:匡恩网络女副总裁 编辑:程序博客网 时间:2024/06/06 05:40
    在日常的Android项目开发中,免不了集成大量第三方库,由于各个公司开发风格不一,导致在项目集成过程中东粘一块西粘一块,对于有代码洁癖的人来说无疑是场灾难,面对第三方库如此强大的代码侵入性,我们无所适从,只能尽量整合,用良好的编码结构来规避混乱,不过java也为我们提供了一套在编译期自动生成代码的利器,让我们不再面对那些某些狗皮膏药似的代码,以微信登录为例(抛砖引玉),做微信登录的朋友都知道集成它要强制编写一个WXEntryActivity类,包名还有一定规范,必须是apk包名下的.wxapi下,实在不能接受这种写法的朋友可以继续往下看,当然看完这篇文章,那么你对ButterKnife(通过自动生成代码),XUtils(通过反射实现比ButterKnife逊色)等诸多注解框架的核心原理也就知道了。
       首先为什么我们要自动生成微信Activity,直接写上去不是更方便吗?答案是为了抽取到基准库,我们作为开发人员都有自己的库文件,当需要开发项目时直接把库集成进来就可以了,而第三方微信的集成如登录,支付在正常情况下需要已知包名的情况下去书写类文件,而底层库不依赖于具体项目而存在,抽取有诸多不便。大致思路如下:首先我们在底层库中创建一个activity,在这个activity中完成微信登录后的所有操作逻辑并发出回调,对于上层用户(项目开发者)来说,他只关心微信的最终openId是否能得到,至于中间的过程就交给底层框架来处理,框架负责发出登录请求,接收微信返回并回馈给上层项目,最大限度实现解耦,现在切入正题。


 
 第一步:创建整个项目架构,如图:

       


 我们把整个项目拆分成四个部分。
  1. android application模块—相当于主项目。
  2. 注解模块— java库模块(定义注解的模块,因为annotation属于java范畴,所以只要是java工程即可)。
  3. 注解编译模块—(同样是java模块,因为需要使用java及第三方的注解处理api)。
  4. 核心库模块— (android库,因为核心库涉及到UI,网络以及抽取的基类如BaseActivity等,所以必须是android库)。

对于模块的拆分因人而异,不过在实际项目中模块的拆分我觉得是很有必要的,他可以灵活搭配,适度取舍。甚至以上这种拆分我都闲太集中,我们完全可以把apt-lib拆分为net library,ui library, utils library,还有各种thirdpart library,甚至可以更细,谁都不知道实际项目需要用到什么,当把没必要的库引入到了项目中,不但增加了包体大小,同时也影响了审美,对代码洁癖者来说是无法忍受的,不过在这里我就只用四个模块吧。

第二:梳理依赖关系
  • apt-android 依赖 apt-lib
  • apt-lib 依赖 apt-annotations 
  • apt-android 编译期依赖apt-compiler  (就是在编译的时候使用apt-compiler来产生java代码在apt-android下的gradle配置中只要加上annotationProcessor project(':apt-compile')
  • apt-compiler 依赖apt-annotations 同时也依赖部分第三方jar 
    compile 'com.squareup:javapoet:1.9.0’    (生成java类的关键库,提供了很多非常简洁的api)
    compile 'com.google.auto.service:auto-service:1.0-rc3
    compile 'com.google.auto:auto-common:0.8’ 
    compile project(':apt-annotations)


第三:编写java代码
在apt-annotations中定义一个WXActivityGenerator 微信生成器注解。
package com.late.apt.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by xuhongliang on 2017/11/15.
*/
//声明注解作用范围是作用在类,接口,注解,枚举上
@Target(ElementType.TYPE)
//声明注解的有效期为源码期
@Retention(RetentionPolicy.SOURCE)
public @interface WXActvityGenerator {
    //声明该注解所要生成的包名规则
    String getPackageName();
    //声明该注解所生成java类需要继承哪个父类
    Class<?> getSupperClass();
}
在apt-lib库中定义模板类,继承自AppCompatActivity
package com.late.apt.lib;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
/**
* Created by xuhongliang on 2017/11/15.
*/
public class WXTemplateActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //执行登录回调逻辑,包括请求token,获取openid,回给真实项目模块
    }
}
在apt-android中随便哪个类上使用注解@WXActivityGenerator,这里在MainActivity上使用
package com.xhl.apt;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.late.apt.annotations.WXActvityGenerator;
import com.late.apt.lib.WXTemplateActivity;
@WXActvityGenerator(
        getPackageName = "com.xhl.apt",
        getSupperClass = WXTemplateActivity.class
)
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
以上三步是定义并使用了注解,具体的注解处理生成代码还未写,我们在apt-compiler中写相关注解处理代码.
定义一个WXActivityAnnotationProcessor
package com.late.apt.compile;
import com.google.auto.service.AutoService;
import com.late.apt.annotations.WXActvityGenerator;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor7;
/**
* Created by xuhongliang on 2017/11/15.
*/
@AutoService(Processor.class)
public class WXActivityAnnotationProcessorextends AbstractProcessor {
    //可以理解为编译期的文档管理员
    private Filerfiler;
    @Override
    public synchronized voidinit(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        this.filer = processingEnvironment.getFiler();
    }
    //这个函数式注解处理的核心函数,env对象是编译器环境,可获取整个包中的所有元素属性
    @Override
    public booleanprocess(Set<?extends TypeElement> set, RoundEnvironment env) {
        generatorJavaCode(filer, env);
        return true;
    }
    private void generatorJavaCode(Filer filer, RoundEnvironment env) {
        //首先创建一个注解属性解析器
        final WXActvityAnnotationVisitor visitor =new WXActvityAnnotationVisitor(filer);
        //获取标注了WXActvityGenerator注解的所有元素集合
        final Set<?extends Element> elementsWithWXAnnotation = env.getElementsAnnotatedWith(WXActvityGenerator.class);
        //遍历
        for (Element element : elementsWithWXAnnotation) {
            //取得带有属性的注解元素
            final List<?extends AnnotationMirror> annotationMirrors = element.getAnnotationMirrors();
            //遍历
            for (AnnotationMirror annotationMirror : annotationMirrors) {
                //获取注解属性值
                final Map<? extends ExecutableElement, ?extends AnnotationValue> map = annotationMirror.getElementValues();
                for (AnnotationValue annotationValue : map.values()) {
                    //把属性值丢给注解解析器处理
                    annotationValue.accept(visitor, null);
                }
            }
        }
    }
    @Override
    public Set<String>getSupportedAnnotationTypes() {
        //因为就一个注解,所以创建只存在一个注解的集合
        final Set<String> annotationNames = Collections.singleton(WXActvityGenerator.class.getCanonicalName());
        return annotationNames;
    }
    final static class WXActvityAnnotationVisitorextends SimpleAnnotationValueVisitor7<Void, Void> {
        private final Filer filer;
        private String packageName;
        public WXActvityAnnotationVisitor(Filer filer) {
            this.filer = filer;
        }
        @Override
        public VoidvisitString(String s, Void aVoid) {
            //解析得到包名
            this.packageName = s;
            return aVoid;
        }
        @Override
        public VoidvisitType(TypeMirror typeMirror, Void aVoid) {
            //生成java类也就是WXEntryActivity
            generatorJavaCode(typeMirror);
            return aVoid;
        }
        private void generatorJavaCode(TypeMirror typeMirror) {
            try {
                //创建类的描述
                final TypeSpec classType = TypeSpec.classBuilder("WXEntryActivity")
                        .addModifiers(Modifier.FINAL, Modifier.PUBLIC)
                        .superclass(TypeName.get(typeMirror)).build();
                //创建java类文件
                final JavaFile javaFile = JavaFile.builder(packageName +".wxapi", classType)
                        .addFileComment("WXActivity")
                        .build();
                //写入编译文档文件
                javaFile.writeTo(filer);

            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}
因为注释已经很详细了,在这边也不解释具体怎么写了,主要是定义一个注解处理器,这个处理器继承自AbstractProcessor,
这个类是java包中的专门处理注解的抽象类,我们只要实现process方法即可拿到注解上的元素信息,接着丢给一个注解元素访问者即AnnotationVisitor,我们通过他的visit+类型这类方法就可以拿到注解中定义的元素值,也就是packageName与需要继承的类。

最后我们编译工程就会发现我们的WXEntryActivity神不知鬼不觉的出现在了项目中,而且不用被你看见就可以使用他,毕竟眼不见为净。


而相关的处理逻辑我们已经在模板类中执行,这些逻辑是通用的,不管在哪个项目里都一样。等有新项目我们只要引入包,配置下依赖,配置下清单文件,再写个注解即可,根本不用重复在写登录分享逻辑了。

在这里只是以生成微信特有类为例来使用了一下apt,其实在我们的项目中apt还可以干更多的事,比如根据json文件自动生成java类,都是可以在编译期来实现的,再也不用你手写javaBean了,当然网上有这种工具,那如果我们自己实现岂不是很牛逼?

ButterKnife作为非常知名的注解框架也是使用了这一原理,无非就多了些处理逻辑,本质是一样的,我们来简单看下源码。

这就相当于我们的apt-annotations库,只是butterknife定义的注解多了点而已,注解本质就是为了识别属性与提供属性值而存在的。

这个类就是执行绑定的整个逻辑的库,本质最终处理还是会走findViewById(R.id.btn);
public final class ButterKnife {
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
  View sourceView = target.getWindow().getDecorView();
  return createBinding(target, sourceView);
}
private static Unbinder createBinding(@NonNull Object target,@NonNull View source) {
  Class<?> targetClass = target.getClass();
  if (debug) Log.d(TAG,"Looking up binding for " + targetClass.getName());
  Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

  if (constructor == null) {
    return Unbinder.EMPTY;
  }

  //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
  try {
    return constructor.newInstance(target, source);
  } catch (IllegalAccessException e) {
    throw new RuntimeException("Unable to invoke " + constructor, e);
  } catch (InstantiationException e) {
    throw new RuntimeException("Unable to invoke " + constructor, e);
  } catch (InvocationTargetException e) {
    Throwable cause = e.getCause();
    if (cause instanceof RuntimeException) {
      throw (RuntimeException) cause;
    }
    if (cause instanceof Error) {
      throw (Error) cause;
    }
    throw new RuntimeException("Unable to create binding instance.", cause);
  }
}
}
这句是核心
 Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
我们拿到了compiler生成的java类的构造器生成对象,在生成对象的时候已经将具体view赋值了,以下是具体的生成类。
package com.diabin.fastec.example;
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;
public class ExampleDelegate_ViewBinding implements Unbinder {
  private ExampleDelegate target;
  private View view2131624084;
  @UiThread
  publicExampleDelegate_ViewBinding(final ExampleDelegate target, View source) {
    this.target = target;
    View view;
    view = Utils.findRequiredView(source,R.id.btn_test,"method 'onClickTest'");
    view2131624084 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public voiddoClick(View p0) {
        target.onClickTest();
      }
    });
  }
  @Override
  @CallSuper
  public voidunbind() {
    if (target ==null)throw new IllegalStateException("Bindings already cleared.");
    target =null;
    view2131624084.setOnClickListener(null);
    view2131624084 =null;
  }
}
在来看butterknife的compiler包

ButterKnifeProcessor为核心类.
process方法如初一折,目的就是生成像 ExampleDelegate_ViewBinding这样的类(对于Activity来说是中间赋值类)。
@Override public booleanprocess(Set<?extends TypeElement> elements, RoundEnvironment env) {
  Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
  for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
    TypeElement typeElement = entry.getKey();
    BindingSet binding = entry.getValue();
    JavaFile javaFile = binding.brewJava(sdk);
    try {
      javaFile.writeTo(filer);
    } catch (IOException e) {
      error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
    }
  }
  return false;
}

附github地址: https://github.com/lategege/apt-android/tree/master

在本文结尾向ButterKnife的作者致敬,让程序员摆脱了很多重复的没意义的代码,实属丰功伟绩。
      

阅读全文
1 0
原创粉丝点击