標籤:

聊聊Apt/Annotation Processor

背景

開發過程中,經常會遇到要編寫好多重複性代碼,這個時候就要使出control-c,control-v大法,但是萬一粘貼錯了怎麼辦。有沒有靠譜一點的解決方案。

先來看一個小例子,讀取Settings的相關配置。

以下代碼為精簡過的偽代碼。

public interface IGlobalSettingObserver {
void onSaveData(SharedPreferences.Editor editor);
void onLoadData(SharedPreferences sp);
}

簡單解釋下上面的介面,每個功能模塊自定義自己的SettingObserver,實現onSaveData, onLoadData方法

public class GarageGlobalSetting implements IGlobalSettingObserver {

private String sp_key_ad_banner_enable = "sp_key_gs_garage_ad_banner_enable";
private String sp_key_ad_banner_info = "sp_key_gs_garage_ad_banner_info";
private String sp_key_operation_entrance_count = "sp_key_gs_garage_operation_entrance_count";
private String sp_key_operation_entrance_info = "sp_key_gs_garage_operation_entrance_info";
private String sp_key_garage_tab_title = "sp_key_garage_tab_title";
private String sp_key_second_hand_car_url = "sp_key_second_hand_car_url";
private String sp_key_cars_classify = "sp_key_cars_classify";

public boolean ad_banner_enable;
public String ad_banner_info;
public int operation_entrance_count = 0;
public String operation_entrance_info;
public String garage_tab_title_jsonarray;
public String second_hand_car_url;
public String cars_classify;

@Override
public void onSaveData(SharedPreferences.Editor editor) {
editor.putBoolean(sp_key_ad_banner_enable, ad_banner_enable);
editor.putString(sp_key_ad_banner_info, ad_banner_info);
editor.putInt(sp_key_operation_entrance_count, operation_entrance_count);
editor.putString(sp_key_operation_entrance_info, operation_entrance_info);
editor.putString(sp_key_garage_tab_title, garage_tab_title_jsonarray);
editor.putString(sp_key_second_hand_car_url, second_hand_car_url);
editor.putString(sp_key_cars_classify, cars_classify);
}

@Override
public void onLoadData(SharedPreferences sp) {
ad_banner_enable = sp.getBoolean(sp_key_ad_banner_enable, false);
ad_banner_info = sp.getString(sp_key_ad_banner_info, "");
operation_entrance_count = sp.getInt(sp_key_operation_entrance_count, 0);
operation_entrance_info = sp.getString(sp_key_operation_entrance_info, "");
garage_tab_title_jsonarray = sp.getString(sp_key_garage_tab_title, "");
second_hand_car_url = sp.getString(sp_key_second_hand_car_url, "");
cars_classify = sp.getString(sp_key_cars_classify, "");
}
}

隨著業務的迭代,每次新增一個欄位,我們要做下面的幾件事情,1.先定義一個SP存儲的key。 2.定義一個變數存值。3.在onSaveData()方法,調用editor.put。 4.在onLoadData方法中get值。

重複代碼寫多了,一個是容易出錯,一個是容易厭煩,上面的代碼能不能自動生成?經過一系列操作後,代碼變成了這樣。

public class GarageGlobalSetting implements IGlobalSettingObserver {
@SettingKey("ad_banner_enable")
public boolean ad_banner_enable;
@SettingKey("ad_banner_info")
public String ad_banner_info;
@SettingKey("operation_entrance_count")
public int operation_entrance_count;
@SettingKey("operation_entrance_info")
public String operation_entrance_info;
@SettingKey("garage_tab_title_jsonarray")
public String garage_tab_title_jsonarray;
@SettingKey("second_hand_car_url")
public String second_hand_car_url;
@SettingKey("cars_classify")
public String cars_classify;

@Override
public void onSaveData(SharedPreferences.Editor editor) {
}

@Override
public void onLoadData(SharedPreferences sp) {
}
}

那麼進入今天的主題Annotation Processor。

Annotation Processor與APT的區別

APT(Annotation Processing Tool)是一種處理注釋的工具,它對源代碼文件進行檢測找出其中的Annotation,使用Annotation進行額外的處理。高版本的gradle已經不支持APT了。如果要使用,要把相關的代碼刪除,官方默認支持AnnotationProcessor。

//根目錄build.gradle
classpath com.neenbedankt.gradle.plugins:android-apt:1.8
//app目錄build.gradle
apply plugin: android-apt

AnnotationProcessor在處理Annotation時可以根據源文件中的Annotation生成額外的源文件和其它的文件(文件具體內容由Annotation處理器的編寫者決定)

工作原理圖

使用步驟

1.創建java模塊,annotationModule,

gradle配置

apply plugin: java-library
dependencies {
implementation fileTree(dir: libs, include: [*.jar])
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"

定義註解介面。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface SettingKey {
String value();
int defaultInt() default 0;
long defaultLong() default 0L;
float defaultFloat() default 0F;
boolean defaultBoolean() default false;
String defaultString() default "";
}

定義中轉類。

public class ClassFinder {
public static final String SUFFIX = "$$Impl";
private ClassFinder() {}
public static <T> T findClass(Class<T> clazz) {
T t = null;
try {
@SuppressWarnings("unchecked")
Class<T> realClazz = (Class<T>) Class.forName(clazz.getCanonicalName() + SUFFIX);
if (realClazz != null) {
t = realClazz.newInstance();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}

return t;
}
}

2.創建java模塊,compiler模塊

gradle配置

apply plugin: java-library
dependencies {
implementation fileTree(dir: libs, include: [*.jar])

compile com.google.auto.service:auto-service:1.0-rc2
compile com.squareup:javapoet:1.7.0
compile project(:annotation)
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"

定義SettingProcessor.java

@AutoService(Processor.class)
public class SettingProcessor extends AbstractProcessor {
public synchronized void init(ProcessingEnvironment processingEnv) {
//init操作
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//注意要點
//1.process會多次調用。
//2.annotations可以獲取剛剛定義的註解種類,
//3.roundEnv.getElementsAnnotatedWith(SettingKey.class)獲取列表
//4.獲取註解的類,生成子類XXX$$Impl.java(路徑build/generated/source/apt/debug),生成對方的方法,添加對應的方法塊。
return true;
}

@Override
public Set getSupportedAnnotationTypes() {
//添加自定義的註解種類
Set types = new LinkedHashSet<>();
types.add(SettingKey.class.getCanonicalName());
return types;
}
}

3.主app依賴

gradle配置

compile project(:annotation)
annotationProcessor project(:compiler)

中轉類使用

GarageGlobalSetting garageGlobalSetting =
ClassFinder.findClass(GarageGlobalSetting.class);

調試AbstractProcessor

1.gradle.properties添加配置

org.gradle.daemon=true
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8014

2.新建remote service

3.終端執行守護線程

./gradlew --daemon

4.點擊debug按鈕,設置斷點

5.clean編譯

./gradlew clean assembleDebug

推薦閱讀:

TAG:Android開發 |