SyntaxHighlighter

2013年6月9日日曜日

イベントバスライブラリOtto

Androidのアプリ内でIntentServiceとか使った時に、処理の完了をIntentやCallbackでActivityに通知するコードを毎回書いていて不毛だなーと思っていたら、Ottoという便利そうなライブラリを見つけた。
※確かIntelliJにRobolectricのPluginが無いか探してるときに発見したような気がする。世の中何がきっかけになるかわかりませんな。
で、備忘も兼ねて導入と利用のポイントとして次のことをメモっておく。
  1. Ottoとは
  2. 導入
  3. 前準備
  4. イベント送信/受信
  5. Produce
  6. AndroidAnnotationsとの相性
1. Ottoとは
Ottoは最近日本にも進出したSquareがオープンソースで開発しているイベントバスのライブラリで、出来ることは単純だけど、その分シンプルで気軽に使える。
(個人的にはライブラリってのはこうあるべきだと思う)
とりあえずは、アプリ内IntentとかCallback的なイベントハンドリングのコードが超簡単に書けると思っておけばオッケー。

2. 導入
導入は本当に何も難しくなくて、「公式サイト見てくれ」状態。

2-1-a. pomる
最近はAndroidでもmavenってる人も多いと思うけど、その場合はpomのdependenciesに以下を追加するだけ。Gradleでも基本変わらないハズ。多分。
もちろん、バージョンはこれを書いている時の話だから、導入時に公式サイトでチェックした方が良いと思う。

2-1-b. libsる
公式サイトでjarを落とすこともできるので、それを普通にlibsに突っ込んでもOK。

2-2. proguard対策
Ottoはアノテーションを使って呼び出すメソッドを特定する。その都合で、呼び出すメソッドがproguard的に使われてないからいらないと判断されて捨てられてしまう可能性がある。その対策として、proguard-project.txtに以下を追記しておく。
3. 前準備
Ottoでイベントの送受信をする際に、重要となるのがBusクラスのインスタンス。Busクラスのインスタンスはイベントを送受信するそれぞれのクラスで必要なので、Singletonな管理クラスを1つ作ってそこで管理してしまうのが良いだろう。
と、いうことで次のようなクラスを作っておく。
ここで、new Bus()するときにThreadEnforcerなる引数を渡すことができる。これにより、Busにイベントを登録する(=イベントを実行する)スレッドの種類を限定することができる。限定した種類意以外のスレッドからイベントが登録された場合、例外が発生する。 デフォルトではThreadEnforcer.MAINが指定されていて、メインスレッド(=UIスレッド)からしかイベントが登録できなくなっている。どのスレッドが登録しても良い場合はThreadEnforcer.ANYを引数に指定すれば良い。

4. イベント送信/受信
ここまでくればイベント送信と受信は簡単。

