技术干货 > 文章详情

动态 Android 编程:避免无聊的代码

麦子新闻1年前 Android

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

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


假设User是这样的

1
2
3
4
class User {  
    int id;
    String name;
}

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


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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  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里取出正确的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static  T fromCursor(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();
      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。

1
2
3
4
5
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface MyAnnotation {  
  String value();
}

2. 定义你的Processor类,继承AbstractProcessor。

1
2
3
4
5
6
7
// 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方法,实现生成代码的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
 @Override
  public boolean 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";
      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

1
2
3
4
 @MyAnnotationclass User {  
    int id;
    String name;
}

5. 用apt工具把我们上面写的库加到编译过程去。


Tips:
* 用AutoService可以方便的生成Processor方法
* 强推Javapoet,用来生成漂亮的代码


AOP


AOP的做法和Processor类似,这里就不详述。你可能用AspectJ。


Gradle plugin


最后我还是没有完全采用上面的方法,因为:
* 在编译时生成的代码在打开编译器时找不到
* 有时候有些特殊需求,比如很多属性要在多个地方共享使用,能配置化会更好些


于是我们就用了Gradle Plugin来通过可配置文件生成代码


以下是简单的例子:
1. 定义配置文件,这里选用比较简单的toml文件

1
2
3
4
5
6
7
8
srcDir = "src/main/java"  
pkg = "com.glow.android.storage.db"  
[[tables]]
name = "user"  
  [[tables.columns]]
  name = "id"
  type = "int"
  isKey = true

2. 在buildSrc项目里创建Plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DbPlugin implements Plugin {
 
  @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. 这样就可以得到漂亮的已经生成好的代码

(原文源自:cocochina 链接:http://www.cocoachina.com/android/20151221/14725.html)

推荐阅读:麦子专题丨一分钟了解Android开发

            给Android开发面试者的几个建议

            Android开发丨10个UI设计图标资源站

推荐学习:Android开发企业直通班带你玩转Android开发


1

登录 后参与讨论

没有更多评论了

有一位课程导师想与你聊聊

客服热线 400-862-8862

回到顶部