Android Studioのテンプレートってどうなってんの?

android, android studio

sample-activity

前回でAndroid Studioのモジュールテンプレートを作りましたが、同じようなことを考える人がきっといるだろうということで、テンプレートの中身の解説を書くことにしました。
ドキュメントなどあるはずもないので、大部分が挙動からの推測であることは注意です。

構成

テンプレートが置いてある場所はMacでは/Applications/Android Studio.app/Contents/plugins/android/lib/templates/らへんです。
他の環境ではわかりませんが、、linuxならfind -type d templates$とかで何かひっかかるのではないでしょうか。

ではテンプレートの構造を把握するためにシンプルなテンプレートであるactivities/BlankActivityを見て行きましょう。
BlankActivityはactivityのテンプレート選択時にBlankActivityを選ぶと実行されるテンプレートです。
BlankActivityの構造は以下です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
├── globals.xml.ftl
├── recipe.xml.ftl
├── root
│   ├── AndroidManifest.xml.ftl
│   ├── res
│   │   ├── layout
│   │   │   └── activity_simple.xml.ftl
│   │   ├── menu
│   │   │   └── main.xml.ftl
│   │   ├── values
│   │   │   ├── dimens.xml.ftl
│   │   │   └── strings.xml.ftl
│   │   └── values-w820dp
│   │       └── dimens.xml
│   └── src
│       └── app_package
│           └── SimpleActivity.java.ftl
├── template.xml
└── template_blank_activity.png

重要なファイルはtemplate.xmlrecipe.xml.ftlです。
まずはtemplate.xmlを見て行きましょう。

template.xml

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?xml version="1.0"?>
<template
    format="3"
    revision="4"
    name="Blank Activity"
    minApi="7"
    minBuildApi="14"
    description="Creates a new blank activity with an action 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" />

    <parameter
        id="activityTitle"
        name="Title"
        type="string"
        constraints="nonempty"
        default="MainActivity"
        suggest="${activityClass}"
        help="The name of the activity. For launcher activities, the application title." />

    <parameter
        id="menuName"
        name="Menu Resource Name"
        type="string"
        constraints="layout|unique|nonempty"
        suggest="menu_${classToResource(activityClass)}"
        default="menu_main"
        help="The name of the resource file to create for the menu items" />

    <parameter
        id="isLauncher"
        name="Launcher Activity"
        type="boolean"
        default="false"
        help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />

    <parameter
        id="parentActivityClass"
        name="Hierarchical Parent"
        type="string"
        constraints="activity|exists|empty"
        default=""
        help="The hierarchical parent activity, used to provide a default implementation for the 'Up' button" />

    <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>

いろいろ定義されていますが、これがどう影響してくるのでしょうか。

いろいろ試した結果、parameterで定義されているのはActivityのテンプレートを選んだ次の画面で出てくる入力項目の定義のようです。
試しにバックアップを取った後、

1
2
3
4
5
6
<parameter
        id="testParam"
        name="Test param"
        type="string"
        constraints="nonempty"
        default="testvalue" />

を追加してAndroid Studioを再起動し、BlankActivityを選んでみましょう。
追加した項目が現れるはずです。

sample-input

しかしここで定義されているparameterが全て出てくるわけではありません。
推測ですが、テンプレートは単独で実行されるわけではないようで、このテンプレートに到達するまでに決定してる値は出てこない、とかそういうものと考えています。 parameterはチェックボックスやドロップダウン、ファイル参照なども定義できるようで、いろんなテンプレートを触ってそれの名前を元にtemplates以下を探したり、grepしたりして既にあるテンプレートを参考に定義方法を知ることができます。

ということで、parameterを定義することでユーザからの入力を受け付けることができますね。

categoryについてはコンテキストメニューのどこに入るか、に関わってきます。 ActivityにしておけばActivity選択画面で選択できるようになります。

thumbsはテンプレートウィザード画面のサムネイルです。

globalsは定数をまとめたファイルを指定しています。

