Android ソースコード読書会 第4回 Activity.java

というわけで、日本Androidの会福岡支部の中で、Android SDKソースコードを読む読書会(輪講)を隔週でやってまして、今回は第4回目です。Androidの中心とも言えるActivity.javaのライフサイクルを中心にソースコードを読み進めていきました。担当は @kenz_firespeed さんでした。

android.git.kernel.org Git - platform/frameworks/base.git/blob - core/java/android/app/Activity.java

まずはライフサイクルの整理


読みやすいので図解Androidのライフサイクルとプラットフォーム « Tech Boosterさんより引用

  • onStop(), onDestory()は場合によっては呼ばれない可能性があるのでスレッドの終了処理をここに書いちゃダメ。書くとしたらキャッシュの削除とかかな。
  • onCreate()はKillされないと呼ばれないので、ここに全ての初期化処理を書くと呼ばれない事がある。
  • onResume(), onPause()を中心に初期化、終了処理を書くと手堅い。でも、パフォーマンスに難があるので、少しずつ別メソッドに移すと無駄がないかも。
  • finish()を呼ぶと一気にonDestory()が呼ばれ、onPasuse(), onStop()などのライフサイクルは呼ばれないのでこれも注意。Activity.onCreate()のコメントに書いてた。
  • データを保持するBundleに保存するためのonSaveInstanceState(Bundle)とBundleから値を取得するonRestoreInstanceState(Bundle)がある。
  • このライフサイクルをメインに、オーバーライドできるメソッドは結構ある。

Activityはだれが管理しているのか。

ActivityThread.javaです。
android.git.kernel.org Git - platform/frameworks/base.git/blob - core/java/android/app/ActivityThread.java

だいたい以下の流れです。

ActivityThreadにLooperからMessageがhandleMessage(Message)経由で次々に送られてきます。Message.whatで呼び出し理由をswitchで判断してます。case ***:を見るとActivity起動時のLAUNCH_ACTIVITYなどが並んでます。(メモリーが減るとLOW_MEMORYが呼ばれるんだろうなぁ。ふむふむ)

 923         public void handleMessage(Message msg) {
 924             if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what);
 925             switch (msg.what) {
 926                 case LAUNCH_ACTIVITY: {
 927                     ActivityClientRecord r = (ActivityClientRecord)msg.obj;
 928 
 929                     r.packageInfo = getPackageInfoNoCheck(
 930                             r.activityInfo.applicationInfo);
 931                     handleLaunchActivity(r, null);
 932                 } break;
 933                 case RELAUNCH_ACTIVITY: {
 934                     ActivityClientRecord r = (ActivityClientRecord)msg.obj;
 935                     handleRelaunchActivity(r, msg.arg1);
 936                 } break;
 937                 case PAUSE_ACTIVITY:
 938                     handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2);
 939                     maybeSnapshot();
 940                     break;

辿っていくと1611行目でmInstrumentation.callActivityOnCreate()でonCreateを呼んでます。その前後にsetThemeしてたりするので、Activityのライフサイクルの前後に何をしているのか理解できていいですね。

1605                 int theme = r.activityInfo.getThemeResource();
1606                 if (theme != 0) {
1607                     activity.setTheme(theme);
1608                 }
1609 
1610                 activity.mCalled = false;
1611                 mInstrumentation.callActivityOnCreate(activity, r.state);
1612                 if (!activity.mCalled) {
1613                     throw new SuperNotCalledException(
1614                         "Activity " + r.intent.getComponent().toShortString() +
1615                         " did not call through to super.onCreate()");
1616                 }

まとめ

ActivityThreadのソースコード読むのが楽しくて盛り上がったので、Activityを読む回は次回以降にまた行うことになりました。ライフサイクルがわかるとAndroidが可愛く見えてきますね。
あと、InstrumentationはAndroidのテストコード書くときに重要になるので覚えておきたいところです。

次回はIntent.javaです。Activity.startActivity(intent)からの処理を中心に進めていきます。

んじゃまた!

Effective Java 読書会 第1回目のまとめだよ。


しかだよ。Effective Javaの読書会を始めたのでブログにまとめてみるよ。

