作一个简单的 APT 小项目——AppShortcut

最近学习了编译时注解框架的制做,写了一个小项目。阅读本文前但愿你们有关于注解的相关知识。java

本文介绍一个简单的编译时注解小项目的制做过程。项目地址:github.com/wuapnjie/Ea…,我选择了Android API 25的新功能App Shortcut,使用注解来快速制做一个Shortcut。为何选择Shortcut呢,由于我以为不少应用只须要使用到静态加载的Shortcut就行了,而对于静态加载的Shortcut要写一个比较长的Xml配置文件,我以为特别麻烦。git

先来看一下咱们实现的效果。github

Java代码api

@ShortcutApplication
@AppShortcut(resId = R.mipmap.ic_launcher,
        description = "First Shortcut")
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
          //API 调用
        ShortcutCreator.create(this);
    }
}复制代码

效果图:app

APT简单介绍

APT,全称Annotation Processing Tool,它用来在编译时处理源代码中的注解信息,咱们能够根据注解来生成一些Java文件,防止编写冗余的代码,好比ButterKnife项目,正是利用了APT工具,帮助咱们少写了许多重复冗余的代码。本文中,经过注解来少写一些配置文件。框架

项目结构

本项目共分为4个Module,两个Java Library module,一个Android Library module和用于演示的Android Application moduleide

  • easyshortcuts-api:Android Library module,用于供客户端的调用。
  • easyshortcuts-annotation:Java Library module,用于提供注解类。
  • easyshortcuts-compiler:Java Library module,用于编写处理注解并生成相关Processor的注解处理模块
  • 还有一个普通的应用模块

其中easyshortcuts-apieasyshortcuts-compiler模块依赖easyshortcuts-annotation模块。工具

注解模块的编写

搭好项目后,第一个动手编写的应该是easyshortcuts-annotation模块,经过查看Android Developer官网上的Shortcut介绍后,发现经过Java代码,咱们只能够经过ShortcutManager生成动态Shortcut,生成一个动态Shortcut的代码简单重复,每一个Shortcut须要一个String类型的Id,图标的ResId,显示的文字,以及一个Intent的Action字段。学习

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface AppShortcut {
    int resId();

    int rank() default 1;

    String description();

    String action() default Define.SHORTCUT_ACTION;
}复制代码

以后我用注解所在类的类名称做为Shortcut的Id。ui

这里,我还建了一个注解ShortcutApplication,是一个没有任何字段的注解,这个注解应该用在用户第一个打开的Activity。由于这里使用了动态加载的方式建立Shortcut,因此必需要执行代码才能够生成Shortcut,因此应该在Launcher Activity使用。

注解处理器的编写

肯定了注解后,咱们要编写相应的注解处理器来处理注解并生成相应的Java文件,这里easyshortcuts-compiler依赖了google的auto-service库和square的javapoet库。

compile "com.squareup:javapoet:$rootProject.ext.squareJavaPoetVersion"
compile "com.google.auto.service:auto-service:$rootProject.ext.googleAutoServiceVersion"复制代码

其中auto-service库能够很方便的帮助咱们生成配置文件,javapoet库能够很方便的帮助咱们自动生成Java代码。

新建一个继承自AbstractProcessorShortcutProcessor,下面是一个基本的Processor应有的要素,咱们的重点在与process()方法。

//帮助咱们生成配置文件的注解
@AutoService(Processor.class)
public class ShortcutsProcessor extends AbstractProcessor {
    private Filer mFiler;
    private Elements mElementUtils;
    private Messager mMessager;
    ……

    //在初始化时得到相关帮助对象
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnvironment.getFiler();
        mElementUtils = processingEnvironment.getElementUtils();
        mMessager = processingEnvironment.getMessager();
    }

      //根据相应的注解进行处理
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
           ……
        return true;
    }

      //返回要支持的注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(ShortcutApplication.class.getCanonicalName());
        types.add(AppShortcut.class.getCanonicalName());
        return types;
    }

      //返回Java语言的支持版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    //辅助的日志打印方法
    private void printNote(String message) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, message);
    }

    private void printError(String error) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, error);
    }
}复制代码