executeで指定しているファイルがユーザ入力から入手した値を元に実際にテンプレート生成を行うファイルで最も重要なファイルと言えます。

適当にActivityカテゴリに追加してみたのが冒頭にも載せていたこちらです。見慣れたActivity選択画面の中に見慣れない物体が混ざってますね

sample-activity

ftl?

ところでxmlの後ろについているftlとはなんでしょうか?
これも推測ですが、テンプレートエンジンの対象となるファイルのようで、${}で値を参照できたり、<#if></#if>という素敵な文法で制御構文を書くことができます。

globals.xml.ftl

このファイルでは前述の通り定数が定義されています。

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0"?>
<globals>
    <global id="manifestOut" value="${manifestDir}" />
    <global id="appCompat" type="boolean" value="${(hasDependency('com.android.support:appcompat-v7'))?string}" />
    <!-- e.g. getSupportActionBar vs. getActionBar -->
    <global id="Support" value="${(hasDependency('com.android.support:appcompat-v7'))?string('Support','')}" />
    <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
    <global id="resOut" value="${resDir}" />
    <global id="relativePackage" value="<#if relativePackage?has_content>${relativePackage}<#else>${packageName}</#if>" />
</globals>

よく使うのはsrcOutでしょうか。 srcOutmoduleディレクトリ/package名になっていて、Javaを出力するときに役に立ちます。
必要な値があればここで定義しましょう。

recipe.xml.ftl

さて、このファイルが実際の実行を定義しているファイルです。

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
<?xml version="1.0"?>
<recipe>

    <merge from="AndroidManifest.xml.ftl"
             to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />

    <instantiate from="res/menu/main.xml.ftl"
            to="${escapeXmlAttribute(resOut)}/menu/${menuName}.xml" />

    <merge from="res/values/strings.xml.ftl"
             to="${escapeXmlAttribute(resOut)}/values/strings.xml" />

    <merge from="res/values/dimens.xml.ftl"
             to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
    <merge from="res/values-w820dp/dimens.xml"
             to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />

    <instantiate from="res/layout/activity_simple.xml.ftl"
                   to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />

    <instantiate from="src/app_package/SimpleActivity.java.ftl"
                   to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />

    <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
    <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
</recipe>

目に入るのはmergeinstantiateopenです。 mergeは既にあるファイルに文字通りマージすることができます、が、現在はxmlとgradleにのみ対応しているようです。
xmlは一度パースしているのでしょうが、gradleはASTまでやってたりするんですかねー。

instantiateは新しくファイルを作ります。

mergeinstantiatefromからtoへファイルを解釈した結果を生成します。 fromで指定するファイルはrootディレクトリをルートして参照します。

openはテンプレート生成が完了したときに開くファイルのようです。

他にもmkdirでディレクトリ作成、copyでコピー、dependencybuild.gradlecompileを追加することができます。 androidTestCompileに相当するものは見つけられませんでした。

これで色々な事ができそうですが、mergeを使う場合他のファイル構成都の兼ね合いのせいか、最終的なファイル内容構成が変化することがあります。
例えばbuild.gradlemergeした場合、gradleのタスク定義順に依存関係があるとすると順番が変化して思うように動かない場合がありました。

ここまでで、ユーザ入力から目的のファイルをある程度生成できますね。

参考までに前回張った成果物を再度張っておきます。

tmiyamon/android-studio-template

参考になれば幸いです。

まとめ

  • template.xml
    • ウィザード画面、テンプレート定義
    • parameter: ユーザ入力定義
    • category: コンテキストメニューや選択画面に影響
  • global.xml.ftl
    • 定数定義
  • recipe.xml
    • dependency: 依存定義
    • mkdir: ディレクトリ作成
    • copy: コピー
    • instantiate: テンプレート解釈、ファイル生成
    • merge: テンプレート解釈、既存の同名ファイルトマージ、xmlとgradleのみ

サンプル tmiyamon/android-studio-template

Comments