動態(tài)Android編程

原文轉(zhuǎn)載地址:http://tech.glowing.com/cn/dynamic-android-programming/

作者:劉聰

MENU

Home

SUBSCRIBE

MENU

動態(tài)Android編程

11 DECEMBER 2015

注意:本文章有些例子需要對Java或Android有一定編程基礎。

與Python相比,Java是一門比較嚴肅的語言。作為一個先學Python的程序員,做起Android難免會覺得不舒服,有些死板,非常懷念decorator等方便的方法。為了實現(xiàn)一個簡單的邏輯,你可能需要寫很多額外的代碼。

舉個例子,做數(shù)據(jù)庫查詢時,怎么從一個Cursor里取出類型為User的實例到List?

假設User是這樣的

classUser{

intid;

String name;

}

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

2. 如果索引存在,根據(jù)類型取出正確的值。

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. 循環(huán)屬性隊列。

3. 把屬性設置成accessible。

4. 找到索引。

5. 取出屬性的類型,根據(jù)類型從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

用了反射后,也會有一些其他問題,這樣的代碼可讀性不是太好,不是很容易調(diào)試。

既然我們可以通過反射實現(xiàn)這些邏輯,為什么不干脆通過反射把這部分代碼直接生成出來呢?

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方法,實現(xiàn)生成代碼的邏輯。

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

...// 像在反射那節(jié)中,取出所有的fields,并循環(huán)取出每個元素,即每列

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

// 結(jié)束循環(huán)

// 生成代碼到文件里

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項目里創(chuàng)建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. 像在上節(jié)講的那樣生成代碼,把數(shù)據(jù)源從annotation換成toml里的定義

4. 在項目里把Plugin引用進去,并執(zhí)行

5. 這樣就可以得到漂亮的已經(jīng)生成好的代碼

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,991評論 19 139
  • 很實用的編程英語詞庫,共收錄一千五百余條詞匯。 第一部分: application 應用程式 應用、應用程序app...
    春天的蜜蜂閱讀 1,435評論 0 22
  • WHY 如果說OOP(面向?qū)ο蟮某绦蛟O計)的主要思想是將問題歸分到相應的對象(類)中去實現(xiàn),再把相關類模塊化使得模...
    野生大P閱讀 986評論 0 8
  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,554評論 0 17
  • 站在一顆星球上 看滿天無數(shù)繁星 你不會孤單 因為我也在另一顆星球上 一樣的看著同一片星空
    辛安小閱讀 582評論 41 51