Effective Java

この本は中級以上のJava技術者になるための必須本ですね。


Effective Java 第2版 (The Java Series)

クリックして買ってね!

輪講

福岡の30歳前後プログラマー @daichan4649 @seisuke @kensei_kick @kenz_firespeed @shikajiro で始めました。早速 @daichan4649 が仕事で来れなかったので、次回、抹茶オレをおごってもらうことにします。 :)

というわけで議事録みたいなまとめ。

第1章 はじめに

前説なので飛ばします。

第2章 オブジェクトの生成と消滅

new Hoge(); について考える章ですね。newとかstaticファクトリーメソッドとかfinalizeとかについて書いてます。

項目1 コンストラクタの代わりにstaticファクトリーメソッドを検討する。

いきなりnew使うなって説明です。staticファクトリーメソッドを使う良さが解説されてます。以下メリット。

1.名前を持つのでオブジェクト生成が分かりやすくなる。

  • new HogeClass(1,2,3);
  • HogeClass.createTriangle(1,2,3);

2.オブジェクトを生成しなくても良いので、パフォーマンスを調整できる。
例えばこんなの風に書けば、instanceは2個以上生成されないのでパフォーマンスが向上する。

 private HogeClass instance;
 public HogeClass createTriangle(int a, int b, int c){
 	if(instance != null){
		return instance;
	}else{
		instance = new HogeClass(a, b, c);
		return instance;
	}
 }
//もちろん適当に書いたサンプルコードだから、このソースコードに有用性があるとかないとかについて突っ込んじゃだめだぜっ。

3.返すクラスを隠蔽化できる。
自分自身のクラスを生成するだけではなく、自分のサプクラスのインスタンスも返せます。

  • extendsの実装をnon publicにして隠蔽化できる。
  • インターフェースさえ同じであれば、どんな実装でもOK。
  • 返すClassは実装してなくてもいい。

ここらへんはインターフェースを活かした実装の話ですね。

4.めんどいパラメータを省略できる。
ジェネリクスを省略できます。

List<String> list = new ArrayList<String>();
List<String> list = HogeList.createInstance();

短所
1.サブクラスは作れなくなる。
privateコンストラクタのクラスは継承できない。でも、継承ではなく委譲を促すのでよしとする。

2.普通のstaticメソッドと見分けがつかない。
そんな時は命名規則に従おう。

valueOf 型変換
of valueOfの省略形
getInstance シングルトンインスタンスを返す
newInstance 毎回別のインスタンスを返す。限りなくnewと同じ。
getType 自分とは別のクラスのシングルトンインスタンスを返す。
newType 自分とは別のクラスのインスタンスを返す。

※鹿訳なので、正確な意味はEffective Java 第2版を買ってね!
この命名規則は役に立つ!ルールに従うのがユーザーも実装者も困らない。

項目2 数多くのコンストラクタパラメータに直面したときにはビルダーを検討する

パラメーターが多いときはbuilderパターンで。
1.テレスコーピングパターン

  • 必須コンストラクタとそれをthis()で呼ぶ別のコンストラクタで構成。
  • 可視性が悪い
  • テレスコーピングパターンって名前を知らなかった。

2.setterでがんばる。

  • 不整合が起きやすい。

3.そんなあなたにビルダーパターン

  • 1.と2.のイイトコどり。
  • 整合性チェックを実行出来る。
  • 防御的コピーとかちゃんとしようね。

AndroidだとAlertDialog.Builderがよく使われますね。

欠点
1.パフォーマンスが落ちる

  • でもちょっと重くなるくらいなので、そんなに気にしない。

2.ソースコードが冗長になる。

  • 4個以上になりそうならbuilderで

項目3 privateのコンストラクタかenum型でシングルトン特性を強制する。

3つのやり方。

1.publicなフィールドにstatic finalで持つ。

public static final Hoge INSTANCE = new Hoge();

2.privateなフィールドのインスタンスをメソッド経由で取得する。

private static final Hoge INSTANCE = new Hoge();
public Hoge getInstance(){
       return INSTANCE;
}

3.enumでやる。
なんだってー!

