ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

OnEditorActionListener を使って EditText への Enter キー入力やアクション入力をハンドルする

EditText への Enter キー入力を検知して何か処理をしたい、ということを調べてみると、TextView.OnEditorActionListenerTextView#setOnEditorActionListener メソッド で設定すればよいというようなブログ記事がいくつか見つかる。

editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        // ... 処理 ...
        return false;
    }
});

ただ、ここら辺の記事を読んでも (一番下の記事以外は) onEditorAction メソッドの第 2 引数 actionId が何であるかや、第 3 引数 eventnull になりうることについての言及がなかったりして情報として不十分だったので、調べたことを書き残しておく。

TextView#setOnEditorActionListener メソッドとは

Javadoc には次のように書かれている。

Set a special listener to be called when an action is performed on the text view. This will be called when the enter key is pressed, or when an action supplied to the IME is selected by the user.

TextView | Android Developers

すなわち、Enter キーが入力されたときや IME に提供されているアクションが選択されたときに呼ばれるコールバック処理を設定するものである。 アクションについては後述する。 コールバック処理は TextView.OnEditorActionListener#onEditorAction(TextView v, int actionId, KeyEvent event) メソッド として記述される。

引数の actionId は、選択されたアクションを識別するための ID である。 Enter キー入力の場合は EditorInfo.IME_NULL になる。

第 3 引数 event には、Enter キー入力の場合にのみ null でない値が渡される。

また、Enter キー入力の場合には、キーダウン時とキーアップ時の 2 回 onEditorAction メソッドが呼ばれるようである。

アクションとは

IME に提供されるアクションについては、次のドキュメントに書かれている。

EditText複数行を受け付けない場合に、多くのソフトウェアキーボードでは Enter キーの代わりにアクションボタンが表示されるようである。 デフォルトでは 「Next」 や 「Done」 が表示される。 EditText 要素の android:imeOptions 属性を使って、「Next」 や 「Done」 ではなく 「Go」 や検索ボタンを表示させることもできる。

上で説明した TextView#setOnEditorActionListener メソッドを使うことで、次のような感じでアクションをハンドルできる。

editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        boolean handled = false;
        if (actionId == EditorInfo.IME_ACTION_DONE) {
            // ... なんかの処理 ...
            handled = true;
        }
        return handled; // このメソッド中でアクションを消化したら true を返す。
    }
});

IME については詳しくないのでわからないのだけれど、『Most soft input methods provide a user action button in the bottom corner』 って書かれているので、おそらく IME の実装次第ではアクションボタンが提供されないこともある気がする。

あと、当然ながらハードウェアキーボードしかない場合はユーザーがアクションを実行することはできない。

OnEditorActionListener#onEditorAction メソッドについてのプラクティス

  • Enter キー入力以外では、第 3 引数 eventnull になるので、気を付ける必要がある。
    • アプリケーション側でアクションを指定していなくても (例えば android:imeOptions="actionNone" を指定していても)、IME の実装によっては勝手にアクションをユーザーに提供することもある *1 ので、eventnull になり得ないつもりでコードを書いてはいけない。
  • ソフトウェアキーボードの Enter キー入力では OnEditorActionListener#onEditorAction メソッドが呼ばれないことがある。
  • 結論として、「EditText での Enter 入力時に (改行させずに) 何か処理をするようにしたい」 という場合、次のようにすべきだと思われる。 *3
    • EditText 要素の android:inputType 属性を設定し、複数行入力をできないようにする。
    • EditText 要素の android:imeOptions 属性に actionNone 以外の値を設定し、IME にアクションを提供する。 (あるいは独自のアクションを指定するとか。)
    • onEditorAction メソッドでは、アクションを受け取った場合の処理 (ソフトウェアキーボード用) と Enter キー入力を受け取った場合 (ハードウェアキーボード用) の両方の処理を書いておく。
      • このとき、Enter キー入力については onEditorAction メソッドが 2 回 (action=ACTION_DOWN と action=ACTION_UP) 呼ばれる可能性を考慮してコードを書いた方が良さそう。 (どういう条件でそうなるかはわかってないけど、1 回しか呼ばれないこともあれば 2 回呼ばれることもあるっぽい。)
    • IME の実装次第ではアクションボタンが表示されず、Enter キー入力では onEditorAction メソッドが呼ばれないということもありうると思うので、onEditorAction メソッドが呼ばれなくても次に進むための方法をユーザーに与えておくべき。 (「検索」 ボタンを配置しておく、とか。)

挙動については確証がない部分もあるけど、上のような感じで実装すれば問題はないはず。

Android Pattern Cookbook マーケットで埋もれないための差別化戦略

Android Pattern Cookbook マーケットで埋もれないための差別化戦略

*1:Nexus 7 (2013 年版) の 「英語 (米国) Google キーボード」 で確認した。

*2:Nexus 7 (2013 年版) の 「英語 (米国) Google キーボード」 を使っている場合、Enter キー入力ではコールバックメソッドが呼ばれないことを確認した。

*3:以下ではレイアウト XML 中に属性として記述する方法を書いているが、Java 上で同様のことをするようにしても良い。

cron 設定ファイル (crontab ファイル) の置き場所と書式について

上の記事を読んで興味を惹かれたので、『WEB+DB PRESS Vol.79』 の 「cron 周りのベストプラクティス」 を読んだ。 Web 上でも公開されている。 (2014-06-23 追記。)

