前言
在开发 Android 项目的过程中,难免会遇到重复编写同一段逻辑的代码的情况,就拿目前比较流行的 MVP 模式来举例好了,要实现一个页面的 MVP 开发,我们需要编写以下的类:
- 一个 MVP 的契约接口,里面有一个
view
层接口 和P
层的接口(或抽象类) - 对应的
view
层接口的实现类 - 对应的
P
层接口的实现类 - 对应的
model
层
如果当前页面需要使用 RecyclerView
,那么还得去写对应的 Adapter
和 viewHolder
等你全部弄完后,估计你的大脑都快进入 CD 阶段了,效率一点都不搞高效
因此,我们很有必要去使用一些自动生成代码的技巧,比方说 Android Studio Template
Android Studio Template
当我们通过 Android Studio 创建一个新的 Actvity
时可以看到下面的图片:
只要点击下去,就能很轻松生成一个全新的 Actvity
,这就是 Android Studio Template 的作用
Android Studio Template 依靠 FreeMarker 引擎,将事先定义好的模板文件生成我们所需的 class 文件、layout 文件等等,可以极大减少样板式代码的编写,帮助我们节省大量的时间
先去到 Android Studio\plugins\android\lib\templates 下面,这里就是模板的存放位置 ,可以看到这里其他默认的模板:
这里以相对简单典型的 EmptyActivity
为例进行讲解
用编译器打开,可以看到下面的东西:
下面我们来一个一个来看,这里先将 kt(SimpleActivity.kt.ftl) 的模板忽略掉,先看 SimpleActivity.java.ftl
里面的内容:
package ${packageName};
........
........
........
public class ${activityClass} extends ${superClass} {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
........
setContentView(R.layout.${layoutName});
........
........
}
........
}
将全部的影响因素都去掉了,这里就很好懂了,这个就是 Activity
代码生成模板,想必这里时候都注意到了 ${activityClass} 等变量了,这些都是从外部获取值的标量,那么这个外部是哪里呢?
其实就是 template.xml
<?xml version="1.0"?>
<template
format="5"
revision="5"
//模板的名字,不可重复(注释在开发模板时需删除)
name="Empty Activity"
minApi="9"
minBuildApi="14"
//模板的提示语(注释在开发模板时需删除)
description="Creates a new empty activity">
//category 是模板类型(注释在开发模板时需删除)
//不写的话,无法出现了上面图一右键菜单里面(注释在开发模板时需删除)
<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="generateLayout"
name="Generate Layout File"
type="boolean"
default="true"
help="If true, a layout file will be generated" />
<parameter
id="layoutName"
name="Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="${activityToLayout(activityClass)}"
default="activity_main"
visibility="generateLayout"
help="The name of the layout to create for the activity" />
...........
<parameter
id="packageName"
name="Package name"
type="string"
constraints="package"
default="com.mycompany.myapp" />
<!-- 128x128 thumbnails relative to template.xml -->
<thumbs>
<!-- default thumbnail is required -->
<thumb>template_blank_activity.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>
这里也是只保留一些相对熟悉的内容,看到上面的 ${activityClass} 等字段,是不是就和一开始的 SimpleActivity.java.ftl
里面的内容链接上了呢
现在,先将上面的一些重要节点梳理一遍吧:
category
这个是模板的分类指向,如下图:
如果需要新增分类的话,category
也是需要修改的
thumb
这个是模板编译的封面图
parameter
这里设置在创建模板时可修改的参数变量
该节点有以下参数:
-
id:变量名, 变量的唯一标识,不可重复
-
name: 在创建模板时展示的变量名
-
type :类型,有 string,boolean,enum 等,经常用到的也就 string 和 boolean
-
constraints:变量约束, 常见的有 class,代表类名;layout 代表布局名;package 代表包路径; unique 则是不能与现有的重复;nonemptye 表示不能为空,一般来说这里直接 copy 原有的代码即可
-
suggest: 推荐值,未修改变量时根据其他变量生成,这里到了一些函数,比如在 layoutName 里有
suggest="${activityToLayout(activityClass)}"
这里的 activityToLayout(activityClass) 的作用是 activityClass的名字转为规范的 layout 名字,比如参数MainActivity 就会返回 activity_main
除了 activityToLayout 这个函数,还有下面的这些函数:- classToResource(String):转为小写并删除资源提示的字符串,比方说 HomeFragment 就会转换为 home
- layoutToActivity(string) :activityToLayout 的反向操作
-
default:默认值
-
help:当编辑一个变量时,显示的提示语
globals 和 execute
execute:执行 recipe.xml.ftl
文件的内容,将模板文件生成具体的可用文件,一般不用修改
globals :执行 global.xml.ftl
文件的内容,一般不用修改
这两个都是外部文件,相信这时聪明的读者肯定已经知道这个 template.xml
的作用了
这时再看下 global.xml.ftl
<?xml version="1.0"?>
<globals>
<global id="hasNoActionBar" type="boolean" value="false" />
<global id="parentActivityClass" value="" />
<global id="simpleLayoutName" value="${layoutName}" />
<global id="excludeMenu" type="boolean" value="true" />
<global id="generateActivityTitle" type="boolean" value="false" />
<#include "../common/common_globals.xml.ftl" />
</globals>
整个文件很简单,用 global 标签定义了一系列的全局参数,供其他的模板文件使用
<#include> 标签的作用,这个很好猜测,有兴趣可以打开对应的文件看下,这里就不多说了
接着看 recipe.xml.ftl
:
<?xml version="1.0"?>
.......
<recipe>
<#include "../common/recipe_manifest.xml.ftl" />
.......
<#include "../common/recipe_simple.xml.ftl" />
<open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
</#if>
.......
<instantiate from="root/src/app_package/SimpleActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
</recipe>
这里也是只保留一些核心内容
<#include> 标签那行表示包含了 recipe_manifest.xml.ftl 文件的内容,打开对应文件后内容如下:
<recipe folder="root://activities/common">
<#if requireTheme!false>
<#include "recipe_theme.xml.ftl" />
</#if>
<merge from="root/AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<merge from="root/res/values/manifest_strings.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
</recipe>
这里使用了 merge 标签,其作用就是将定义的 AndroidManifest.xml.ftl 里面的代码注入到项目中的 AndroidManifest.xml 中,比方说我们新生成的 activity 类在 AndroidManifest.xml 中自动添加注册代码
instantiate 是一个较为关键的标签 ,它的作用是以指定的 .ftl
模板文件,生成对应的内容的文件,不过需要指定文件名,比方说 SimpleActivity.java.ftl
为模板,依据里面的内容生成一个新的 HomeActivity.java 文件
open 标签这个就很好理解了,就是打开我们刚才生成的文件
好了,知道了以上的内容后,就开始制作一个自己的模板吧
定制模板
现在是动手的时间了,目标是自动生成下面的代码:
第一步、找到需要生成的文件并整理模板文件
为了方便,这里可以直接复制 EmptyActivity
文件,在 templates 下面新建个文件夹和重命名 EmptyActivity
文件夹:
要制作模板,优先要确定需要模板帮助我们生成什么代码文件,目前我们需要就是上面的 4 个文件,那么就创建 4个对应的 .ftl
文件,首先是 3 个 Java 文件的模板:
然后再新建几个文件,这里就是 xml 文件的模板了:
第二步、整理模板文件需要的变量
这里以代码量最多的 SimpleActivity.java.ftl
为例子,其他的请看结尾的文件
package ${packageName};
import android.view.View;
public class ${activityName} extends BaseActivity<${presenterName}>
implements ${contractName}.${contract}View, View.OnClickListener{
@Override
public int getLayoutId() {
return R.layout.${activityLayoutName};
}
@Override
public void initView() {
}
@Override
public void initData() {
}
@Override
@SingleClick
public void onClick(View v) {
switch (v.getId()) {
default:
break;
}
}
}
上面的代码参数什么的,已经解析过一遍了,就不多说了
第三步、添加清单文件的处理
这里直接使用下面代码即可,不过文件存放位置要正确:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name="${packageName}.${activityName}"/>
</application>
</manifest>
第四步、配置好 template.xml
现在已经有了代码模板了,就要设置变量了,这里就直接贴代码了:
<?xml version="1.0"?>
<template
format="5"
revision="5"
name="Mvp Empty Activity"
minApi="9"
minBuildApi="14"
description="项目中的 Mvp 结构的Base Activity 模板">
//category 是模板类型(注释在开发模板时需删除)
//不写的话,无法出现了上面图一右键菜单里面(注释在开发模板时需删除)
<category value="Mvp" />
<formfactor value="Mobile" />
<parameter
id="contract"
name="Mvp Name"
type="string"
constraints="class|unique|nonempty"
default="Main"
help="即将创建模块的名字" />
<parameter
id="activityName"
name="Activity Name"
type="string"
constraints="class|unique|nonempty"
suggest="${contract}Activity"
default="MainActivity"
help="即将创建 Activity " />
<parameter
id="contractName"
name="Contract Name"
type="string"
constraints="class|unique|nonempty"
suggest="${contract}Contract"
default="MainContract"
help="即将创建的 MVP 契约类" />
<parameter
id="presenterName"
name="Presenter Name"
type="string"
constraints="class|unique|nonempty"
suggest="${contract}Presenter"
default="MainPresenter"
help="即将创建的 MVP 的 P层" />
<parameter
id="activityLayoutName"
name="Activity Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="${activityToLayout(activityName)}"
default="activity_main"
help="即将创建 Activity xml 的名字" />
<parameter
id="packageName"
name="Package name"
type="string"
constraints="package"
default="com.mycompany.myapp" />
<!-- 128x128 thumbnails relative to template.xml -->
<thumbs>
<!-- default thumbnail is required -->
<thumb>template_blank_activity.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>
第五步、globals.xml.ftl 和 recipe.xml.fl
globals.xml.fl
保存原样即可,但是部分文件路径需要修改一下:
<?xml version="1.0"?>
<globals>
............
<#include "../../activities/common/common_globals.xml.ftl" />
</globals>
是的,就是 common
的路径需要修改一下,当然你也可以将它删除了,如果你不需要用到它的话
再来看看 recipe.xml.fl
文件
<?xml version="1.0"?>
<recipe>
<merge from="root/AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<instantiate from="root/src/app_package/SimpleActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityName}.java" />
<instantiate from="root/src/app_package/Contract.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${contractName}.java" />
<instantiate from="root/src/app_package/Presenter.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${presenterName}.java" />
<instantiate from="root/res/activity.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${activityLayoutName}.xml" />
<open file="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<open file="${escapeXmlAttribute(srcOut)}/${activityName}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${contractName}.java" />
<open file="${escapeXmlAttribute(srcOut)}/${presenterName}.java" />
<open file="${escapeXmlAttribute(resOut)}/layout/${activityLayoutName}.xml" />
</recipe>
这里是自己去配置注入到清单文件的代码了,这样子足够简单,可以避免一些运行错误
说到运行错误,这里提一下,如果执行模板的代码出现问题,Android Studio 可以查看到具体的原因,大大减轻了编写模板的难度
测试模板
重启 Android Studio,让模板生效,这时候就可以测试我们刚才的模板了:
我的模板下载地址为:https://download.csdn.net/download/f409031mn/10871230
结语
模板看着很简单,不过真正要写出适合自己的模板,还是要花点时间的
但是在掌握它之后,你就会对它爱不释手了