FLYING

/* TODO: 気の利いた説明を書く */

Android開発の落とし穴

昨日のエントリーに引き続き,バッドノウハウ的なものを箇条書きでまとめておく。思い付いた順に追加していく予定。

Activity関連

  • 永続化はonPauseで行う*1。詳しくはActivityのライフサイクル図を参照のこと。
  • onPauseと対になっている処理は,onResumeで行うこと。onStart/onStopはあんまり使わない,気がする。
  • DialogはAlertDialog.Builderを使って実装するのが楽。ただし,裏で何らかの処理を行なっている間,ユーザーに操作をさせないために表示するダイアログ(いわゆるProgressDialog)は使わないようにする。DialogではなくActivityを新しく作って表示させることで,いくつかのトラブルを回避できる*2 *3
  • 重いタスクはUIスレッドで処理しない。AsyncTaskなどを使ってワーカースレッドで実行する。ただし,ワーカースレッドからUIを操作するときは,Handlerクラスなどを使ってUIスレッドにキューを送って処理するようにする。
  • Activityはワーカースレッドの面倒を見る責務を持つ。つまり,Activityが終了する際(onPauseする際)は,それまでに起動したワーカースレッドをすべて終了するようにする。
  • Activity間のデータの伝達にはIntentを使う。Intent#putExtra/Intent#getうんたらExtraなどのメソッドが利用可能。
  • (別のアプリがアクティブになったり,画面が回転されたりするなどして)Activityのインスタンスが破棄されたときに,Activityが持っている状態を失わないようにするには,onRestoreInstanceState/onSaveInstanceStateを使う。引数のBundleに対してデータの読み込み・書き込みを行うようにする。
  • launchModeがsingleTaskまたはsingleInstanceに設定されているActivityをstartActivityForResultで起動すると,対象のActivityが生成される前に即座に呼び出し元のonActivityResultが呼び出される(startActivityForResultが使えない)。
  • Activityの多重起動を避けたい場合,launchModeでsingleTopにすることで対応可能。Intent#setFlagsでFLAG_ACTIVITY_SINGLE_TOPを指定した場合も同様*4
  • 外部アプリのActivityを起動する場合は,Intent#setFlagsでFLAG_ACTIVITY_NEW_TASKを指定するようにする*5

View関連

  • Viewのwidthやheightは,Viewが画面上に描画されるまでは値が0になっている。onWindowFocusChangedで引数のflagがtrueのときや,onLayoutの中やonSizeChangedの中でならgetWidth/getHeightで非ゼロの値を取得できる。ただし,onLayoutも最初はwidth/height共に0の状態で呼ばれることがある。
  • うんたらLayout系のViewGroupを継承したクラスは,子ビューをルールに基づいて自動的にレイアウトしてくれる。レイアウト時に使用するルールは,LayoutParamsとして外から与える仕組みになっている。つまり,うんたらLayout系のクラスは,与えられたLayoutParamsを元にonLayout/onMeasureで子Viewのレイアウトを行う*6
  • カスタムViewを作るときは,うんたらLayoutを継承して,onLayout/onMeasureでsuper.onLayout/super.onMeasureを呼ぶことで楽できることがある。逆に,すべて自分でやるのであればうんたらLayoutを継承する必要はなく,ViewGroupを継承すれば必要十分。
  • ほとんどのViewはsetClickable(true),setBackgroundDrawable(StateListDrawable),setOnClickListener(OnClickListener)してやればボタンとして使える。
  • Viewの描画を自前でやるにはonDrawを,ViewGroupの描画を自前でやるにはdispatchDrawをoverrideする。canvasに対してあれこれ操作ができる*7
  • カスタムViewについて。経験上,API 9以降では正常に描画されていても,API 7/API 8ではViewが画面上に描画されないということがよくある*8
  • ViewGroupのlayoutメソッドを呼んでもonLayoutメソッドが実行されない場合があるが,measureメソッドを呼んでからlayoutメソッドを呼ぶと,onLayoutも実行されるようになる*9