そこでは cron 設定 (crontab) ファイルの記述方法として crontab コマンドを使用する方法が書かれていた *1。 で、記事を見てるうちに、以前 crontab コマンドやら /etc/cron.d やらの違いがよくわからずに調べて社内日記に書き残しておいたことを思いだしたのでブログで公開しておこうと思う。

ちなみに man の内容などは Debian 7 で見てるので、別ディストリビューションだと違ったりするのかもしれない。

WEB+DB PRESS Vol.79

WEB+DB PRESS Vol.79

  • 作者: 成瀬ゆい,そらは(福森匠大),西磨翁,小川航佑,佐藤新悟,塚越啓介,藤原亮,堀哲也,田村孝文,桑野章弘,松浦隼人,中村俊之,田中哲,福永亘,杉山仁則,伊藤直也,登尾徳誠,近藤宇智朗,若原祥正,松木雅幸,奥野幹也,後藤秀宣,羽二生厚美,笹田耕一,平河正博,東舘智浩,渡邊恵太,中島聡,A-Listers,はまちや2,川添貴生,山田育矢,伊藤友隆,村田賢太,まつもとゆきひろ,佐野岳人,山口恭兵,千葉俊輝,平松亮介,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2014/02/22
  • メディア: 大型本
  • この商品を含むブログ (4件) を見る

man について

本記事中で 『cron(8) の man』 などのように書いてある場合、man 8 cron という感じで man コマンドを使うことでその文書を読める。

crontab ファイルの置き場所

cron 設定をどこに書くことができるのか

cron(8) の man を見ると次のように書かれている。

cron は、スプール領域 (/var/spool/cron/crontabs) に置かれた crontab ファイルを探す (これらのファイルには /etc/passwd 内のアカウントを元にした名前がつけられている)。 見つかった crontab ファイルはメモリにロードされる。 このディレクトリの crontab ファイルは直接アクセスすべきではないことに注意せよ。 ファイルのアクセスや更新には crontab を使用すべきである。

cron は /etc/crontab も読み込む。このファイルのフォーマットは少々異なっている ( crontab(5) を参照)。 さらに cron は /etc/cron.d 内のファイルも読み込み、それらのファイルを /etc/crontab ファイルと同様に処理する (/etc/cron.d 内のファイルは /etc/crontab の特別なフォーマットに従う。つまりユーザ名のフィールドを含む)。 しかし、/etc/cron.d 内のファイルは /etc/crontab とは独立している。 つまり、例えば環境変数の設定を /etc/crontab から継承したりはしない。 この機能の用途は、/etc/cron.{daily,weekly,monthly} ディレクトリよりも細かな予定の調節を必要とするパッケージが、 /etc/cron.d に crontab ファイルを追加できるようにすることである。

ということで、cron 設定を書く場所 (crontab ファイルの置き場所) としては次の 3 つがある。

  • 各ユーザーの crontab ファイルの置き場所
    • /var/spool/cron/crontabs ディレクトリ内 (crontab コマンドで読み書きする)
  • root 権限が必要 (?) な crontab ファイルの置き場所
    • /etc/crontab ファイル
    • /etc/cron.d ディレクトリ内

crontab ファイルの書式は crontab(5) の man で見ることができる。 /var/spool/cron/crontabs 下に置く場合の書式と /etc/crontab や /etc/cron.d 下に置かれる crontab ファイルの書式は違うので注意が必要 (詳細は後述)。

crontab コマンドでユーザーごとの設定を書く

/var/spool/cron/crontabs 下のユーザーごとの crontab ファイルを読み書きする際は、crontab コマンドを使用する。 詳細は crontab(1) の man を参照のこと。

ちなみに、crontab コマンドに -r オプションを渡すと crontab が削除されてしまうため、crontab ファイルはユーザーのホームディレクトリなりなんなりでバージョン管理しておいて、crontab コマンドにそのファイルパスを渡すことで cron 設定を行う、というのがベストプラクティスとなっている。

これについては 『WEB+DB PRESS Vol.79』 の 「cron 周りのベストプラクティス」 にも書かれていた。

crontab の書式

crontab(5) の man には次のように書かれている。 実行時刻指定のフィールドの詳細については man を参照のこと。

各行には 5 つの時刻・日付フィールドがあり、 さらにコマンドと改行文字 ('\n') が続く。 (略) フィールドはスペース区切りでもタブ区切りでもかまわない。

(略)

「第 6」フィールド (行の残りの部分) には実行されるコマンドを指定する。 その行のコマンド部 (改行文字または % 文字まで) が /bin/sh (またはその crontab ファイルの SHELL 環境変数で指定されたシェル) によって実行される。 コマンド中にパーセント記号 (%) が バックスラッシュ (\) によってエスケープされずに置かれていると、 改行文字に置き換えられ、最初に現れた % 以降の全てのデータは 標準入力としてコマンドに送られる。 シェルの行末の "\" のような、 コマンドの単一の行を複数行に分割して記述する方法は、crontab にはない。

man にあるサンプルは次のような感じ。