public enum Hoge{
       INSTANCE;
       public void hoge(){}
}

//使うとき
Hoge.INSTANCE.hoge();

シリアライズの問題も解決しており、これが現時点で最善の実装のようです。

項目4 privateのコンストラクタでインスタンス化不可能を強制する

ここまで読んでる時点で、privateのコンストラクタが登場しまくってますがいいでしょう。
ユーティリティクラスとかでインスタンス化させたくないときに有効なやり方。

項目5 不必要なオブジェクト生成を避ける

パフォーマンス改善のお話。

  • staticファクトリーメソッドで不要なインスタンス生成を防ぐ
  • 不変オブジェクトと変更されない可変オブジェクトの再利用
  • 遅延初期化はそんなにパフォーマンス良いわけじゃないし、可読性も下がるので、無理してしないほうがいい。
  • 自動アンボクシングに注意
  • とはいえ、防御的コピーはきちんとしよう。

まとめとして、不必要なオブジェクト生成は避けたほうがいいけど、あくまでパフォーマンス改善の恩恵があるだけなので、必要な場所で防御的コピーをしてバグやセキュリティホールが出ないようにすることの方が大事。たしかに。

項目6 廃れたオブジェクト参照を取り除く

  • 配列とかの独自のメモリ管理をするときはメモリリークに注意しよう。
  • キャッシュはWeakHashMapで表現するといいよ!
  • リスナーやコールバックにも注意すること。

項目7 ファイナライザを避ける

使っちゃダメ。以上。









ダメな理由

  1. 即座に呼ばれない。
  2. 実行保証すらない。
  3. なぜかパフォーマンスが落ちる。
  4. try finnalyで書け!

そんなファイナライザにもメリットが!!
1. finnaly忘れのセーフティネット。dbのclose忘れでdbがロックされっぱなしになるより、実行保証ないけど、いつかcloseされる方がマシとかかな。警告ログを出力して、プログラマにcloseを喚起すること。
2. ネイティブオブジェクトの開放
ネイティブオブジェクト(Cとかの資源かな?)はガーベッジコレクションでメモリクリアされないので、手動でやる必要がある。などなど、ファイナライザはいろいろ大変なのでEffective Java 第2版を買って読もう!

第3章も途中までやったのですが、疲れたので次回一緒にまとめます。

Handler.javaを読む。

Androidの勉強会でAndroidソースコードを読む会を隔週月曜日の夜に行っています。

Handler.javaを理解する。

throw Life - AndroidのHandlerとは何か?にadamrockerさんがめちゃくちゃわかりやすくまとめて下さってますので、Handlerに興味が有る方はそちらをどうぞw

シーケンス図

僕も簡単にシーケンス図にまとめました。
newしたスレッドはrunnableの処理をpost()でキューに貯めこんで、UIスレッドのループがそれを拾いあげて実行する感じですね。なっとく。

https://cacoo.com/diagrams/6NbKDcInDTisv2vU

macportsからhomebrewへ移行したよ


しかだよ。
macportsだとビルドに大変時間がかかるので、SSDの乗り換えに合わせてhomebrewに移行しました。
http://mxcl.github.com/homebrew/

インストール

https://github.com/mxcl/homebrew/wiki/installation
公式サイトにチュートリアルが乗ってるので説明は不要ですね。ですが、僕のxcodeのバージョンが低くて、エラーになりました。

shikajiro-MacBook:~ shikajiro$ brew install git
Warning: Xcode is not installed! Builds may fail!
==> Downloading http://kernel.org/pub/software/scm/git/git-1.7.5.4.tar.bz2
File already downloaded and cached to /Users/shikajiro/Library/Caches/Homebrew
==> make prefix=/usr/local/Cellar/git/1.7.5.4 install
GIT_VERSION = 1.7.5.4
    * new build flags or prefix
