动态Android编程

原文转载地址:http://tech.glowing.com/cn/dynamic-android-programming/

作者:刘聪

MENU

Home

SUBSCRIBE

MENU

动态Android编程

11 DECEMBER 2015

注意:本文章有些例子需要对Java或Android有一定编程基础。

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

举个例子,做数据库查询时,怎么从一个Cursor里取出类型为User的实例到List?

假设User是这样的

classUser{

intid;

String name;

}

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

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

3. 对于每个属性,不断重复上述步骤取出对应的值。

{

intcolumnIndex = cursor.getColumnIndex("id");

if(columnIndex >=0) {

instance.id = cursor.getInt(columnIndex);

}

}

{

intcolumnIndex = cursor.getColumnIndex("name");

if(columnIndex >=0) {

instance.name = cursor.getString(columnIndex);

}

}

这么做的问题在哪?

* 重复代码

* 重复代码

* 无聊

* 容易出错,不好维护

反射

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

1. 取出所有的属性。

2. 循环属性队列。

3. 把属性设置成accessible。

4. 找到索引。

5. 取出属性的类型,根据类型从Cursor里取出正确的值。

publicstaticTfromCursor(Cursor cursor, Class cls){

T instance = cls.newInstance();

List fields = Arrays.asList(cls.getDeclaredFields())

for(Field field : fields) {

field.setAccessible(true);

String fieldName = field.getName();

intcolumnIndex = 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...

}

returninstance;

}

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

Processor

用了反射后,也会有一些其他问题,这样的代码可读性不是太好,不是很容易调试。

既然我们可以通过反射实现这些逻辑,为什么不干脆通过反射把这部分代码直接生成出来呢?

1. 定义你要处理的annotation。

@Retention(RetentionPolicy.CLASS)

@Target(ElementType.TYPE)

public@interface MyAnnotation {

Stringvalue();

}

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")

publicclassMyProcessorextendsAbstractProcessor{

3. 重载process方法,实现生成代码的逻辑。

@Override

publicboolean process(Set annotations, RoundEnvironment roundEnv) {

Set elements = roundEnv.getElementsAnnotatedWith(MyAnnotation.class);

Set 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";

}elseif(columnType == ColumnType.LONG) {

cursorType ="Long";

}elseif(columnType == ColumnType.FLOAT) {

cursorType ="Float";

}elseif(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();

}

returnfalse;

}

4. 修改Userclass加上annotation

@MyAnnotation

classUser{

intid;

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

publicclassDbPluginimplementsPlugin {

@Override

voidapply(Project project){

project.task('initDb') << {

def dir = project.getProjectDir()

def file =newFile(dir,"table.toml")

generateCode(dir,newToml().parse(file).to(DB.class))

}

}

staticvoidgenerateCode(File dir, DB db){

def outputDir =newFile(dir, db.srcDir)

outputDir.mkdirs()

for(Table table : db.tables) {

//Process it

}

}

}

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

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

5. 这样就可以得到漂亮的已经生成好的代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 很实用的编程英语词库,共收录一千五百余条词汇。 第一部分: application 应用程式 应用、应用程序app...
    春天的蜜蜂阅读 1,351评论 0 22
  • WHY 如果说OOP(面向对象的程序设计)的主要思想是将问题归分到相应的对象(类)中去实现,再把相关类模块化使得模...
    野生大P阅读 953评论 0 8
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,397评论 0 17
  • 站在一颗星球上 看满天无数繁星 你不会孤单 因为我也在另一颗星球上 一样的看着同一片星空
    辛安小阅读 571评论 41 51