You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: androidweekly/使用buildSrc Gradle项目和Codemodel生成java代码/readme.md
+18-66Lines changed: 18 additions & 66 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,20 +9,12 @@
9
9
> * 状态 : 校对中
10
10
11
11
##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.
[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.
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**类一样。
22
16
23
17
##生成的代码长什么样?
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:
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.
@@ -109,61 +97,35 @@ public class TldListGenerator {
109
97
codeModel.build(outputDir)
110
98
}
111
99
}
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.
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.
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.
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.
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.
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.
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:
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.
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.
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
+
##例子
220
172
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看起来像下边这样
0 commit comments