./generate-cmdlist.sh > common-cmds.h+ && mv common-cmds.h+ common-cmds.h
gcc -o hex.o -c   -O3 -march=core2 -msse4.1 -w -pipe -I. -DUSE_ST_TIMESPEC  -DSHA1_HEADER='<openssl/sha.h>'  -DNO_MEMMEM  hex.c
cc1: error: invalid option ‘sse4.1’
hex.c:1: error: bad value (core2) for -march= switch
hex.c:1: error: bad value (core2) for -mtune= switch
make: *** [hex.o] Error 1
make: *** Waiting for unfinished jobs....
==> Exit Status: 2
http://github.com/mxcl/homebrew/blob/master/Library/Formula/git.rb#L31
==> Environment
/usr/bin/gcc
HOMEBREW_VERSION: 0.8
HEAD: (none)
HOMEBREW_PREFIX: /usr/local
HOMEBREW_CELLAR: /usr/local/Cellar
HOMEBREW_REPOSITORY: /usr/local
HOMEBREW_LIBRARY_PATH: /usr/local/Library/Homebrew
Hardware: dual-core 64-bit penryn
OS X: 10.6.7
Kernel Architecture: i386
Ruby: 1.8.7-174
/usr/bin/ruby => /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
Xcode: 
GCC-4.0: build 5465 (5494 or newer recommended)
GCC-4.2: build 401 (5664 or newer recommended)
LLVM: N/A 
MacPorts or Fink? false
X11 installed? true
==> Build Flags
CC: /usr/bin/cc => /usr/bin/gcc-4.0
CXX: /usr/bin/c++ => /usr/bin/c++-4.0
LD: /usr/bin/cc => /usr/bin/gcc-4.0
CFLAGS: -O3 -march=core2 -msse4.1 -w -pipe
CXXFLAGS: -O3 -march=core2 -msse4.1 -w -pipe
MAKEFLAGS: -j2

Error: Failed executing: make prefix=/usr/local/Cellar/git/1.7.5.4 install
Please report this bug: https://github.com/mxcl/homebrew/wiki/new-issue

Also try:
  `brew doctor` to check your setup for common problems.
  `brew missing` to check installed packages for missing deps.

xcodeを3.2.6にしたらインストールできました。※xcodeのダウンロード時間がかかるなぁ・・・。

Androidのシナリオテストツール Robotium やってみた。

昨日、パプテマス Scirocco 触ってたら中はRobotiumというのを使っていたので調べてみたら、そこそこメジャーなシナリオテストツールだったので、触ってみました。

Robotium とは

Androidのシナリオテストを簡単に書けるライブラリです。UIスレッドを意識せずに書けるので、テスト仕様書に近いコードを書くことができます。ブラウザテストツールのSeleniumを意識してるみたいですね。

サンプルプロジェクトを動かす。

とりあえずサンプルプロジェクトを動かします。やることは二つ。

  1. SDKのサンプルアプリ notePad をインポート
  2. ExampleTestProjectをインポート

テスト対象となるプロジェクトはSDKに含まれるsampleのNotePadプロジェクトです。Eclipseにインポートします。
Downloads - robotium - It's like Selenium, but for Android™ - Google Project Hosting から最新のExampleTestProjectを落とします。落としたzipはandroidプロジェクトなのでeclipseでインポートします。

比較してみた

Robotiumを使ったテストコードと使わないテストコードを比較してみました。

Robotiumのソースコード
	public void testAddNote() throws Exception {
		solo.clickOnMenuItem("Add note");
		//Assert that NoteEditor activity is opened
		solo.assertCurrentActivity("Expected NoteEditor activity", "NoteEditor"); 
		//In text field 0, add Note 1
		solo.enterText(0, "Note 1");
		solo.goBack(); 
		//Clicks on menu item
		solo.clickOnMenuItem("Add note");
		//In text field 0, add Note 2
		solo.enterText(0, "Note 2");
		//Go back to first activity named "NotesList"
		solo.goBackToActivity("NotesList"); 
		boolean expected = true;
		boolean actual = solo.searchText("Note 1") && solo.searchText("Note 2");
		//Assert that Note 1 & Note 2 are found
		assertEquals("Note 1 and/or Note 2 are not found", expected, actual); 
	}

なにをやってるのかが一目でわかりますね。テスト以外のコードがないので書きやすく読みやすいです。