# コマンドの実行に、デフォルトの /bin/sh ではなく /bin/bash を使用する。
SHELL=/bin/bash
# (この crontab の所有者に関らず) あらゆる出力を `paul' にメールする。
MAILTO=paul
#
# 毎日、日付変更の 5 分後に実行する
5 0 * * *       $HOME/bin/daily.job >> $HOME/tmp/out 2>&1
# 毎月初日の 2:15pm に実行する -- 出力は paul にメールされる
15 14 1 * *     $HOME/bin/monthly
# 平日の午後 10 時に実行してジョーを心配させる
0 22 * * 1-5    mail -s "午後10時だ" joe%ジョー、%%お前の子どもはどこだい?%
23 0-23/2 * * * echo "毎日 0,2,4..時 23 分に実行する"
5 4 * * sun     echo "日曜 4 時 5 分に実行する"

ジョーを心配させててひどい。

/etc 下の crontab について

/etc/crontab

/etc/crontab はシステム管理者が触るためのもので、普通はいじらない感じにしておくらしい。 デフォルト (?) では /etc/cron.daily/ や /etc/cron.weekly/ ディレクトリ内のスクリプトを定期的 (cron.daily なら 1 日ごと、cron.weekly なら 1 週間ごと) に実行するようになってるぽい。

/etc/cron.d ディレクトリ下の crontab

この中に置かれたファイルも cron による実行対象である。 cron が自動的にファイルの編集時刻をチェックしてくれるので、/etc/cron.d ディレクトリの中のファイルを変更しても crond の再起動などは必要ない。

crontab の書式

crontab(5) の man には次の記述がある。 /var/spool/cron/crontab 下の crontab の書式と違うのは、実行ユーザーの設定を行うためのフィールドがある点である。

各行には 5 つの時刻・日付フィールドがあり、 さらにコマンドと改行文字 ('\n') が続く。 システムの crontab ファイル (/etc/crontab) は同様のフォーマットを使用するが、 時刻・日付フィールドとコマンドの間で、 コマンドを実行するユーザ名を指定する。

man には次のような例が書かれていた。

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user    command
42 6 * * *        root    run-parts --report /etc/cron.daily
47 6 * * 7        root    run-parts --report /etc/cron.weekly
52 6 1 * *        root    run-parts --report /etc/cron.monthly

crontab の書式に関するおまけ

標準出力とエラー出力をファイルに出力したい場合

コマンドの出力結果は、通常はメールで送信されるが、以下のようにリダイレクトしてファイルに書きだすこともできる。 エラー出力も標準出力と同じファイルに書きだしたければ 2>&1 って付けておけばよい。

* * * * * /usr/bin/perl -e'print STDOUT "test\n"; print STDERR "test error\n";' >> $HOME/cron_test.log 2>&1

実行日の決定のはまりどころ

crontab(5) の man より。

注意: コマンド実行の日は 2 つのフィールドで指定できる — 月内日および曜日である。 もし両方のフィールドが制限指定 (* 以外) であると、いずれかのフィールドが現在時刻と合った時にコマンドが実行される。 例えば、

30 4 1,15 * 5

とすると、毎月 1 日と 15 日および毎週金曜日の 午前 4:30 にコマンドが実行される。

月内日と曜日の両方を指定していると、それぞれ独立に実行日の指定に使われるので注意!! *2

crontab をどこに置くのがいいのか

Web サービスなどのための crontab ファイルを /etc/cron.d 下に置くか、ユーザーごとの crontab として設定するか、どっちがいいのだろー、と思ったりした。 会社の方針とか、cron 以外の部分の運用によってもどちらがいいのか変わってくると思うけれど。

会社の人と話して教えてもらった /etc/cron.d 下での運用の利点としては、複数ファイルに分けて管理することがしやすい、ということがある。 確かにプロジェクトあたりに大量の cron 設定があるならファイルを分けておきたい。

それと設定ファイルを探しやすいということもある。 「ちゃんと調べてないですが」 という注意付きで以下のようなコメントを貰った。

12:59 (********) /var/spool/cron ← このへんのディレクトリは実装依存です
13:00 (********) /etc/crontab とか /etc/cron.d は大抵の実装で共通だからうれしい

私は前任者が書いた crontab ファイルを探すという経験をしたことはないのだけれど、ブックマークコメントにも 「/var/spool/cron 下だと見逃すことがある」 というようなことが書かれていたので、経験者的には /etc/cron.d の方が嬉しそう、という雰囲気を感じる。

もちろん root 権限が必要であるというような欠点もあるので、まあどっちにするかは組織での運用方針次第だろうなーという気がする。

(ちなみに私は /etc/cron.d 下に置くことが多い。)

*1:もしかしたら /etc/cron.d とかについても触れられていたかもしれないけど、ざっと見た感じなかったと思う。

*2:これも 「cron 周りのベストプラクティス」 に書かれてた。

Espresso を使って PreferenceActivity の自動 UI テストを行う (Android アプリ開発)

表題どおり、Espresso を使って PreferenceActivity の自動 UI テストを行う方法について簡単に説明します。

この記事内容は古くなっています

このエントリを書いていた頃は Espresso 1.1 でしたが、現在では Espresso 2.0 がリリースされています。 Espresso 自体の使い方はほとんど変わっていませんが、準備の方法が大きく違っています。 Espresso 2.0 については下記エントリを参照してください。

Espresso

Espresso とは

Espresso とは、android-test-kit プロジェクトの一部で、Android アプリの UI テストを行うためのライブラリです。

