您的位置:首页 > 移动开发 > Android开发

动态Android编程

2015-12-23 16:27 543 查看
注意:本文章有些例子需要对Java或Android有一定编程基础。
与Python相比,Java是一门比较严肃的语言。作为一个先学Python的程序员,做起Android难免会觉得不舒服,有些死板,非常怀念decorator等方便的方法。为了实现一个简单的逻辑,你可能需要写很多额外的代码。
举个例子,做数据库查询时,怎么从一个
Cursor
里取出类型为
User
的实例到
List

假设
User
是这样的
class User {
int id;
String name;
}


1. 找出
User
对应所有的列和每列在
Cursor
对应的索引。

2. 如果索引存在,根据类型取出正确的值。

3. 对于每个属性,不断重复上述步骤取出对应的值。
{
int columnIndex = cursor.getColumnIndex("id");
if (columnIndex >= 0) {
instance.id = cursor.getInt(columnIndex);
}
}

{
int columnIndex = cursor.getColumnIndex("name");
if (columnIndex >= 0) {
instance.name = cursor.getString(columnIndex);
}
}

这么做的问题在哪?

* 重复代码

* 重复代码

* 无聊

* 容易出错,不好维护

反射

我就是不想写那么多无聊的代码,怎么办?要不试试范型/反射。

1. 取出所有的属性。

2. 循环属性队列。

3. 把属性设置成accessible。

4. 找到索引。

5. 取出属性的类型,根据类型从
Cursor
里取出正确的值。
public static <T> T fromCursor(Cursor cursor, Class<T> cls) {
T instance = cls.newInstance();
List<Field> fields = Arrays.asList(cls.getDeclaredFields())
for (Field field : fields) {
field.setAccessible(true);
String fieldName = field.getName();
int columnIndex = 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...
}
return instance;
}

这样我们就不用很无聊的把同样的逻辑对于每种类型写一遍又一遍。


Processor

用了反射后,也会有一些其他问题,这样的代码可读性不是太好,不是很容易调试。
既然我们可以通过反射实现这些逻辑,为什么不干脆通过反射把这部分代码直接生成出来呢?

1. 定义你要处理的annotation。
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
String value();
}


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")
public class MyProcessor extends AbstractProcessor {


3. 重载
process
方法,实现生成代码的逻辑。
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(MyAnnotation.class);
Set<? extends TypeElement> 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");

... // 像在反射那节中,取出所有的fields,并循环取出每个元素,即每列
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";
} else if (columnType == ColumnType.LONG) {
cursorType = "Long";
} else if (columnType == ColumnType.FLOAT) {
cursorType = "Float";
} else if (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());
// 结束循环

// 生成代码到文件里
String className = ... // 设置你要生成的代码class名字
JavaFileObject sourceFile =   processingEnv.getFiler().createSourceFile(className, element);
Writer writer = sourceFile.openWriter();
javaFile.writeTo(writer);
writer.close();
}

return false;
}


4. 修改
User
class加上annotation
@MyAnnotation
class User { int id; 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
项目里创建Plugin
public class DbPlugin implements Plugin<Project> {

@Override
void apply(Project project) {
project.task('initDb') << {
def dir = project.getProjectDir()
def file = new File(dir, "table.toml")
generateCode(dir, new Toml().parse(file).to(DB.class))
}
}

static void generateCode(File dir, DB db) {
def outputDir = new File(dir, db.srcDir)
outputDir.mkdirs()
for (Table table : db.tables) {
//  Process it
}
}
}


3. 像在上节讲的那样生成代码,把数据源从annotation换成toml里的定义

4. 在项目里把Plugin引用进去,并执行

5. 这样就可以得到漂亮的已经生成好的代码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: