最近學習了編譯時註解框架的製做,寫了一個小項目。閱讀本文前但願你們有關於註解的相關知識。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,全稱Annotation Processing Tool,它用來在編譯時處理源代碼中的註解信息,咱們能夠根據註解來生成一些Java文件,防止編寫冗餘的代碼,好比ButterKnife項目,正是利用了APT工具,幫助咱們少寫了許多重複冗餘的代碼。本文中,經過註解來少寫一些配置文件。框架
本項目共分爲4個Module,兩個Java Library module,一個Android Library module和用於演示的Android Application moduleide
Processor
的註解處理模塊其中easyshortcuts-api和easyshortcuts-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代碼。
新建一個繼承自AbstractProcessor
的ShortcutProcessor
,下面是一個基本的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…,但願對你們有所幫助。