軽く使ってみた感じですが、結構使いやすい API になっていますし、使い始めるのはそれほど大変ではないですので、単一の activity に対する UI の自動テストなどではどんどん使っていくと良さそうです。

Espresso の準備や使い方

基本的にはプロジェクトサイトのドキュメントを見るといいでしょう。

日本語のブログ記事では、yanzm さんの記事がわかりやすいと思います。 (yanzm さんのブログでは下記記事以外にも Espresso 関係の記事がいくつかあります。)

GoogleInstrumentationTestRunner について

上のページを見ながら Espresso の準備をする際、GoogleInstrumentationTestRunner を使うようにする設定を書くと思います。

GoogleInstrumentationTestRunner というのは、Espresso が必要とするテストランナーです。 android-test-kit プロジェクトに含まれています。 テスト開始までにアプリケーションの onCreate が終わっていることを保証したり、Instrumentation で作られた Activity の onFinish メソッドが Instrumentation の終了時までに呼ばれることを確実にしたりしてくれます。 詳細は次のページ。

PreferenceActivity の自動 UI テスト

PreferenceActivity 上の設定項目をタップ (クリック) して、挙動を検査する』 という簡単なテストの方法について説明します。

PreferenceActivity 上に表示されている項目の選択

PreferenceActivity 上に表示されている項目のリストは ListView で実装されています。 Espresso では、ListView 上に表示されている項目を選択するためのメソッドとして、Espresso.onData メソッドが提供されています。

Espresso.onData メソッドは、ListView の各項目に結び付けられたデータ (Adapter で提供されるデータ) から与えられた条件にマッチするものを見つけ、対応する view を見つけるというようなものです。 詳細は次を見てください。

そして、PreferenceActivity のリストの各項目にマッチするかどうかを調べるための Matcher として、PrefernceMatchers クラスが Espresso には含まれています。

具体的には、次のようなコードで特定のキーを持つ項目をクリックさせることができます。

// 必要な `import` 文
import com.google.android.apps.common.testing.ui.espresso.matcher.PreferenceMatchers;
import org.hamcrest.Matchers;
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onData;
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;

// "setting-name" というキーの設定項目を選択し、クリックする。
onData(Matchers.<Object>allOf(PreferenceMatchers.withKey("setting-name"))).perform(click());
    // 本来は画面上に項目が表示されていなくても自動でスクロールしてくれるはずだが、
    // 必ずしもそうならないことがある。 (詳細は後述)

次の記事も参考になります。

項目の選択についてのバッドノウハウ (スクロールについて)

上のサンプルコードの中のコメントにも書いたように、onData を使って、もともと画面上に表示されていない項目を選択した場合は、Espresso が自動的に項目を画面上に表示してくれます。 しかし、PreferenceActivity のリストではうまく動かない場合がありました。

  • API level 8 でうまく動かないことを確認。
  • API level 19 では、targetSdkVersion を 19 にしていれば動くが、targetSdkVersion を指定しなかった場合は動かないことを確認。

回避策として、一度リストのドラッグを行えば良いようです。 (ドラッグにより項目が見える必要はなくて、単にドラッグすればよいようである。) 自動テスト時のリストのドラッグには TouchUtils を使うのが便利だと思います。

サンプルプロジェクト

Espresso を使って PreferenceActivity の自動 UI テストを行うサンプルプロジェクトを GitHub で公開しています。

このサンプルプロジェクトには、単に設定画面のみを持つアプリ (テスト対象) と、その設定画面の中の特定の項目をタップして、HTTP スキーマの URL を開くためのインテントが投げられることを確認するテストが含まれています。

テストクラスは次のものですので、テストコードの参考にしてください。

Android Studio 0.4.0 への移行

Android Studio 0.4.0 がリリースされていますね! わー、ぱちぱち。

Android Studio 0.4.0 への移行は、単にバージョンを上げるだけという感じではなかったので、苦労した箇所などを簡単にメモしておきます。

Gradle と Android Gradle プラグインのバージョンアップ

Android 0.4.0 では、Gradle 1.9、Android Gradle プラグイン 0.7 が必要となっています。 それぞれバージョンアップを行いましょう。 Gradle は Gradle ラッパーとしてプロジェクト内に持っていることが多いと思うので、gradle/wrapper/gradle-wrapper.properties を次のように書きかえればいいはずです。 *1

-distributionUrl=http\://services.gradle.org/distributions/gradle-1.8-bin.zip
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-bin.zip

Android Gradle プラグインのバージョンアップは、build.gradle の記述を次のような感じで変更すればよいですね。 (0.7.1 がリリースされており、後述する packagingOptions の exclude は 0.7.1 から使えるようになったものなので、バージョンは 0.7.1 以降を指定しています。)

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
-        classpath 'com.android.tools.build:gradle:0.6.+'
+        classpath 'com.android.tools.build:gradle:0.7.1+'

build.gradle の記述変更

Android Gradle プラグイン 0.7 では、大きめの変更が入っていますので、そのままビルドしようとしても失敗する可能性があります。 とりあえずビルドしてみて、エラーが出たら必要に応じて変更を加えればよいでしょう。

以下に、私が行った変更を書いておきます。

buildConfig から buildConfigField に変更

