动态Android编程
2015-12-23 16:27
543 查看
注意:本文章有些例子需要对Java或Android有一定编程基础。
与Python相比,Java是一门比较严肃的语言。作为一个先学Python的程序员,做起Android难免会觉得不舒服,有些死板,非常怀念decorator等方便的方法。为了实现一个简单的逻辑,你可能需要写很多额外的代码。
举个例子,做数据库查询时,怎么从一个
假设
1. 找出
2. 如果索引存在,根据类型取出正确的值。
3. 对于每个属性,不断重复上述步骤取出对应的值。
这么做的问题在哪?
* 重复代码
* 重复代码
* 无聊
* 容易出错,不好维护
1. 取出所有的属性。
2. 循环属性队列。
3. 把属性设置成accessible。
4. 找到索引。
5. 取出属性的类型,根据类型从
这样我们就不用很无聊的把同样的逻辑对于每种类型写一遍又一遍。
用了反射后,也会有一些其他问题,这样的代码可读性不是太好,不是很容易调试。
既然我们可以通过反射实现这些逻辑,为什么不干脆通过反射把这部分代码直接生成出来呢?
1. 定义你要处理的annotation。
2. 定义你的
3. 重载
4. 修改
5. 用apt工具把我们上面写的库加到编译过程去。
Tips:
* 用AutoService可以方便的生成
* 强推Javapoet,用来生成漂亮的代码
* 在编译时生成的代码在打开编译器时找不到
* 有时候有些特殊需求,比如很多属性要在多个地方共享使用,能配置化会更好些
于是我们就用了Gradle Plugin来通过可配置文件生成代码
以下是简单的例子:
1. 定义配置文件,这里选用比较简单的toml文件
2. 在
3. 像在上节讲的那样生成代码,把数据源从annotation换成toml里的定义
4. 在项目里把Plugin引用进去,并执行
5. 这样就可以得到漂亮的已经生成好的代码
与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. 修改
Userclass加上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. 这样就可以得到漂亮的已经生成好的代码
相关文章推荐
- 单独编译Android系统模块并替换进系统
- Android HandlerThread 的使用
- Android系统启动分析(Init->Zygote->SystemServer->Home activity)
- Android ListView 九大重要属性详细分析
- Android Accessibility(辅助功能) --实现Android应用自动安装、卸载
- 如何查看android data 内容
- AndroidStudio怎样导入jar包
- Android ListView使用(非原创)
- Android环境下使用cordova从事手写签名代码
- Android 之 去除 Jar 包中 META-INF/DEPENDENCIES.txt
- 史上最详细的Android Studio系列教程四--Gradle基础
- Android MediaProvider数据库模式说明
- Android代码混淆
- 将genymotion模拟器集成到androidStudio里面
- Stetho(Facebook出品)---Android调试器的使用
- Android--ViewPager
- Android 常用API总结
- Android Fragment(碎片)的使用
- mac 搭建android studio 开发环境
- Android学习之动画实例