process()方法中,要获取全部有相关注解的Element,并获取每一个注解中附带的字段,最后根据这些信息生成一个Java文件。为了更好的获取储存这些字段,我创建了一个model类Shortcut

public class Shortcut {
    private int mResId;
    private int mRank;
    private String mDescription;
    private String mAction;
    private TypeElement mTypeElement;

    public Shortcut(Element element) {
        mTypeElement = (TypeElement) element;
        AppShortcut appShortcut = mTypeElement.getAnnotation(AppShortcut.class);
        mResId = appShortcut.resId();
        mRank = appShortcut.rank();
        mDescription = appShortcut.description();
        mAction = appShortcut.action();
    }

      //相关的getXXX()方法
      ……

}复制代码

以后在process()方法中遍历全部带有相关注解的Element,并生成model对象

for (Element element : roundEnvironment.getElementsAnnotatedWith(AppShortcut.class)) {
     //检查注解所标注的元素是否为咱们须要
     if (!isValid(element)) {
         return false;
     }
     //解析这个element并生成相应的Shortcut对象
     parseShortcut(element);
}复制代码

最后根据全部Shortcut对象生成Java文件

mShortcutClass.generateCode().writeTo(mFiler);复制代码

生成代码我使用Javapoet,能够很方便的生成代码,如下代码经过查看Javapoet的README就能够很快理解。

public JavaFile generateCode() {
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("create")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .addParameter(CONTEXT, "context")
                .addStatement("$T shortcutManager = context.getSystemService($T.class)", SHORTCUT_MANAGER, SHORTCUT_MANAGER)
                .addStatement("$T.Builder builder",SHORTCUT_INFO)
                .addStatement("$T intent",INTENT);

        for (Shortcut shortcut : mShortcuts) {
            methodBuilder.
                    addStatement("builder = new $T.Builder(context,$S)", SHORTCUT_INFO, shortcut.getTypeElement().getSimpleName().toString())
                    .addStatement("intent = new $T(context, $T.class)", INTENT, TypeName.get(shortcut.getTypeElement().asType()))
                    .addStatement("intent.setAction($S)", shortcut.getAction())
                    .addStatement("builder.setIntent(intent)")
                    .addStatement("builder.setShortLabel($S)", shortcut.getDescription())
                    .addStatement("builder.setLongLabel($S)", shortcut.getDescription())
                    .addStatement("builder.setRank($L)", shortcut.getRank())
                    .addStatement("builder.setIcon($T.createWithResource(context, $L))", ICON, shortcut.getResId())
                    .addStatement("shortcutManager.addDynamicShortcuts(singletonList(builder.build()))");
        }

        TypeSpec shortcutClass = TypeSpec.classBuilder(mTypeElement.getSimpleName() + SUFFIX)
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(CREATOR)
                .addMethod(methodBuilder.build())
                .build();

        String packageName = mElementUtils.getPackageOf(mTypeElement).getQualifiedName().toString();

        return JavaFile
                .builder(packageName, shortcutClass)
                .addStaticImport(Collections.class, "singletonList")
                .build();
}复制代码

提供调用接口

写完了注解的解释器后,咱们每次编译都生成了一个Java类文件,可是咱们并无调用它,咱们要提供一个接口来调用,本项目中提供了这样一个静态方法

ShortcutCreator.create(this);复制代码
public class ShortcutCreator {
    public static void create(Context context) {
        try {
            Class<?> targetClass = context.getClass();
            Class<?> creatorClass = Class.forName(targetClass.getName() + "$$Shortcut");
            Creator creator = (Creator) creatorClass.newInstance();
            creator.create(context);
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}复制代码

因为咱们利用APT自动生成的Java类的类名称是知道的且提供了默认的无参数构造器,因此咱们很容易生成一个对象,并调用其相关方法来生成相应的Shortcut。

总结

编译时注解能够大大加快咱们的开发效率,但愿你们能够多制做一些编译时注解的库来造福广大开发者,让你们少些许多简单重复的代码。最后附上源码地址:github.com/wuapnjie/Ea…,但愿对你们有所帮助。

相关文章
相关标签/搜索