V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Glowapp
V2EX  ›  分享发现

Glow 技术博客 之 动态 Android 编程 by Glow Android tech lead Sol

  •  
  •   Glowapp · 2015-12-11 14:13:16 +08:00 · 1371 次点击
    这是一个创建于 3269 天前的主题,其中的信息可能已经有所发展或是发生改变。

    与 Python 相比, Java 是一门比较严肃的语言。作为一个先学 Python 的程序员,做起 Android 难免会觉得不舒服,有些死板,非常怀念 decorator 等方便的方法。为了实现一个简单的逻辑,你可能需要写很多额外的代码。

    全文请移步 Glow 技术博客
    http://tech.glowing.com/cn/dynamic-android-programming/

    Glow 的技术博客会不定期更新,欢迎大家多多关注。

    1 条回复    2015-12-11 14:17:36 +08:00
    Glowapp
        1
    Glowapp  
    OP
       2015-12-11 14:17:36 +08:00
    举个例子,怎么从一个 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. 这样就可以得到漂亮的已经生成好的代码
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2687 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 12:20 · PVG 20:20 · LAX 04:20 · JFK 07:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.