This page looks best with JavaScript enabled

使用Android Studio Code 模板提高编码效率

 ·  ☕ 8 min read

在工作中最近的一个业务上,每次新加一个同一类的功能时,发现会创建许多类似的类,写类似的代码。就思考能否通过模板的方式提高新建同类功能的效率,于是通过搜索发现 IDE 支持的 FreeMaker.

遇见模板

在 Android Studio 中其实已经预置了一些常用模板,比如:

  • 创建 Activity 的多文件模板: Basic Activity 模板
  • 创建单例类的单文件模板: Singleton

单文件模板

通过打开 Android Stuidio -> File -> New -> Edit File Templates 窗口,就能查看预置的单文件模板,也能添加自己的单文件模板。

例如,添加一个 Kotlin data class 模板:

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}

#end
#parse("File Header.java")
data class ${NAME} {
}

什么是多文件模板

比如 Android Studio 的 Basic Activity 模板:

BasicActivity

多文件模板是可自定义的。 每个模板都公开了几个选项(称为参数),允许开发人员自定义生成的代码。使用模板的最常见工作流程如下:

  1. 选择一个模板。
  2. 填充模板选项(参数)。
  3. 预览然后执行项目的添加/更改。

其对应的文件结构为:

├── globals.xml.ftl
├── recipe.xml.ftl
├── recipe_fragment.xml.ftl
├── root
│   ├── res
│   │   └── layout
│   │       ├── activity_fragment_container.xml.ftl
│   │       └── fragment_simple.xml.ftl
│   └── src
│       └── app_package
│           ├── SimpleActivity.java.ftl
│           ├── SimpleActivity.kt.ftl
│           ├── SimpleActivityFragment.java.ftl
│           └── SimpleActivityFragment.kt.ftl
├── template.xml
├── template_basic_activity.png
└── template_basic_activity_fragment.png

FreeMaker

模板使用 FreeMarker,一个 Java 模板引擎,用于启用文件中的控制流和变量替换等内容。 它类似于PHP,Django模板等。按照惯例,要由 FreeMarker 处理的模板目录结构中的任何文件都应具有 .ftl 文件扩展名。 因此,如果您的一个源文件是 MyActivity.java,并且它包含 FreeMarker 指令,则应将其命名为 MyActivity.java.ftl 。

有关 FreeMarker 的更多文档,请参阅文档。 特别是关于字符串操作的引用。https://freemarker.apache.org/docs/index.html

目录结构

模板是包含许多 XML 和 FreeMarker 文件的目录。 有两个必需文件是 template.xml和recipe.xml.ftl。模板源文件(PNG 文件,模板化 Java 和 XML 文件等)属于 root/ 的子目录。 下面是模板的示例目录结构:

MyTemplate - 根目录
├── globals.xml.ftl - 可选的全局变量
├── recipe.xml.ftl - 说明/脚本(要复制的文件等)
├── root - 源文件(与输出项目一起处理/复制/合并)
│   ├── res
│   │   └── layout
│   │       └── fragment_bell.xml.ftl
│   └── src
│       └── app_package
│           ├── BellActivityAnswerData.kt.ftl
│           ├── BellActivityData.kt.ftl
│           ├── BellActivityScoreData.kt.ftl
│           ├── BellFragment.kt.ftl
│           ├── Logger.kt.ftl
│           ├── PracticeProcess.kt.ftl
│           ├── PresentProcess.kt.ftl
│           └── ResultProcess.kt.ftl
└── template.xml - 元数据(描述,参数等)

template.xml

每个模板目录必须包含 template.xml 文件。 此 XML 文件包含有关模板的元数据,包括 IDE 将作为用户选项显示的名称,描述,类别和用户可见参数。 XML 文件还指示配方 XML 文件的名称(由 FreeMarker 处理),以及全局变量 XML 文件,如果除了模板参数值之外还有全局变量,应该对所有FreeMarker 处理的文件(.ftl文件都可见))。

例如 Android Studio 提供的 Basic Activity 模板:

 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
<?xml version="1.0"?>
<template
        format="5"
        revision="6"
        name="Basic Activity"
        minApi="9"
        minBuildApi="14"
        description="Creates a new basic activity with an app bar.">

    <category value="Activity"/>
    <formfactor value="Mobile"/>

    <parameter
            id="activityClass"
            name="Activity Name"
            type="string"
            constraints="class|unique|nonempty"
            suggest="${layoutToActivity(layoutName)}"
            default="MainActivity"
            help="The name of the activity class to create"/>

    <parameter
            id="layoutName"
            name="Layout Name"
            type="string"
            constraints="layout|unique|nonempty"
            suggest="${activityToLayout(activityClass)}"
            default="activity_main"
            help="The name of the layout to create for the activity"/>

    <globals file="globals.xml.ftl"/>
    <execute file="recipe.xml.ftl"/>