4-1. イベント送信
より簡単なイベント送信から。イベント送信はBusクラスのインスタンスを取得して、イベントとして送信したいクラスのインスタンスをpostすればOK。
ここで送信したクラスの種類によって、受信するメソッドを分けられるのだが、この辺は自然につくっていれば上手くいくはず。
4-2. イベント受信
イベントを受信するには、@Subscribeアノテーションをつけたメソッドを持ったクラスを作り、そのクラスのインスタンスをBusのregister/unregisterを使って登録する必要がある。
register/unregisterはライフサイクル(※)に合わせて実行する必要がある。
※もちろん、クラスのライフサイクルに厳密に合わせる必要はなく、受信が必要になったらregisterして、不要になったらunregisterすれば良い。
Activityであれば、onResumeでregisterして、onPauseでunregisterするような形になるだろう。
5. Produce
Ottoの最後のメイン機能であるProduceは、@Subscribeなメソッドがregisterされた瞬間にイベントを発行するという、ちょっとわかりにくい機能である。
使いどころは、常にある値が時々変動しているような場合に、その値をイベントで追跡したい場合に、初期値を取得するような時だと思われる。
具体例としては、例えば温度センサのイベントを作った場合、Produceを使えば、「温度センサを使うと言った時点の温度(Produceでイベント通知)」と「温度が変化した際の温度(Bus#postでイベント通知)」の両方のイベントを送信できる。
使い方は、@Subscribeと同様、register/unregisterするようにした上で、イベントとして渡すクラスのインスタンスを生成するメソッドを作り、@Produceアノテーションを作るだけである。
6. AndroidAnnotationsとの相性
最後にAndroidAnnotationsと併用する際の注意点を。
AndroidAnnotationsと併用する場合、AndroidAnnotationsのバージョン3.0以上を使うこと。(2013/6/9時点で3.0SNAPSHOTしかないが…)
これは、Ottoが性能を出すために、@Subscribe/@Produceの探索をする際に継承関係まで辿らないことと、AndroidAnnotationsがActivityを継承したActivity_を作って使用することが組み合わさって、イベントが正常にメソッドを起動できなくなるからである。
AndroidAnnotationsの3.0-SNAPSHOTからは、@Subscribe/@Produceのメソッドを継承先でも用意するようになっているので、問題は発生しなくなっている。

2013年1月27日日曜日

RobolectricでBluetoothAdapterをMockする

RobolectricでAndroidのクラスをMockする時はShadowを使うのが普通で、独自拡張が必要な場合は拡張(あるいは新規作成)したShadowクラスをRobolectric.bindShadowClassすれば良い。

つまり、BluetoothAdapterのShadowクラスを作る時は、ShadowBluetoothAdapterの拡張Shadowクラスを作ると思う。ところが、これを@Beforeや@TestでbindShadowClassしてもうまくいかない場合がある。

結論から書くと、ShadowBluetoothAdapterのgetDefaultAdapterの返り値を上書くのがポイント。
ShadowBluetoothAdapterのgetDefaultAdapterは、Robolectric.applicationで保持しているBluetoothAdapterを返す仕組みになっていて、これが生成されるのはRobolectric.applicationが生成されたときである。
つまり、@Beforeや@TestでbindShadowClassしてもgetDefaultAdapterの戻り値が拡張前のShadowBluetoothAdapterがbindされたBluetoothAdapterになってしまうのである。

回避するには、次の2つの方法がある。

1. getDefaultAdapterをOverrideする。
getDefaultAdapterをOverrideして、Robolectric.newInstanceOf(BluetoothAdapter.class.getName())を返すようにしておく。こうすることで、getDefaultAdapterをする時点で登録されていたShadowBluetoothAdapterがbindされたBluetoothAdapterが返されるようになる。
シングルトンにするかどうかはテストの性質などを考えて適宜調整すればOK。

2. Robolectric.applicationの生成前にbindShadowClassする。
これをするには、RobolectricRunnerを継承した独自Runnerを作って、bindShadowClassesをOverrideして、その中でbindShadowClassすれば良い。こうすれば、Robolectric.applicationの生成前に、BluetoothAdapterクラスに拡張したShadowBluetoothAdapterを紐付けられる。

サンプルコードは以下(1の方法で実装している)

2012年12月2日日曜日

RobolectricとPowerMockを合わせてAndroidのUnit Test(不完全)

AndroidのUnit Testをするのに、RobolectricとPowerMockを合わせたら良いんじゃないかと思ったら意外と設定が面倒だったのでその記録。
不完全な点は、androidパッケージに関わるクラスについてはPowerMockできないので、例えばActivity内でテスト対象のクラスを直接newしているような場合には差し替えができない。

1. Robolectric用プロジェクトの作成と設定
まずは必要なjarをRobolectricのサイトからダウンロードする。
Sonatypeから落とせって言われるけど、どこから落として良いのかわかりづらい…
全部入りのjarが欲しいのでwith-dependenciesを選択して、右のタブでArtifact Informationを選んでDownloadボタンで落とせる。


次にRobolectric用のプロジェクト(以下、RobolectricTest)を作る。
RobolectricTestは、Android Java Projectではなくて、普通のJava Projectとして作成する。
ビルドパスには以下の4つを設定
その1 Robolectric
さっきダウンロードしたRobolectricのjarをlibディレクトリにでも置いてビルドパスを通しておく。
その2 Android.jar
Android SDKにあるandroid.jarを、RobolectricTargetに合わせたバージョンで外部jarとして登録しておく。ビルドパスの順序で、android.jarがRobolectricのjarより先に読まれるようになっているとエラーが出るので注意すること。
その3 JUnit 4
JUnit 4はAdd Library...で設定しても良いし、jarをダウンロードしてビルドパスに設定してもOK。ただ、少なくともRobolectric-1.1はJUnit 4.11に対応していないので、その点は注意すること。
その4 RobolectricTarget
RobolectricTargetはビルドパス設定のProjectsで設定しておく。

テストコードは、ややこしいことにテスト対象のプロジェクト(以下、RobolectricTarget)の方に作る必要があるので、RobolectricTargetにテストコード用のディレクトリ(以下、unit_test)を作っておく。
RobolectricTest側のソースコードディレクトリは必要無いので、srcがあれば削除して、代わりにさっき作ったRobolectricTargetのunit_testへのリンクをソースコードディレクトリとして設定する。
設定方法は、プロジェクトプロパティのJava Build PathでLink Source...をクリックして、RobolectricTargetのunit_testを指定するだけ。

ここまででRobolectric用のプロジェクト設定は完了。大体こんな感じになっているはず。

2. JUnitの実行設定
RobolectricのテストをJUnitで走らせるのにはちょっと設定が必要なので、Run Configurations...で、JUnit用の新しい設定を作成する。
で、Testタブの設定で、Test RunnerはJUnit 4、LauncherはEclipse JUnit Launcherを選ぶ。(Launcherの設定はRun Configuration設定の下部にリンクでSelect one...かSelect other...みたいな感じで出てるはず)

次に、ArgumentsタブのWorking Directoryの設定をOtherにしてWorkspace...からRobolectricTargetを選ぶ

最後に、EnvironmentタブでANDROID_HOMEを設定する。ValueはAndroid SDKのパス。

3. テストコードを書く
テストコードは以下のような感じで書けばOK。ポイントはテストをテスト対象のActivityがあるパッケージと同じところにすること。こうしないとprotectedなonCreateが呼べなくてハマる。

4. PowerMockの設定
PowerMockの設定のポイントは2つ。RunWithがRobolectricに使われていて使えないので、Ruleで設定することと、PowerMockIgnoreを使ってPowerMockの適用範囲からRobolectric関連のクラスを除外すること。

まず、PowerMock関連のjarを入手する。必要なのはPowerMock本体とPowerMockRule関連のjar。
PowerMock本体のjarはpowermock-mockito-junitのzipファイルをダウンロードしてJUnit以外のjarを、RobolectricTestのlibディレクトリにでも置いてビルドパスを設定すればOK。
PowerMockRule関連のjarは、classloadingにxstreamを選ぶこと。具体的には次のjarをダウンロードする。
powermock-module-junit4-rule
powermock-classloading-base
powermock-classloading-xstream
追加でXStreamのjarも必要なので、Binary distributionをダウンロードして次のjarを使う。
xstream
xpp3_min
xmlpull
これらのjarファイルもPowerMock本体のjarと同様、RobolectricTestのlibディレクトリにでも置いてビルドパスを設定すればOK。
設定は後はだいたいこんな感じになる。

で、テストコードは次のように書く。RuleとPowerMockIgnoreの設定がポイント。(ここでandroid.*をignoreしちゃうので、不完全になってしまうのだが、ignoreしないとRobolectricと喧嘩してうまく動かない…ううむ)

この時点で実行してStub!のエラーが出るようであれば、ビルドパスの読み込み順に問題があるので、android.jarを一番最後の読むようにする。多分xmlpullより後にandroid.jarが読まれていれば大丈夫なはずだけど、ややこしいので最後にした方が良さそう。

と、いうことで不完全ながらRobolectricとPowerMockを同時に使う方法でした。
githubにソースを丸ごと上げておいたので参考までに…。