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 }
Effective Java 読書会 第1回目のまとめだよ。
しかだよ。Effective Javaの読書会を始めたのでブログにまとめてみるよ。
輪講
福岡の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.テレスコーピングパターン
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 ファイナライザを避ける
使っちゃダメ。以上。
ダメな理由
- 即座に呼ばれない。
- 実行保証すらない。
- なぜかパフォーマンスが落ちる。
- 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.
Androidのシナリオテストツール Robotium やってみた。
昨日、パプテマス Scirocco 触ってたら中はRobotiumというのを使っていたので調べてみたら、そこそこメジャーなシナリオテストツールだったので、触ってみました。
Robotium とは
Androidのシナリオテストを簡単に書けるライブラリです。UIスレッドを意識せずに書けるので、テスト仕様書に近いコードを書くことができます。ブラウザテストツールのSeleniumを意識してるみたいですね。
サンプルプロジェクトを動かす。
とりあえずサンプルプロジェクトを動かします。やることは二つ。
- SDKのサンプルアプリ notePad をインポート
- 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アプリのシナリオテストを自動化する - 遙かへのスピードランナー
という意見もあるので、単体テストをしっかり書いた上で、全体の動きのテストができるといいなーって感じですね。とはいえ、しっかり動くようになるととても協力なツールなので、今後も使っていきたいと思います。
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
テスト実行
ExampleTestProjectを Scirocco Junit Test として実行。おお、動く動く。んで、結果が、ExampleTestProjectのsciroccoディレクトリにhtmlとjpg画像が保存されてます。
まとめ
基本的な使い方はrobotiumなので使いやすいし、エビデンスを求める業務では重宝しそうです。余裕があったら導入を推薦してみよう。