</template>

参数说明

  • <template>
    模板的根节点

    • format 此模板遵循的模板格式版本
    • revision 可选的。 此模板的版本(您可以在更新模板时递增),作为整数。
    • name 模板的显示名称
    • description 模板的描述。
    • minApi 可选的。 此模板所需的最低API级别。 在实例化模板之前,IDE将确保目标项目的minSdkVersion不低于此值。
    • minBuildApi 可选的。 此模板所需的最小构建目标API级别。 在实例化模板之前,IDE将确保目标 buildVersion 是大于或等于此值的API级别。
  • <dependency>
    表示模板要求目标项目中存在给定库。 如果不存在,IDE将向项目添加依赖项。

    • name 库的名称
    • revision 此模板所需的库的最低版本。
  • <category> 模板类型。 此元素是可选的。

    • value 模板类型。可以自己定义。
  • <parameter>
    定义用户可自定义的模板参数。

    • id 表示此变量的标识符在 FreeMarker 文件中作为全局变量提供。 如果标识符为 foo,则参数值将在 FreeMarker 文件中以 ${foo} 的形式提供。
    • name 模板参数的显示名称。
    • type 参数的数据类型。 string boolean enum separator(分隔符)
    • constraints 可选的。 强加于参数值的约束。 可以使用 | 组合约束。 有效的约束类型是:
      • nonempty 该值不能为空
      • apilevel 该值应表示数字 API 级别
      • package 该值应表示有效的 Java 包名称
      • class 该值应表示有效的Java类名称
      • activity 该值应表示完全限定的 Activity 类名称
      • layout 该值应表示有效的 xml 布局名称
      • drawable 该值应表示有效的 Drawable 名称
      • string 该值应表示有效的字符串资源名称
      • id 该值应表示有效的id资源名称
      • unique 该值必须是唯一的; 此约束仅在指定其他约束时才有意义,例如布局,这意味着该值不能表示现有的布局资源名称
      • exists 该值必须已经存在; 此约束仅在指定其他约束时才有意义,例如布局,这意味着该值应该是现有的布局资源名称
    • suggest 可选的。 表示自动建议参数值。
    • default 可选的。 此参数的默认值。
    • help 用于为此参数显示给用户的帮助字符串。
    • <option> 对于 enum 类型的参数,表示可以选择的值。
      • id 如果选择此选项,则设置的参数值。
      • minApi 可选的。 选择此选项时所需的最低API级别。 在实例化模板之前,IDE 将确保目标项目的 minSdkVersion 不低于此值。
      • [text] option 在 IDE 下拉框的显示内容。
    • <thumb> 表示模板的缩略图。 <thumb> 元素应包含在 <thumbs> 元素中。 此元素的文本内容表示缩略图的路径。 如果此元素具有任何属性,则它们将被视为参数值的选择器。 例如,如果有两个缩略图:
1
2
3
4
<thumbs>
  <thumb>template.png</thumb>
  <thumb navType="tabs">template_tabs.png</thumb>
</thumbs>
如果 `navType` 模板参数的值为 `tabs` ,则模板的缩略图将显示 template_tabs.png,否则将显示template.png。

globals.xml

可选的全局变量 XML 文件包含全局变量定义。 在 globals.xml 中定义的变量,能在所有 .ftl 文件中使用。

例如:

1
2
3
4
5
6
<globals>
    <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
    <!-- 将 activityName 转为小写,储存在 activityNameLower -->
    <global id="activityNameLower" value="${activityName?lower_case}" />
    <global id="activityClass" value="${activityName}Activity" />
</globals>

recipe.xml.ftl

配置 XML 文件,包含从此模板生成代码时应执行的各个指令。

例如,您可以复制某些文件或目录(copy),通过 FreeMarker 从源文件模板生成代码(instantiate),并在生成代码后打开文件(open)。

例子:

 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
