与 Python 相比, Java 是一门比较严肃的语言。作为一个先学 Python 的程序员,做起 Android 难免会觉得不舒服,有些死板,非常怀念 decorator 等方便的方法。为了实现一个简单的逻辑,你可能需要写很多额外的代码。
全文请移步 Glow 技术博客
http://tech.glowing.com/cn/dynamic-android-programming/
Glow 的技术博客会不定期更新,欢迎大家多多关注。
1
Glowapp OP 举个例子,怎么从一个 Cursor 里取出类型为 ClassA 的实例到 List ?
1. 找出 ClassA 对应所有的列和每列在 Cusor 对应的索引。 int columnIndex = cursor.getColumnIndex("columnA"); 2. 如果索引存在,根据类型取出正确的值。 if (columnIndex >= 0) { instance.columnA = cursor.getString(columnIndex); } 3. 对于每个属性,不断重复上述步骤取出对应的值。 这么做的问题在哪? * 重复代码 * 重复代码 * 无聊 * 容易出错,不好维护 反射 我就是不想写那么多无聊的代码,怎么办?要不试试范型/反射。 1. 取出所有的属性。 Arrays.asList(cls.getDeclaredFields()) 2. 循环属性队列。 3. 把属性设置成 accessible 。 field.setAccessible(true); 4. 找到索引。 int columnIndex = cursor.getColumnIndex(fieldName); if (columnIndex < 0) { continue; } 5. 取出属性的类型,根据类型从 Cursor 里取出正确的值。 Class fieldType = field.getType(); if (fieldType.equals(int.class)) { field.setInt(instance, cursor.getInt(columnIndex)); } else { // more type check 6. 结束循环。 这样我们就不用很无聊的把同样的逻辑对于每种类型写一遍又一遍。 Processor 用了反射后,也会有一些其他问题,这样的代码可读性不是太好,不是很容易调试。 既然我们可以通过反射实现这些逻辑,为什么不干脆通过反射把这部分代码直接生成出来呢? 1. 定义你要处理的 annotation 。 2. 定义你的 Processor 类,继承 AbstractProcessor 。 @AutoService(Processor.class) @SupportedSourceVersion(SourceVersion.RELEASE_7) @SupportedAnnotationTypes("com.glow.android.Annotation") public class MyProcessor extends AbstractProcessor { 3. 创建要生成的方法。 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"); 4. 循环取出每一列,并像下面这样生成代码。 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); } 5. 把代码输出到编译时的文件里。 JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile( concatClassName(packageName, className), element); Writer writer = sourceFile.openWriter(); javaFile.writeTo(writer); writer.close(); 6. 用 apt 工具把我们上面写的库加到编译过程去。 Tips: * 用 AutoService 可以方便的生成 Processor 方法 * 强推 Javapoet ,用来生成漂亮的代码 AOP AOP 的做法和 Processor 类似,这里就不详述。你可能用 AspectJ 。 Gradle plugin 最后我还是没有完全采用上面的方法,因为: * 在编译时生成的代码在打开编译器时找不到 * 有时候有些特殊需求,比如很多属性要在多个地方共享使用,能配置化会更好些 于是我们就用了 Gradle Plugin 来通过可配置文件生成代码 以下是简单的例子: 1. 定义配置文件,这里选用比较简单的 toml 文件 srcDir = "src/main/java" pkg = "com.glow.android.baby.storage.db" [[tables]] name = "user" [[tables.columns]] name = "user_id" type = "long" 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. 这样就可以得到漂亮的已经生成好的代码 |