Glow 技术博客 之 动态 Android 编程 by Glow Android tech lead Sol - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Glowapp
V2EX    分享发现

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

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

    与 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/a>     Solana     1103 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 18:13 PVG 02:13 LAX 10:13 JFK 13:13
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86