原文轉(zhuǎn)載地址:http://tech.glowing.com/cn/dynamic-android-programming/
作者:劉聰
MENU
動(dòng)態(tài)Android編程
11 DECEMBER 2015
注意:本文章有些例子需要對(duì)Java或Android有一定編程基礎(chǔ)。
與Python相比慕蔚,Java是一門比較嚴(yán)肅的語(yǔ)言。作為一個(gè)先學(xué)Python的程序員,做起Android難免會(huì)覺得不舒服,有些死板允坚,非常懷念decorator等方便的方法呵晨。為了實(shí)現(xiàn)一個(gè)簡(jiǎn)單的邏輯吐限,你可能需要寫很多額外的代碼。
舉個(gè)例子天揖,做數(shù)據(jù)庫(kù)查詢時(shí)夺欲,怎么從一個(gè)Cursor里取出類型為User的實(shí)例到List?
假設(shè)User是這樣的
classUser{
intid;
String name;
}
1. 找出User對(duì)應(yīng)所有的列和每列在Cursor對(duì)應(yīng)的索引今膊。
2. 如果索引存在些阅,根據(jù)類型取出正確的值。
3. 對(duì)于每個(gè)屬性斑唬,不斷重復(fù)上述步驟取出對(duì)應(yīng)的值市埋。
{
intcolumnIndex = cursor.getColumnIndex("id");
if(columnIndex >=0) {
instance.id = cursor.getInt(columnIndex);
}
}
{
intcolumnIndex = cursor.getColumnIndex("name");
if(columnIndex >=0) {
instance.name = cursor.getString(columnIndex);
}
}
這么做的問題在哪?
* 重復(fù)代碼
* 重復(fù)代碼
* 無(wú)聊
* 容易出錯(cuò)黎泣,不好維護(hù)
反射
我就是不想寫那么多無(wú)聊的代碼,怎么辦缤谎?要不試試范型/反射抒倚。
1. 取出所有的屬性。
2. 循環(huán)屬性隊(duì)列坷澡。
3. 把屬性設(shè)置成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;
}
這樣我們就不用很無(wú)聊的把同樣的邏輯對(duì)于每種類型寫一遍又一遍。
用了反射后斟赚,也會(huì)有一些其他問題呆抑,這樣的代碼可讀性不是太好,不是很容易調(diào)試汁展。
既然我們可以通過(guò)反射實(shí)現(xiàn)這些邏輯鹊碍,為什么不干脆通過(guò)反射把這部分代碼直接生成出來(lái)呢?
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方法器紧,實(shí)現(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)取出每個(gè)元素熊尉,即每列
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 = ...// 設(shè)置你要生成的代碼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工具把我們上面寫的庫(kù)加到編譯過(guò)程去。
Tips:
* 用AutoService可以方便的生成Processor方法
* 強(qiáng)推Javapoet掌腰,用來(lái)生成漂亮的代碼
AOP
AOP的做法和Processor類似狰住,這里就不詳述。你可能用AspectJ齿梁。
Gradle plugin
最后我還是沒有完全采用上面的方法催植,因?yàn)?
* 在編譯時(shí)生成的代碼在打開編譯器時(shí)找不到
* 有時(shí)候有些特殊需求,比如很多屬性要在多個(gè)地方共享使用勺择,能配置化會(huì)更好些
于是我們就用了Gradle Plugin來(lái)通過(guò)可配置文件生成代碼
以下是簡(jiǎn)單的例子:
1. 定義配置文件创南,這里選用比較簡(jiǎn)單的toml文件
srcDir ="src/main/java"
pkg ="com.glow.android.storage.db"
[[tables]]
name ="user"
[[tables.columns]]
name ="id"
type="int"
isKey =true
2. 在buildSrc項(xiàng)目里創(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. 在項(xiàng)目里把Plugin引用進(jìn)去省核,并執(zhí)行
5. 這樣就可以得到漂亮的已經(jīng)生成好的代碼