Android全般

  • Activityを作ったらAndroidManifest.xmlも更新する*10
  • assetsには容量の大きな非圧縮ファイルは入れることができない(目安は〜1MB)。それ以上の容量であれば,zipにしてassetsに登録し,アプリの初回起動時などにアプリが読み書きできる領域に解凍するようにする。
  • Contextが必要な場面では,大体の場合ApplicationContextを渡しておけば動く。
  • dpとpxの相互変換は本当によく使うので,各自でUtilitityクラスを作るなどして,簡単に計算できるようにしておくのがよい。
  • ViewGroup.FILL_PARENTとViewGroup.WRAP_CONTENTの値もLayoutParamsの生成時に頻出するので,FPやWCみたいな定数としてすぐ参照できる場所に定義しておくと楽。
  • targetSdkVersionよりも上のAPIバージョンでしか定義されていないメソッドが「あれば呼び出す」ときに限り,リフレクションを使ってもよい。そのメソッドがないAPIバージョンで実行するとNoSuchMethodExceptionがthrowされるので,ログに書き出すなりしつつ握り潰す必要がある。

Eclipse/ADBは困ったちゃん

  • Eclipseが言うことを訊かなくなったらプロジェクトのクリーンを実行する。それでもダメならEclipseを再起動する*11
  • apkのインストールがうまくいかないときは「adb uninstall <アプリのパッケージ名>」というコマンドを叩いてから改めてインストールする。apkのインストールがタイムアウトするときは,Eclipseの環境設定から「Android-DDMS」と辿り,タイムアウトまでの時間を長めに設定する。
  • xmlを開いている時にデバッグ実行して謎のxml.outというファイルが出力されてイライラしたら,Eclipseの環境設定から「実行/デバッグ-起動」と辿り,起動操作欄の「常に前回起動した〜」にチェックを入れる。

頼りになる参考サイト(過去ログ読め的なものも含まれる)

間違いなどありましたら,コメント欄などで指摘していただけると大変助かります。Android開発でハマると本当に絶望的な気持ちになるので,Android開発の神みたいな人とお知り合いになりたいです。StackOverflowがなければ今頃死んでいた。

*1:onStop/onDestroyは必ずしも実行されない。プログラマは,onPause以降は何が起きてもいいように,永続化処理をonPauseで行う必要がある

*2:Dialogを使うことで発生する問題としては,画面の回転やAndroid側の設定変更など,Activity外の要因によって不意にダイアログが閉じられてしまう点が挙げられる。Activityであれば,不意にユーザーがホームボタンを押したりしてもonPauseが走るので,そこで中断処理を行うことができる。単なるAlertなど,突然閉じられても困らないDialogであれば,この類の心配は不要

*3:追記:AlertDialog.Builder#showを使って表示したダイアログは画面回転時などに否応なく閉じられるが,ここでメモリリークが発生してしまう。これを回避するには,Activity#showDialog/Activity#onCreateDialogを使ってDialogの管理をActivityに任せるようにする

*4:ただし,該当Activityが既に起動している場合,onCreateは走らず,onNewIntentとonResumeが呼ばれる。既に起動している該当Activityを終了してから新しく該当Activityを起動するには,FLAG_ACTIVITY_CLEAR_TOPを使う

*5:そうしないと,HOMEボタンでアプリをバックグラウンド状態にした後,当該アプリを起動した際に直前に表示されていた外部アプリのAvtivityが前面に表示されてしまう

*6:LayoutParamsは,そのViewの親のクラスに属するものを使う。LinearLayoutの子に与えるLayoutParamsは,LinearLayout.LayoutParamsである必要があり,他のクラスのLayoutParamsだと実行時エラーになる

*7:canvas.save,canvas.rotate,super.dispatchDraw,canvas.restoreの順で呼べば,Viewをまるごと回転したりできる

*8:onLayout/onMeasureできちんと子Viewをレイアウトしてやればまず起きないはず

*9:このとき,measureメソッドに与えるMeasureSpecは適当でよい。例えば,MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)など

*10:manifestに記述されているActivity以外は起動できない

*11:びっくりするほど治る