Skip to content

Commit fde0ca3

Browse files
committed
优化语言并去掉英文
1 parent ac35836 commit fde0ca3

File tree

1 file changed

+18
-66
lines changed
  • androidweekly/使用buildSrc Gradle项目和Codemodel生成java代码

1 file changed

+18
-66
lines changed

androidweekly/使用buildSrc Gradle项目和Codemodel生成java代码/readme.md

Lines changed: 18 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,12 @@
99
> * 状态 : 校对中
1010
1111
##Introduction
12-
13-
Assume that you have to embed some data in your Android application. Data which is gathered from some external source and needs to be parsed to be usable inside an app. In this example we will use list of Internet top-level domains. As you can see at ICANN TLD Report there are over 1000 TLDs and new ones are being added once in a while. Furthermore over the time some domains have been retired.
1412

15-
假设现在你现在需要在Android应用中嵌入和解析外部数据,你会怎么做?在本文中我们将在应用中获取[互联网顶级域名的列表](https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains)。你可以在[ICANN TLD报告](http://stats.research.icann.org/dns/tld_report/)报告中看到一共有一千多种顶级域名,时不时有新域名的加入,也有旧的域名被废弃。
16-
17-
[API](https://developer.android.com/reference/android/util/Patterns.html#TOP_LEVEL_DOMAIN_STR) for managing TLDs in Android framework has been deprecated because it will become out-of-date very quickly. So where to get up-to-date TLD list from? Fortunately there is machine-friendly source maintained by IANA: IANA — Root Zone Database. OK, we’ve got a source, so how to embed that data in app? There are many ways to do it, for example we can just put downloaded text file in assets or res/raw directory and parse it at runtime. But there is more efficient way – parse data before app compilation and use it ready-made at runtime. We can generate Java code which will provide method say getTldList() which will always return TLDs up-to-date at compilation time. Just like Android build tools generate fresh R class at every compilation.
13+
假设现在你现在需要在Android应用中嵌入和解析外部数据,你会怎么做?在本文中我们将在应用中获取[互联网顶级域名(TLD)的列表](https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains)。你可以在[ICANN TLD报告](http://stats.research.icann.org/dns/tld_report/)报告中看到一共有一千多种顶级域名,时不时有新域名的加入,也有旧的域名被废弃。
1814

19-
Android中管理TLDs的[API](https://developer.android.com/reference/android/util/Patterns.html#TOP_LEVEL_DOMAIN_STR)因为它很快就过时了已经不赞成使用。哪里能获得最新的TLD的列表呢?幸好这里有一个INAN维护的机器识别源:[IANA - Root Zone DataBase](https://data.iana.org/TLD/tlds-alpha-by-domain.txt).好了,我们已经获得了一个源,那如何在应用中嵌入呢?这里有一些方法,例如,我们可以下载文本文件到**assets**或者**res/raw**目录,运行时再解析。但这里有一个更有效的方法-应用编译前解析数据和运行时使用已经处理好的数据。我们可以使用所提供的方法叫**getTldList()**,它返回最新的TLDS。就像Android的编译工具在每次编译时自动刷新**R**类一样。
20-
21-
##How generated code should look like?
15+
由于Android中管理TLDs的[API](https://developer.android.com/reference/android/util/Patterns.html#TOP_LEVEL_DOMAIN_STR)很快就过时了,不赞成使用。哪里能获得最新的TLD的列表呢?幸好这里有一个INAN维护的机器识别源:[IANA - Root Zone DataBase](https://data.iana.org/TLD/tlds-alpha-by-domain.txt).好了,我们已经获得了一个源,那如何在应用中嵌入呢?这里有一些方法,例如,我们可以下载文本文件到**assets**或者**res/raw**目录,运行时再解析。但这里有一个更有效的方法-应用编译前解析数据和运行时使用已经处理好的数据。我们可以使用所提供的方法叫**getTldList()**,它返回最新的TLDS。就像Android的编译工具在每次编译时自动刷新**R**类一样。
2216

2317
##生成的代码长什么样?
24-
25-
In this example data is a list of strings so it can be represented as List<String>. Actually it is a set of strings since domains are unique, but it will be never modified and List interface gives a little bit more opportunities eg. indexing, so we will use a list. Finally we are going to make utility class with one method which should look like that:
2618

2719
例子中的数据是一个字符串的列表,所以它可以被表示为**List****<****String****>**。域是独一无二的,它永远不能被编辑并且[List](http://developer.android.com/reference/java/util/List.html)接口提供稍微多一点的内容,比如索引。我们用一个方法创建了一个实用性的类,就像下边这样:
2820

@@ -40,11 +32,7 @@ public final class TldList {
4032
```
4133

4234
##How to generate Java code automatically?
43-
##如何自动生成Java代码
44-
45-
To generate Java code we need to download source data and rewrite it embedding in Java source file. The latter can be done manually by just printing Java syntax elements to file. But more elegant way is to use dedicated library. In such case you don’t need to worry about braces, newlines and other syntactic elements and focus on the logic. One of the libraries for Java code generation is Codemodel which will be used in this example. With Codemodel code generation is straightforward. Our generator will be written in Groovy like Gradle itself and most of its plugins.
46-
47-
Here is complete code generator:
35+
##如何自动生成Java代码
4836

4937
生成Java代码需要下载源文件并重写到Java源文件中。后者可以打印java语法元素到文件中。但更好的方法是使用专用库。在此情况下你不必担心大括号,换行符和其他语法元素只需要关注逻辑。在这个例子中使用的生成java代码的库是[Codemodel](https://codemodel.java.net/)。使用Codemodel生成代码是简单的。我们用Groovy编写Gradle本身和大部分的插件。
5038

@@ -109,61 +97,35 @@ public class TldListGenerator {
10997
codeModel.build(outputDir)
11098
}
11199
}
112-
```
113-
Let’s explain key code parts. At the beginning we are loading javadocs from properties using ConfigSlurper. Properties are key-value pairs and are located in separate file so code is not mixed with data and all the javadoc text is available in one place similar to Android String resources. Codemodel API are self-explanatory, we call its methods just like we will write the code manually.
100+
```
114101

115-
让我们解释下关键代码部分。在开始的部分我们使用[ConfigSlurper](http://docs.groovy-lang.org/latest/html/gapi/groovy/util/ConfigSlurper.html)从属性中加载java文档。属性是一对key-value,和代码位于不同文件中,并没有被混淆在一起,所有的java文档都在一个地方就像好Android的String资源。Codemodel API就像我们平时写代码一样调用方法即可。
116-
117-
Input is read using ResourceGroovyMethods#eachLine() method, like the name suggests the closure (code fragment enclosed with braces) will be executed for each line read from the URL. Special variable it is a String with each line contents respectively. Lines beginning with hash are comments so we are putting them into javadoc. Other lines contain TLDs so we are passing them lowercased to the generated code. After all lines are processed the source is closed automatically, like in Java’s try-catch-finally or try-with-resources statements.
102+
让我们解释下关键代码部分。在开始的部分我们使用[ConfigSlurper](http://docs.groovy-lang.org/latest/html/gapi/groovy/util/ConfigSlurper.html)从属性中加载java文档。属性是一对key-value,和代码位于不同文件中,并没有被混淆在一起,所有的java文档都在一个地方就像好Android的String资源。Codemodel API就像我们平时写代码一样调用方法即可。
118103

119-
读取输入使用[ResourceGroovyMethods#eachLine()](http://docs.groovy-lang.org/latest/html/api/org/codehaus/groovy/runtime/ResourceGroovyMethods.html#eachLine)方法,从URL中读取每一行内容就好像闭包(代码片段用大括号包围)。特别的变量**it**是一个String内容,是每一行的内容。刚开始的行是注释,所以我们可以写入到java文档中。其他的行包含TLDs,所以我们转换为小写字母写入代码中。所有的行被处理过后,源会被自动关闭。像java中的try-catch-finally或try-with-resources声明。
120-
121-
It is important to specify English Locale explicitly while lowercasing TLDs since that operation is language-dependent. Without Locale specified default one from host invoking generator will be used. For example if that Locale is set to Turkish or Azeri then non-ASCII character (small dotless ı) will be produced as a result of lowercasing ASCII letter I and some of our generated TLDs will be invalid. See Internationalizing Turkish for more info. Finally we are creating output directory structure and saving generated Java file.
104+
读取输入使用[ResourceGroovyMethods#eachLine()](http://docs.groovy-lang.org/latest/html/api/org/codehaus/groovy/runtime/ResourceGroovyMethods.html#eachLine)方法,从URL中读取每一行内容就好像闭包(代码片段用大括号包围)。特别的变量**it**是一个String内容,是每一行的内容。刚开始的行是注释,所以我们可以写入到java文档中。其他的行包含TLDs,所以我们转换为小写字母写入代码中。所有的行被处理过后,源会被自动关闭。像java中的try-catch-finally或try-with-resources声明。
122105

123-
小写TLDs的操作设置为英语环境也是很重要的。如果没有则使用host提供的默认设置。例如:如果设置为土耳其或者阿塞拜疆,非ASII字符小写i会被当作一个小写的ASCII字符I,我们生成的一些TLDs就会无效。更多信息可以查看[Internationalizing Turkish](http://www.i18nguy.com/unicode/turkish-i18n.html).最终,我们创建了输出的目录结构并保存了生成的代码。
124-
125-
What about error handling, what will happen if there is no Internet connection and data cannot be downloaded? As you can see there is no catch statements nor throws clause. In Groovy they are not needed, all exceptions are treated like unchecked. If exception is thrown from our method it will simply cause Gradle build to fail and will be shown in Gradle Console and Messages windows in Android Studio.
106+
小写TLDs的操作设置为英语环境也是很重要的。如果没有则使用host提供的默认设置。例如:如果设置为土耳其或者阿塞拜疆,非ASII字符小写i会被当作一个小写的ASCII字符I,我们生成的一些TLDs就会无效。更多信息可以查看[Internationalizing Turkish](http://www.i18nguy.com/unicode/turkish-i18n.html).最终,我们创建了输出的目录结构并保存了生成的代码。
126107

127108
怎么处理错误,如果没有网络连接或者数据没有被下载会发生什么?如,你看到的没有**cactch**声明也无**throws**处理。在Groovy里我们是不需要的,所有的异常都是未被检查的被处理过。如果我们的代码抛出了异常,它仅仅引起Gradle编译失败,会在Android Studio的消息窗口和控制台中显示。
128-
129-
##Where to place code generator?
130-
##哪里存放生成的代码
131-
132-
Gradle gives us several opportunities to place our code generator eg.:
133109

134-
Gradle给我们提供了几个方式保存生成的代码,例如:
135-
136-
direct embedding into build.gradle of an app project
110+
##哪里存放生成的代码
111+
112+
Gradle给我们提供了几个方式保存生成的代码,例如:
137113

138114
1.直接嵌入到我们App项目的**build.gradle**文件中
139-
140-
separate file eg. generator.gradle and using apply from: 'generator.gradle' in build.gradle
141115

142116
2.分开的文件。例如:**generator.gradle**和在**build.gradle**文件中使用**apply from: 'generator.gradle'**
143-
144-
buildSrc project
145117

146118
3.[buildSrc project](https://docs.gradle.org/current/userguide/organizing_build_logic.html#sec:build_sources)
147-
148-
standalone project
149119

150120
4.[standalone project](https://docs.gradle.org/current/userguide/custom_plugins.html#N16FA7)
151-
152-
First two options gives us the least flexibility eg. our code generator cannot be easily tested and (especially in the first case) code generation logic is mixed with build configuration causing poor readability. The last two options differs mainly by the manner how plugin will be applied to the app project. Standalone project is useful when plugin will be used in many projects and requires a repository or at least copying a JAR file. In this example we are going to use our generator in single application project and we will place it in buildSrc project.
153121

154122
头两个选项没有一点灵活性。例如:我们生成的代码不能很简单的测试,(特别是第一个)生成的代码与编译配置选项容易混淆让人难以读懂。剩余两项的关键不同是在应用中如何配置。独立的项目在当很多项目被配置使用、需要一个仓库或至少拷贝一个JAR文件时是有用处的。在这个例子中我们将在一个单独的项目中使用我们生成好的代码并且我们将它放在**buildSrc**项目中
155-
156-
##What is buildSrc project?
157-
##**buildSrc** project是什么?
158-
159-
When directory called buildSrc is found in project’s root directory by Gradle it is treated in a special way. Subproject buildSrc (and submodule in Android Studio/IntelliJ) is created automatically (no need to declare it in settings.gradle). Moreover even build.gradle for that project is not needed since default one is applied implicitly. See Organizing Build Logic in Gradle documentation for more information. That project is added to the classpath of buildsript so it contents will be available in build.gradle files in the same way as classpath dependencies eg. classpath 'com.android.tools.build:gradle:1.2.3'. buildSrc project like normal ones can contain unit tests and resources. Its tests are executed on each Gradle invocation on any of other projects.
123+
124+
##**buildSrc** project是什么?
160125

161126
Gradle特别处理过的名字为**buildSrc**的目录在项目的根目录时。子项目**buildSrc**(和Android Studio或IntelliJ的子module)被自动创建(不需要在**settings.gradle**中声明)。甚至项目的**build.gradle**都不是必须的因为默认的已经被隐式实现。在Gradle文档中查看[Organizing Build Logic](https://docs.gradle.org/current/userguide/organizing_build_logic.html#sec:build_sources)更多信息。这个项目被加入到了编译脚本的classpath中,所以它的内容可以在build.gradle中用同样的方式使用作为classpath的依赖。例如:**classpath com.android.tools.build:gradle:1.2.3'****buildSrc**项目一样也有单元测试和资源。可以被任一项目的Gradle执行。
162-
163-
##How to use our generator?
164-
##如何使用生成的代码?
165-
166-
We need to invoke our generateTldListClass() method somewhere to see any results. We can create a complete Gradle plugin but for this simple purpose we can just add a custom task to build.gradle file in app project. Sample implementation can look like this:
127+
128+
##如何使用生成的代码?
167129

168130
我们需要调用我们的**generateTldListClass()**方法。我们能创建一个完整的Gradle插件,但对于这个简单的目的,我们在App项目中的**build.gradle**文件中添加一个自定义的任务。例子实现如下:
169131

@@ -191,19 +153,12 @@ android {
191153
<rest of the android closure>
192154
}
193155
```
194-
195-
Let’s analyze a buildscript. By default files produced during Gradle builds are placed in build directory located in the project’s root directory. It can be retrieved as buildDir in build.gradle. So we are building path to our output directory upon it.
196156

197-
让我们分析这个编译脚本。Gradle编译时产生的文件默认会被放在项目根目录下的**build**目录。在**build.gradle**中会被作为**buildDir**被检索到。如此,我们构建了输出目录。
198-
199-
We are also creating custom task called generateTldList. Note that << is a shortcut to define an action. Look at Gradle tasks documentation for more info about tasks. Next we are adding our task as a dependency of preBuild task from Android Gradle Plugin which is executed at the beginning of each project build. Finally we are appending our output directory to the main source set, so code from it will be accessible from app source code.
157+
让我们分析这个编译脚本。Gradle编译时产生的文件默认会被放在项目根目录下的**build**目录。在**build.gradle**中会被作为**buildDir**被检索到。如此,我们构建了输出目录。
200158

201159
我们也可以创建自定义任务叫**generateTldList**.注意**<<**是一个定义为行为的快捷方式。关于任务的更多信息看[Gradle tasks documentation](https://docs.gradle.org/current/userguide/more_about_tasks.html)。在Android Gradle插件里我们继续添加我们的任务作为**preBuild**任务的一个依赖,当每一个项目编译刚开始时被执行。最终我们把输出目录加入到了main source中,在app源码中可以被引入。
202-
203-
##How to use generated code?
160+
204161
##如何使用生成的代码
205-
206-
We can use our generated class like any other class. Example below shows how to create a simple Spinner containing TLDs:
207162

208163
我们可以像使用其他类一样使用我们生成的类。下边的例子展示如何创建一个包含TLDS的简单Spinner:
209164

@@ -213,12 +168,9 @@ spinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_dropdown_ite
213168
```
214169

215170
##Sample project
216-
##例子
217-
218-
Sample project with code generator and simple Android app can be found on Github: koral–/buildsrc-sample. Note that editor in Android Studio (version 1.3) is complaining about Class 'TldListGenerator' already exists in 'pl.droidsonroids.domainnameutils' however that is bogus error and does not prevent project from building. Running app looks like that:
219-
Screenshot
171+
##例子
220172

221-
生成代码的例子和简单的Android项目可以在Github上看到:[koral–/buildsrc-sample](https://github.com/koral--/buildsrc-sample)。Android Studio 1.3中会提示**在'pl.droidsonroids.domainnameutils'已经存在的Class 'TldListGenerator'**是但是它并不会影响项目的构建。运行App看起来像下边这样
173+
生成代码的例子和简单的Android项目可以在Github上看到:[koral–/buildsrc-sample](https://github.com/koral--/buildsrc-sample)。Android Studio 1.3中会提示**Class 'TldListGenerator' already exists in 'pl.droidsonroids.domainnameutils'**是但是它并不会影响项目的构建。运行App看起来像下边这样
222174

223175
![Screenshot](https://cloud.githubusercontent.com/assets/3340954/9006569/24f8743a-3789-11e5-9ccf-b36bce782894.png)
224176

0 commit comments

Comments
 (0)