原文轉(zhuǎn)載地址:http://tech.glowing.com/cn/dynamic-android-programming/
作者:劉聰
MENU
動態(tài)Android編程
11 DECEMBER 2015
注意:本文章有些例子需要對Java或Android有一定編程基礎。
與Python相比,Java是一門比較嚴肅的語言。作為一個先學Python的程序員,做起Android難免會覺得不舒服,有些死板,非常懷念decorator等方便的方法。為了實現(xiàn)一個簡單的邏輯,你可能需要寫很多額外的代碼。
舉個例子,做數(shù)據(jù)庫查詢時,怎么從一個Cursor里取出類型為User的實例到List?
假設User是這樣的
classUser{
intid;
String name;
}
1. 找出User對應所有的列和每列在Cursor對應的索引。
2. 如果索引存在,根據(jù)類型取出正確的值。
3. 對于每個屬性,不斷重復上述步驟取出對應的值。
{
intcolumnIndex = cursor.getColumnIndex("id");
if(columnIndex >=0) {
instance.id = cursor.getInt(columnIndex);
}
}
{
intcolumnIndex = cursor.getColumnIndex("name");
if(columnIndex >=0) {
instance.name = cursor.getString(columnIndex);
}
}
這么做的問題在哪?
* 重復代碼
* 重復代碼
* 無聊
* 容易出錯,不好維護
反射
我就是不想寫那么多無聊的代碼,怎么辦?要不試試范型/反射。
1. 取出所有的屬性。
2. 循環(huán)屬性隊列。
3. 把屬性設置成accessible。
4. 找到索引。
5. 取出屬性的類型,根據(jù)類型從Cursor里取出正確的值。
publicstaticTfromCursor(Cursor cursor, Class cls){
T instance = cls.newInstance();
List fields = Arrays.asList(cls.getDeclaredFields())
for(Field field : fields) {
field.setAccessible(true);
String fieldName = field.getName();
intcolumnIndex = cursor.getColumnIndex(fieldName);
if(columnIndex <0) {
continue;
}
Class fieldType = field.getType();
if(fieldType.equals(int.class)) {
field.setInt(instance, cursor.getInt(columnIndex));
}else{
// 處理更多類型 String/float...
}
returninstance;
}
這樣我們就不用很無聊的把同樣的邏輯對于每種類型寫一遍又一遍。
用了反射后,也會有一些其他問題,這樣的代碼可讀性不是太好,不是很容易調(diào)試。
既然我們可以通過反射實現(xiàn)這些邏輯,為什么不干脆通過反射把這部分代碼直接生成出來呢?
1. 定義你要處理的annotation。
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public@interface MyAnnotation {
Stringvalue();
}
2. 定義你的Processor類,繼承AbstractProcessor。
// Helper to define the Processor
@AutoService(Processor.class)
// Define the supported Java source code version
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// Define which annotation you want to process
@SupportedAnnotationTypes("com.glow.android.MyAnnotation")
publicclassMyProcessorextendsAbstractProcessor{
3. 重載process方法,實現(xiàn)生成代碼的邏輯。
@Override
publicboolean process(Set annotations, RoundEnvironment roundEnv) {
Set elements = roundEnv.getElementsAnnotatedWith(MyAnnotation.class);
Set typeElements = ElementFilter.typesIn(elements);
for(TypeElement element : typeElements) {
ClassName currentType = ClassName.get(element);
MethodSpec.Builder builder = MethodSpec.methodBuilder("fromCursor")
.returns(currentType)
.addModifiers(Modifier.STATIC)
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.get("android.database","Cursor"),"cursor");
...// 像在反射那節(jié)中,取出所有的fields,并循環(huán)取出每個元素,即每列
CodeBlock.Builder blockBuilder = CodeBlock.builder();
blockBuilder.beginControlFlow("");
blockBuilder.addStatement("int columnIndex = cursor.getColumnIndex($S)", column);
blockBuilder.beginControlFlow("if (columnIndex >= 0)");
ColumnType columnType = columnTypeMap.get(column);
String cursorType =null;
if(columnType == ColumnType.INT) {
cursorType ="Int";
}elseif(columnType == ColumnType.LONG) {
cursorType ="Long";
}elseif(columnType == ColumnType.FLOAT) {
cursorType ="Float";
}elseif(columnType == ColumnType.STRING) {
cursorType ="String";
}else{
abort("Unsupported type", element);
}
blockBuilder.addStatement("instance.$L = cursor.get$L(columnIndex)",
fieldName,
cursorType);
blockBuilder.endControlFlow();
blockBuilder.endControlFlow();
builder.addCode(blockBuilder.build());
// 結(jié)束循環(huán)
// 生成代碼到文件里
String className = ...// 設置你要生成的代碼class名字
JavaFileObject sourceFile =processingEnv.getFiler().createSourceFile(className, element);
Writer writer = sourceFile.openWriter();
javaFile.writeTo(writer);
writer.close();
}
returnfalse;
}
4. 修改Userclass加上annotation
@MyAnnotation
classUser{
intid;
String name;
}
5. 用apt工具把我們上面寫的庫加到編譯過程去。
Tips:
* 用AutoService可以方便的生成Processor方法
* 強推Javapoet,用來生成漂亮的代碼
AOP
AOP的做法和Processor類似,這里就不詳述。你可能用AspectJ。
Gradle plugin
最后我還是沒有完全采用上面的方法,因為:
* 在編譯時生成的代碼在打開編譯器時找不到
* 有時候有些特殊需求,比如很多屬性要在多個地方共享使用,能配置化會更好些
于是我們就用了Gradle Plugin來通過可配置文件生成代碼
以下是簡單的例子:
1. 定義配置文件,這里選用比較簡單的toml文件
srcDir ="src/main/java"
pkg ="com.glow.android.storage.db"
[[tables]]
name ="user"
[[tables.columns]]
name ="id"
type="int"
isKey =true
2. 在buildSrc項目里創(chuàng)建Plugin
publicclassDbPluginimplementsPlugin {
@Override
voidapply(Project project){
project.task('initDb') << {
def dir = project.getProjectDir()
def file =newFile(dir,"table.toml")
generateCode(dir,newToml().parse(file).to(DB.class))
}
}
staticvoidgenerateCode(File dir, DB db){
def outputDir =newFile(dir, db.srcDir)
outputDir.mkdirs()
for(Table table : db.tables) {
//Process it
}
}
}
3. 像在上節(jié)講的那樣生成代碼,把數(shù)據(jù)源從annotation換成toml里的定義
4. 在項目里把Plugin引用進去,并執(zhí)行
5. 這樣就可以得到漂亮的已經(jīng)生成好的代碼