Robotiumを使わずに書いた場合。(一部分からないのでコメント。動く保証もないです><)
	public void testNonRobo(){
		ActivityMonitor monitor = new Instrumentation.ActivityMonitor("NoteEditor", null, true);
		getInstrumentation().addMonitor(monitor);
		
		NotesList activity = getActivity();
		sendKeys(KeyEvent.KEYCODE_MENU);
		sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
		
		Activity next = getInstrumentation().waitForMonitor(monitor);
		//Assert that NoteEditor activity is opened
		assertEquals(NoteEditor.class, next.getClass());
		//In text field 0, add Note 1
		EditText note = (EditText) next.findViewById(R.id.note);
		note.setText("Note 1");
		sendKeys(KeyEvent.KEYCODE_BACK);
		//Clicks on menu item
		sendKeys(KeyEvent.KEYCODE_MENU);
		sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
		sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
		//In text field 0, add Note 2
		note.setText("Note 2");
		//Go back to first activity named "NotesList"
		sendKeys(KeyEvent.KEYCODE_BACK);
		//TODO 戻ったあとのActivityの状態を見るのはどうするんだろう・・・。
		//assert(書くの疲れた)
	}

monitorやらInstrumentationやら慣れないとわからないですね。書いてて辛いです。今回はUIを駆使する事がないですが、UIスレッドを意識したテストも書くとどんどんややこしくなっていきます。

まとめ

Robotiumを使えば、簡単にシナリオテストが書けます。とっても便利!でも、

というわけで、まだ信頼性に不安があるので、Robotiumだけでテストを書くというわけにはいかないとのが僕の感想です。自動テストのメインはAndroidSDK標準のテストフレームワークで作った単体テストで行い、サブ的に主要なシナリオテストをRobotiumで作成する、というのはアリかと思います。

RobotiumでAndroidアプリのシナリオテストを自動化する - 遙かへのスピードランナー
という意見もあるので、単体テストをしっかり書いた上で、全体の動きのテストができるといいなーって感じですね。とはいえ、しっかり動くようになるととても協力なツールなので、今後も使っていきたいと思います。

Unable to resolve target 'android-x' until the SDK is loaded.

タイトルのエラーになってしまって、Androidプロジェクトがビルドできません。androidSDKを4、7、8と変えてもだめ、クリーンしてもだめ。
Eclipse再起動したら治りました。なんだったんだろう。

AndroidのUI自動テストツール Scirocco 触ってみた。




テスト大好きしかだよ。Scirocco っていうUIテスト自動化ツールがリリースされたので触ってみました。

Scirocco

Zガンダムパプテマス・シロッコと関係があるかわからないですが、UIテストの後スクリーンショットを撮って保存したり、レポートを出力することが出来るみたいです。さらに、TMSってのを使うと、プロジェクトメンバーと共有もできるらしい。業務支援ですな。
中ではrobotiumを動かしてるので、記述は簡略されてます。

公式サイトのQuick Start をやってみます。
http://code.google.com/p/scirocco/wiki/QuickStartScirocco

Eclipse Plug-in インストール

Eclipse Plug-in があるとは!早速インストールしてみます。URLからインストールできます。
http://184.73.200.19/android/eclipse
Eclipseの設定で、scirocco のAndroidSDKを指定する。(ADTと連携できないのかな?)

サンプルのインポート

http://code.google.com/p/scirocco/downloads/list
から

  • ExampleApplicationProject_v1.0.zip
  • ExampleTestProject_v1.0.zip

をダウンロード。
zip解凍したらeclipseandroidプロジェクトだった。eclipseでインポート。

エラーでござる
  • 文字化けしてました。どうやらExampleプロジェクては shift-jis でコーディングされているようです。utf-8じゃないのか・・・。
  • アノテーションがエラー。Javaコンパイラが1.4になってた。1.5にしましょう。

テスト実行

ExampleTestProjectを Scirocco Junit Test として実行。おお、動く動く。んで、結果が、ExampleTestProjectのsciroccoディレクトリにhtmlとjpg画像が保存されてます。

まとめ

基本的な使い方はrobotiumなので使いやすいし、エビデンスを求める業務では重宝しそうです。余裕があったら導入を推薦してみよう。