<recipe>
    <!-- runs FreeMarker, then copies from [template-directory]/root/ to [output-directory]. -->
    <instantiate from="AndroidManifest.xml.ftl" />
    <!-- automatically creates directories as needed -->
    <copy from="res/drawable-hdpi" />
    <copy from="res/drawable-mdpi" />
    <copy from="res/drawable-xhdpi" />
    <copy from="res/values/dimens.xml" />
    <copy from="res/values/styles.xml" />
    <copy from="res/values-large/dimens.xml" />
    <copy from="res/menu/main.xml" to="res/menu/${activityNameLower}.xml" />
    <instantiate from="res/values/strings.xml.ftl" />
    <!-- Decide which layout to add -->
    <#if navType?contains("pager")>
        <instantiate from="res/layout/activity_pager.xml.ftl" to="res/layout/activity_${activityNameLower}.xml" />
    <#elseif navType == "tabs" || navType == "dropdown">
        <copy from="res/layout/activity_fragment_container.xml" to="res/layout/activity_${activityNameLower}.xml" />
    <#else>
        <copy from="res/layout/activity_simple.xml" to="res/layout/activity_${activityNameLower}.xml" />
    </#if>
    <!-- Decide which activity code to add -->
    <#if navType == "none">
        <instantiate from="src/app_package/SimpleActivity.java.ftl" to="${srcOut}/${activityClass}.java" />
    <#elseif navType == "pager">
        <instantiate from="src/app_package/PagerActivity.java.ftl" to="${srcOut}/${activityClass}.java" />
    <#elseif navType == "tabs">
        <instantiate from="src/app_package/TabsActivity.java.ftl" to="${srcOut}/${activityClass}.java" />
    <#elseif navType == "dropdown">
        <instantiate from="src/app_package/DropdownActivity.java.ftl" to="${srcOut}/${activityClass}.java" />
    </#if>
    <!-- open the layout file when done -->
    <open file="res/layout/${activityNameLower}.xml" />
</recipe>

命令说明

  • <copy>

唯一必需的参数是 from,它指定要在根目录下复制的源文件的位置。 如果需要,将自动创建所有必需的祖先目录。

默认目标位置是输出目录根目录下的相同路径(即目标项目的位置)。 如果提供了可选的to参数,则指定输出目录。 请注意,如果from路径以 .ftl 结尾,它将自动被剥离。 例如 <instantiate from =“res / values / strings.xml.ftl”/> 就足够了; 这将创建一个名为 strings.xml 的文件,而不是 strings.xml.ftl

此参数以递归方式工作,因此如果 from 是目录,则以递归方式复制该目录。

  • <instantiate>

<copy> 相同,但每个源文件首先会通过 FreeMarker 模板解析。

  • <merge>

该指令将用于将源文件的内容合并到项目中的现有文件中。 最常见的用例是将组件添加到目标项目的 AndroidManifest.xml 文件中,或将诸如字符串之类的资源合并到现有的 strings.xml 文件中。

  • <open>

在代码生成完成后,在 IDE 中打开由 file 参数指定的文件。

root/

实际的模板文件(资源,Java源代码,AndroidManifest 更改)应该放在 root/ 目录中。

一个区别是,不是将源文件放在 src/com/google/... 中,您可以使用命令约定,如 src/app_package/,表示此目录下的文件将放在目标项目的源文件包根目录中。

额外的模板函数

除了标准的内置 FreeMarker 函数之外,FreeMarker 表达式和文件还有几个函数可用。

string activityToLayout(string)

将类似 Activity 类的标识符字符串(如 FooActivity )转换为对应的资源友好标识符字符串,例如 activity_foo

  • 参数 activityClass

Activity 类名称,例如 FooActivity

string camelCaseToUnderscore(string)

将驼峰大小写标识符字符串(如 FooBar)转换为其对应的下划线分隔标识符字符串,例如 foo_bar

string escapeXmlAttribute(string)

转义字符串,例如 Android's,以便它可以用作XML属性值:Android&apos;s。 将把 ', ", <& 转义为对应的 XML 值。

string escapeXmlText(string)

转义字符串,例如 A & B's,以便它可以用作 XML 文本。 将转义 <>,但与 escapeXmlAttribute 不同,它不会转义 ''

string escapeXmlString(string)

转义字符串,例如 A & B's,以便它适合作为 XML 文本插入到字符串资源文件,例如 A &amp; B\S。 除了转义 < 之类的XML字符外,它还执行其他 Android 特定的转义,例如使用反斜杠转义撇号(' -> /),等等。

string extractLetters(string)

从字符串中提取所有字母,删除任何标点符号和空白字符。

string classToResource(string)

将 Android类名称(如FooActivityFooFragment)转换为对应的资源友好的标识符字符串(如foo),剥离 ActivityFragment 后缀。 会剥离的后缀:

  • Activity
  • Fragment
  • Provider
  • Service

string layoutToActivity(string)

将资源友好的标识符字符串(例如activity_foo)转换为对应的Java类友好标识符字符串(例如FooActivity)。

string slashedPackageName(string)

将完整 Java包名称转换为其对应的目录路径。 例如,如果给定的参数是 com.example.foo,则返回值将为 com/example/foo

string underscoreToCamelCase(string)

将下划线分隔的字符串(如foo_bar)转换为其对应的驼峰字符串(如FooBar)。

参考:

Support the author with
alipay QR Code
wechat QR Code

Yang
WRITTEN BY
Yang
Developer