Android Gradle プラグイン 0.6.x のときには、BuildConfig クラスのフィールドを定義するために、次のように buildConfig メソッドを使用することができました。

android {
    buildTypes {
        release {
            buildConfig "public final static boolean XXX_TEST_MODE = false;"
        }

0.7.0 以降ではこれは使用できなくなっており、代わりに buildConfigField メソッドを使用します。 buildConfigField は 3 引数をとるメソッドで、前から順にフィールドの型、フィールドの識別子、フィールドの値を設定します。

android {
    buildTypes {
        release {
            buildConfigField "boolean", "XXX_TEST_MODE", "false"
        }
私がはまったところ

私はもともと次のようなビルドスクリプトを書いていました。

android {
    buildTypes {
        debug {
            buildConfig "public final static boolean XXX_TEST_MODE = false;"
        }
        testtarget.initWith(buildTypes.debug)
        testtarget {
            buildConfig "public final static boolean XXX_TEST_MODE = true;"
        }

で、Android Gradle プラグイン 0.6.x の頃までは問題なくビルドできていたのですが、0.7.1 に移行して次のように書きかえるとビルドできなくなりました。

android {
    buildTypes {
        debug {
            buildConfigField "boolean", "XXX_TEST_MODE", "false"
        }
        testtarget.initWith(buildTypes.debug)
        testtarget {
            buildConfigField "boolean", "XXX_TEST_MODE", "true"
        }

ビルドに失敗する際のエラー出力は次のようなもの。

.../BuildConfig.java:14: エラー: 変数 XXX_TEST_MODEはすでにクラス BuildConfigで定義されています
  public static final boolean XXX_TEST_MODE = true;
                              ^

debug ビルドタイプでの buildConfigField の設定がそのまま testtarget に受け継がれて、testtarget ビルドタイプでも同名のフィールドを定義しているので問題が起こる、という感じでした。 で、どうすればいいのか結構悩んだのですが、単に debug ビルドタイプで buildConfigField メソッドを呼び出す前に initWith して testtarget ビルドタイプの初期化をしてやれば良いのでした。

android {
    buildTypes {
        // `initWith` した後に `debug` ビルドタイプで `buildConfigField` 呼び出しをすればよい
        testtarget.initWith(buildTypes.debug)
        debug {
            buildConfigField "boolean", "XXX_TEST_MODE", "false"
        }
        testtarget {
            buildConfigField "boolean", "XXX_TEST_MODE", "true"
        }

jar ファイルに含まれるリソースファイルがだぶる問題

ビルドを実行すると次のようなエラーが発生するかもしれません。 これは、libs ディレクトリの JAR ファイルを展開したときに、同じパスのリソースファイルが複数の JAR ファイルに存在すると発生するエラーのようです。

Error: duplicate files during packaging of APK ...\build\apk\App-testtarget-unaligned.apk
        Path in archive: META-INF/NOTICE.txt
        Origin 1: ...\App\libs\xxxx-4.0.0.jar
        Origin 2: ...\App\libs\yyyy-4.0.0.jar
You can ignore those files in your build.gradle:
        android {
          packagingOptions {
            exclude 'META-INF/NOTICE.txt'
          }
        }

どちらの JAR に含まれるリソースファイルも必要でない場合 (例えばパッケージに含めなくて良い NOTICE.txt など) は、そのリソースファイルをパッケージング時に除外するようにすることでこのエラーを回避できます。 エラーメッセージにも書かれていますが、次のようなコードを書けばよいです。

android {
    packagingOptions {
        exclude 'META-INF/NOTICE.txt'
    }
}

これは Android Gradle プラグインの 0.7.1 で導入された機能です。

lint でエラーになってもビルドに失敗しないようにする

おそらく Android Gradle プラグインの 0.7.0 からだと思うのですが、build タスクの一部として lint タスクも実行されるようになりました。 で、デフォルトでは lint タスクでエラーが発生するとビルドが止まるようになっています。 理想的には lint でエラーが発生しないようにするのが良いと思いますが、実際には問題ない箇所でも lint のエラーになることがあった *2 ので、とりあえず lint でエラーが出てもビルドを継続するようにしました。

android {
    lintOptions {
        abortOnError false
    }

参考資料

Android Studio 0.4.0 移行とは関係ないけど Windows で Gradle を使うときの文字コードではまった

Android Studio 0.4 移行とは関係ないのですが、Windows で Gradle を使っていると、行コメントの次の行までコメント扱いされる、ということがあってはまってしまいました。

Groovy 処理系が期待するファイルの文字エンコーディング (Windows なので CP932) と、実際のファイルの文字エンコーディング (UTF-8) が違っているために起こる問題のようです。 この問題については下記の記事が参考になりました。

結局のところ、Gradle ラッパーを使っているのであれば、gradlew.bat を次のように変更したら良さそうですね。 *3

diff --git a/gradlew.bat b/gradlew.bat
index 8a0b282..c56f988 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -10,6 +10,7 @@ if "%OS%"=="Windows_NT" setlocal

 @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 set DEFAULT_JVM_OPTS=
+set JAVA_OPTS="-Dgroovy.source.encoding=UTF-8" "-Dfile.encoding=UTF-8"

 set DIRNAME=%~dp0
 if "%DIRNAME%" == "" set DIRNAME=.

コンソール出力のエンコーディングも変化してしまうことに気付いた (追記)

-Dfile.encoding によるデフォルトファイルエンコーディングの変更を行うと、コンソール出力のエンコーディングも変化してしまうことに気付きました。 いまのところビルドスクリプトのエンコーディングを指定するいい方法はなさそうですね。

次の記事に、Gradle に変更を加えてビルドスクリプトのエンコーディングを指定できるようにすることについて書きました。

*1:私は Gradle ラッパーの更新時には毎回 jar なども生成し直してます。 念のため。

*2:具体的には、API level を確認したうえで特定 API level 以降でしか使用できないメソッドを呼び出しているような場合。

*3:"-Dgroovy.source.encoding=UTF-8" は必要かどうかわかんないですが。

Java Persistence API (JPA) 実践入門

この記事は Java EE Advent Calendar 2013 の 24 日目の記事です。 昨日は nabedge さんの 「JAX-RSのビューとしてMixer2を使ってみる」 でした。 明日は eller86 さんの 「ウェブアプリケーションサーバでよくあるクラスローダのトラブル」 です!

概要

これから Java Persistence API (JPA) を使ってみようとしている人向けの、JPA の簡単な使い方や IDE の機能の紹介などの話です。 私自身、最近 JPA を使い始めたのですが、日本語の実践的な入門記事みたいなのが見当たらなくて苦労した (JPA の概念の説明記事は結構あって助かったのですが) ので、少しでも参考になればと思います。 IDE 関係の話は Eclipse ユーザー向けの説明になりますが、NetBeans でもだいたい似たようなことができるはずです。 JPA 実装としては、参照実装である EclipseLink を使用しています。

予めて断っておきますが、わりと手探りで方法を探したりしてるのでもっといい方法があったりするかもしれません。

ちなみにこの記事は Java EE Advent Calendar の記事ではありますが、JPAJava EE だけでなく Java SE 環境でも使用できます。

JPA の概念などの入門記事

JPA の概念の説明などの入門記事はわかりやすいものがいくつかありますので、概念的な部分は他の記事を見ていただければよいかと思います。 特に次の記事が非常に参考になりました。

また、英語ですが OracleJava EEチュートリアルも参考になります。

JPA 仕様

JPA 仕様は JSR として公開されています。 正確な記述を知りたい時などは JSR を参照しましょう。

開発環境

この記事では、次のような開発環境を念頭において話をします。

まずは簡単に JPA を使ってみる

JPA を使用した簡単なサンプルプロジェクトを作ってみました。

次のように git clone して、gradle run で実行できます。 (JDK 1.7 がインストールされていれば動くはずです。)

git clone https://github.com/nobuoka/jpa-sample.git
cd jpa-sample
./gradlew run

サンプルプロジェクトの説明

このサンプルプロジェクトには、Entity クラスとして、次の User クラスのみが使含まれています。 User クラスは persistence.xml で定義される 「jpa-sample」 という persistence unit に含まれています。

この User を 3 つ作成して DB に保存し、その後 DB から全 User を取得して表示するという処理を次のクラスで行なっています。

接続先 DB などの設定は psersistence.xml に記述することもできますが、今回は createEntityManagerFactory メソッドの第 2 引数に渡すようにしています (App.createDbSetting メソッド参照)。 DBMS は Derby を指定しており、DB が存在しない場合は自動的に DB が作成されます (app/jpa-sample.derby というディレクトリ)。 また、次の設定により、テーブルが存在しない場合はテーブルも生成されるようになっています。

settings.put(PersistenceUnitProperties.DDL_GENERATION, "create-tables");

今回の例では、JPA 実装として EclipseLink を使用し、接続先 DB の DBMS が Derby であるため、依存ライブラリとして EclipseLink と Derby の JDBC を指定してあります。

そんな感じで

上で説明したように、とりあえず JPA を使ってみるだけなら次のものを用意すれば使用できます。

  • 依存ライブラリを記述 (JPA 実装と接続先 DB 用の JDBC ドライバ)
  • Entity クラス (@Entity アノテーションを付与したクラス) を記述
  • persistence.xml (Java SE 環境で使用する場合は META-INF/persistence.xml に置く) を記述

あとは EntityManager を取得して使用するだけです。 あ、もちろん接続先 DB の準備も必要です。 上の例では Derby を使って、接続時に DB の作成やテーブルの作成もしてしまっているので手で準備はしていませんが、一般的には *1 予め DB を用意しておく必要があります。

クエリを投げる方法

上のサンプルプロジェクトでは、User クラスで定義された 「User.findAll」 という名前の Named Query を使用して全 User を取得するクエリを投げていました。

JPA では、クエリの記述方法として次のようなものがあります。

  • Java Persistence query language (JPQL) : JPA 用に定義されたクエリ言語で、SQL などに変換されて使用される
    • クエリに名前をつけて Entity クラスのアノテーションなどで定義することもできる (named query)
  • Native SQL : 生の SQL をクエリとして使用することもできる
  • Criteria API : 上のような文字列ベースのクエリではなく、オブジェクトベースでクエリを生成する

可読性やパフォーマンスを考慮すると、静的なクエリには named query を使うのが良さそうですね。 動的にクエリを組み立てる場合は、Criteria API と後述する Metamodel クラスを組み合わせて使うのが型安全性が高くて良さそうです。

パフォーマンス的な話については、去年の Java EE Advent Calendar の次の記事が興味深いです。

Metamodel クラスの生成

JPA 2.0 で Metamodel というものが導入されました。 Metamodel クラスは、Entity の情報を持っているもので、例えば永続フィールドの型の情報を Metamodel クラスが持っていたりします。 Metamodel クラスと Criteria API を組み合わせて使うことで、型の安全性の高い状態でクエリ生成を行うことができます。

Metamodel 自体は動的に生成されることもあるものですが、コーディング時に使用するためには静的なクラスとして Metamodel クラス (特に、実装間の互換性が保証された canonical metamodel クラス) を生成する必要があります。 静的な Metamodel クラスを生成するために Annotation Processing を使用します。

EclipseLink の Annotation Processor で Metamodel クラスを生成する手順は次のドキュメントに記述されています。

Gradle を使用している場合は、次のようにタスクを定義して、コンパイル前に Annotation Processing を実行させることができます。

この例では、生成されたクラスは gen/main/java ディレクトリ下に置くようにしています *2。 上のような Gradle タスクの問題は、生成された Metamodel クラスの元の Entity が削除された場合に Metamodel クラスを手で削除しないといけない (か、または gen/main/java ディレクトリをまるごと削除する必要がある) ということですが、まあとりあえずはそれでいいかなぁという気でいます。

Metamodel クラスを Criteria API で使用するサンプルコードは次のようになります。 単純に、"testuser1" という nameUser 一覧を取得しているのですが、JPQL と比べるとやや可読性は低くなってしまいますね。

Metamodel については以前に書いた記事もあわせて参考にしてください。

EclipseJPA

ここからは EclipseJPA を扱うための話をします。

Eclipse へのインポート

上のサンプルプロジェクトを Eclipse で使用することを考えます。 まずは Eclipse にインポートします。 Gradle を扱うために、Eclipse にあらかじめ 「Gradle Integration for Eclipseプラグインがインストールされている必要があります。 (「Help」 メニューの 「Eclipse Marketplace」 からインストールできます。)

Eclipse の 「File」 メニューから 「Import...」 を選択し、「Gradle」 の 「Gradle Project」 を選びます。 プロジェクトのディレクトリを選択し、「Build Model」 を実行し、表示されたプロジェクトをすべて選んで 「Finish」。

これで Gradle プロジェクトとして上のサンプルプロジェクトを Eclipse にインポートできました。 Eclipse にはサブプロジェクトの概念がないので、Gradle プロジェクトのサブプロジェクトもルートプロジェクトも、Eclipse 上では同じ階層のプロジェクトとなります。

JPA プロジェクトに変換する

インポートした直後のプロジェクトは単なる Gradle プロジェクトです。 JPA 関係の便利機能が使えるように、JPA プロジェクトにコンバートします。

Eclipse の 「Project Explorer」 内でプロジェクト (上のサンプルプロジェクトであれば 「data-model」 プロジェクト) を右クリックして、「Configure」 の 「Convert JPA Project...」 を選択してください。

が選択されていることを確認して次へ。 JPA Facet の設定入力欄では、「Platform」 を 「Generic 2.1」 にしておけば良さそうです (あまり違いがわかってない; 「Generic 2.1」 だと Entity を元にテーブルを生成することができないが、「EclipseLink 2.5.x」 だとそれができる、というような違いがある)。 「JPA implementation」 はとりあえず 「Disable Library Configuration」 で良さそうです ((何に使われるかちゃんとわかってない。))。

「Connection」 はとりあえず 「<None>」 にしておきます。 (後で接続します。)

これで JPA プロジェクトに変換できました。

JPA パースペクティブを開く

Eclipse には、データベース接続などの情報が表示される JPA パースペクティブがあります。 デフォルトでは Eclipse ウィンドウの右上の方にパースペクティブの変更ボタンがあるはずで、そこから JPA パースペクティブに切り替えられます。

f:id:nobuoka:20131223172752p:plain

まだデータベース接続をしていないので何も情報を得られませんが、例えば 「Data Source Explorer」 でデータベースの情報を見たりすることができます。 このあとで、DB 接続を行なっていきます。

ドライバ定義の追加 (例として PostgreSQL 9.3 のドライバを追加する)

Eclipse 上で DB の情報を取得したりするために必要な、DB 接続に用いるドライバの定義を追加します。 今回は例として PostgreSQL 9.3.2 用のドライバを追加します。 次のページから 「JDBC41 Postgresql Driver, Version 9.3-1100」 をダウンロードしておきます。

Eclipse の 「Window」 メニューから 「Preferences」 を選び、「Data Management」 の 「Connectivity」 の 「Driver Definitions」 を開きます。 「Add」 して 「New Driver Definition」 を開きます。 「Name/Type」 タグにおいて、「Generic JDBC Driver」 を選択 *3 して、「Name」 に 「PostgreSQL 9.3 Driver」 とでも入れておきます。 「JAR List」 では先ほどダウンロードした JDBC の JAR ファイルを選択します。 最後に Properties として Connection URL などを適当に入力します (実際の値はあとで入力するため、ここではサンプルの値を入れておけば良い)。 Driver Class だけはちゃんと次に示す値を入力する必要があります。

この操作は、接続先 DBMS の種類ごとに 1 回のみ必要なものです。

DB 接続の追加

新たに DB 接続を追加します。 JPA パースペクティブの 「Data Source Explorer」 の 「Database Connections」 を右クリックして、「New」 を選びます。

「New Connection Profile」 というウィンドウが開くので、「Generic JDBC」 を選択し、「Name」 は適当に DB を表す名前 (今回の例では 「Sample DB」 としておく) を入力して次へ。 ドライバの設定では、先ほど定義した 「PostgreSQL 9.3 Driver」 を選んで、プロパティとして今回の DB の接続に必要な値を入力します。

  • Connection URL : jdbc:postgresql://localhost/sample_db
  • Database Name : sample_db
  • User ID : sample_user_id
  • Password : sample_password

で、「Finish」。

DB 接続が追加されたので、「Data Source Explorer」 で接続先 DB のスキーマなどを確認できます。

JPA プロジェクトに DB 接続を追加する

プロジェクトを右クリックして 「Properties」 をクリックして設定画面を開き、「JPA」 の項目を選択します。 「Connection」 の選択肢に、先ほど追加した DB 接続 (今回の例だと 「Sample DB」) が追加されていますので、それを選びます。

「Connection」 の設定として、「Override default catalog from connection」 や 「Override default schema from connection」 というものがありますが、PostgreSQL 9.3 に接続する今回の場合はどちらも有効にする必要があります (理由はよくわかってませんが、Generic JDBC を使って PostgreSQL に接続しているから?)。 どちらも有効にして、それぞれ 「"sample_db"」 と 「"public"」 を選択してください。

既存のテーブルから Entity クラスを生成する

Eclipse の 「Project Explorer」 を右クリックして 「JPA Tools」 の 「Generate Entities from Tables...」 を選んで、既存のテーブルから Entity クラスを生成することができます。 既存の DB に接続するために Entity を生成する際に便利ですね。 既存のテーブルから Entity を生成するためには、上で説明したように JPA プロジェクトに DB 接続を追加しておく必要があります。

Java EE っぽい話

せっかくの Java EE Advent Calendar なので、Java EE での JPA の話にも少し触れておきます。

Java EE コンテナにおける entity manager factory の取得方法

JSR 338 (JPA 2.1) の 7.3.1 節に記述されています。

Java EE 環境では、PersistenceUnit アノテーションを用いて、EntityManagerFactory をインジェクションさせることができます。 また、 JNDI ルックアップを通して EntityManagerFactory を取得することもできます。

@PersistenceUnit
EntityManagerFactory emf;

コンテナによって管理された entity manager の取得

また、Java EE 環境であれば、EntityManagerFactory とやりとりせず、コンテナによって管理される entity manager を Dependency Injection (DI) で取得することもできます *4。 JSR 338 (JPA 2.1) 7.2.1 節に記述されています。

@PersistenceContext
EntityManager em;

@PersistenceContext(type=PersistenceContextType.EXTENDED)
EntityManager orderEM;

永続ユニット (Persistence Unit) のパッケージング

JSR 338 (JPA 2.1) の 8 章あたりの話です。

Java EE 環境では、EJB-JAR、WAR、EAR、そしてアプリケーションクライアント JAR が永続ユニットを定義できます。 それらの範囲で、任意の個数の永続ユニットを定義することができます。

永続ユニットは、WAR や EAR の中の 1 個以上の jar ファイルにパッケージングされたり、EJB-JAR ファイルの中のクラスの集合としてパッケージングされたり、WAR の classes ディレクトリのクラスの集合としてパッケージされたりします。 それらの組み合わせもあり得ます。

永続ユニットは persistence.xml ファイルで定義されるわけですが、persistence.xml ファイルを含む META-INF ディレクトリの親ディレクトリ、あるいはそのような META-INF ディレクトリを含む jar ファイルは永続ユニットのルートです。 Java EE 環境における永続ユニットのルートは、次のどれかでなければなりません:

  • EJB-JAR ファイル
  • WAR ファイルの WEB-INF/classes ディレクト
  • WAR ファイルの WEB-INF/lib ディレクトリ内の jar ファイル
  • EAR ライブラリディレクトリ内の jar ファイル
  • アプリケーションクライアント jar ファイル

Java EE 関係の話について

ここら辺は JSR 338 を 「ふむふむなるほど」 って読みながら知った知識程度しかないので、とりあえず参考程度に。

おわりに

JPA を使い始めてから得られた知識などをまとめてみました。 2 ヶ月ぐらい前に書いた 「Java での web アプリケーション開発時の開発環境メモ #1」 という記事で 「DB 周りどうしたらいいのかよくわかんない」 って書いたら、ブコメで 『DB周りはJPA2.0でOK。今のJavaEEは「迷ったら標準」』 って教えてもらって JPA を学び始めたのですが、web 上の情報だけだとある程度使えるようになるまでわりと苦労しました。 わかってしまえば (とりあえず使う分には) そんなに難しくもないんですけどね。

まだまだ使いこなすところまで行っていませんが、JPA はなかなか使いやすくて良いと思っています。

*1:PostgreSQL を使う場合など

*2:どこに置くようにするのがいいんでしょうね

*3:PostgreSQL JDBC Driver」 というものもありますが、バージョン 8.x にしか対応していないようなので、Generic の方を選びます。

*4:あと JNDI ルックアップを通しても可能です