2011年10月31日月曜日

Android携帯で音声をMP3で録音したいとき。

Androidでは、音声を各種フォーマットで録音することができます。
録音に使えるクラスはいくつかあります。

ここのページがよくまとまっているかと。


これらのクラスについて調べてみると、さまざまなデータフォーマットでの録音に対応しているのですが、MP3に対応しているクラスは存在しません。

では、Android端末で音声をMP3フォーマットで保存したいときはどうするかというと、LAMEを使います。

LAME。懐かしい響き。
MP3によるリッピングが流行ったころに、お世話になった方もいらっしゃるのではないでしょうか。

次のサイトでは、AndroidでLAMEを使用するサンプルが公開されています。


このサンプルを流用して、独自のプログラムを作成するときに気をつけなければならないことは、次のとおり。
  1. libmp3lame.soは、プロジェクトルートからlibs/armeabi/libmp3lame.soとして保存しなければならない。
  2. SimpleLame.javaやRecMicToMp3.javaのパッケージはそれぞれ、com.uraroji.garage.android.lameとcom.uraroji.garage.android.mp3recvoiceでなければならない。
soファイルの保存先やパッケージ名を変えたいときは、LAMEをNDKでコンパイルしなおさなけばなりません。
コンパイルの方法は、サンプルのドキュメントに示されています。

なお、MP3(LAME)についてはこんな話もあるので、知っておいて損はないかと。


ここの「mp3というフォーマットと特許・著作権

まとめ:Android端末でのMP3録音はLAMEで。ただし、MP3というフォーマットの特許・著作権については知っておこう。

2011年10月26日水曜日

onActivityResultイベントが発生するタイミング

Androidアプリケーション開発のうえで調べたこと。

Windowsアプリケーションでは、フォームやダイアログをモーダルもしくはモーダレスで表示できますが、Androidのアクティビティにも同じような考え方があります。

まず、フォームやダイアログの表示方法と戻り値の有無について整理しておきましょう。

  • Showメソッドによってモーダレスで表示したフォームもしくはダイアログは、呼び出し元に戻り値を返すことはありません。
  • ShowDialogメソッドによってモーダル表示したフォームもしくはダイアログは、呼び出し元に戻り値(DialogResult)を返します。

Androidでも、アクティビティからアクティビへ遷移するときに、同じような区別があります。
ここで遷移元のアクティビティをA、遷移先のアクティビティをBとします。

  • AでStartActivityメソッドを使ってBへ遷移したとき、AはBから戻り値を取得できません。
  • AでStartActivityForResultメソッドを使ってBへ遷移したとき、AはBから戻り値を取得できます。また、Bが非表示になると基本的にはAがアクティブになります。

ここまでは、WindowsアプリケーションとAndroidアプリケーションでとてもよく似ていますが、異なることがあります。それは、AndroidアプリケーションでStartActivityForResultメソッドを使ってアクティビティを表示したときの挙動です。

呼び出し先であるBから、呼び出し元であるAへ制御が戻るときに発生するイベントが存在するということです。

発生するイベントは、onActivityResultです。

では、ここで問題。

onActivityResultは、下記ライフサイクルのどこで発生するでしょうか?



正解は、「onResumeの前」です。

このonActivityResult→onResumeという発生順は次のとおり保障されています。

http://developer.android.com/intl/ja/reference/android/app/Activity.html#onActivityResult(int, int, android.content.Intent)

上記解説に"You will receive this call immediately before onResume() when your activity is re-starting."とあります。

onActivityResultがonResumeより前に呼び出されることを利用すれば、たとえばAというアクティビティがBというアクティビティをstartActivityForResultで呼び出したときに、Bからの戻り値でAの画面表示を変更するということも可能になります。

もう少し具体的に書くと、Aの画面表示処理で、Bから戻ってきたときにだけ実行したい処理があるときに、onResumeではなくonActivityResultに書くとスマートです。

onResumeは、あらゆるアクティビティからの遷移で発生する可能性がありますが、onActivityResultであれば、startActivityForResultで遷移したアクティビティからの戻りによる発生に限定できますので。

まとめ:アクティビティからの戻り値を利用した画面表示をしたいときは、StartActivityForResultとonActivityResultのコンビを使うと便利。onActivityResultがonResumeの前に発生することは保障されている。

2011年10月25日火曜日

Bitmap(特に写真)を表示するアクティビティでOutOfMemoryError

Androidのアプリケーション開発ではまった罠の第二弾。


おそらく、カメラで撮影した写真を表示するアプリケーションを開発したことがある方であれば、「あるある」と思っていただけるのではないかと。


機種にもよりますが、Android携帯で撮影した写真は、jpeg画像であっても数MB程度のサイズであることがあります。


PCで数MB程度の画像を表示することは、大したことではないのですが、Android携帯では問題になることがあります。


これまた機種にもよりますが、開発者が使えるメモリ空間がメガバイト単位でしか存在しないわけですし、そのメモリ空間は自分が開発するアプリ以外も利用するわけです。


そんな状況で、撮影した写真をサイズを小さくすることなく、何枚も読み込んだら何が起きるかはお分かりかと思います。メモリ不足です。


「何枚も読み込んだら」と言いましたが、これには「同じ画像を何度もファイルから読み込んだら」も含まれる場合があります。


実際に経験したケースが、次のようなものです。


撮影した写真をイメージビューに表示するアクティビティAがあるとします。このアクティビティAには、別のアクティビティBから遷移してきます。


アクティビティAを作成し、単体テストをしているとき、たまたま操作を誤ってB→A→B→Aという遷移を2、3回繰り返したところ、EclipseのLogcat上に「OutOfMemoryError」が出力されました。


原因は明らかです。アクティビティAで大きな画像を読み込むことで、メモリが不足したのです。もちろん、AからBへ遷移するときには、写真(Bitmapオブジェクト)への参照はすべて廃棄していました。が、それでもOutOfMemoryError。


当たり前のことですが、参照を破棄したからといって即GCが自動実行されるわけではありません。このケースでは、画像を読み込む→参照をすべて破棄→画像を読み込む→参照をすべて破棄がGCが発生する前に繰り返されたため、メモリ不足になったことが原因でした。


解決法は簡単。GCを明示的に実行すれば良いだけのことです。


画像の読み込みと破棄、GCについてはこちらの記事が良質ですので、Bitmapを扱うアプリケーションを作る前に一度は読んでおくことをオススメします。


http://groups.google.com/group/android-group-japan/browse_thread/thread/fa40fe4d250541f5?pli=1


まとめ:資源は有限!

Application#onTerminateは、実機では呼ばれない。

Androidのアプリケーション開発ではまった罠の第一弾。

Androidでアプリケーションを開発していると、アプリケーションのアクティビティから共通してアクセスできる変数(データ)が欲しくなることがあるかと思います。

もちろん、アクティビティからアクティビティへ渡るたびに、シリアライズしたデータを渡してあげてもよいのですが、それではスマートなコードが書けないこともあるかと思います。

そうしたときに便利なのがApplicationクラスです。Applicationクラスを継承してメソッドや変数を実装してあげれば良いです。

たとえば、ここなんかを参考にすればよろしいかと。


このApplicationクラス、魅力的なメソッドを備えています。


「この2つのメソッドを使えば、アプリケーション開始および終了時に特定の処理をさせることができるじゃないか!」と、開発しているアプリケーションの仕様によっては、心わきき立つこともあるかと思います。

が、その気持ち、おさえましょう。ぐっと。

まず、onTerminate()の説明を読んでみてください。

エミュレータでは、onTerminate上のコードが実行されるけど、実機では実行されないよという旨のことが書かれているかと思います。

さらに悪いことに、こんなことが書いてあったり。
下記サイトの「シングルトンクラス」の部分を読んでみてください。


これだけ読んだら(そんな人はいないかもしれませんが)、onCreateとonTerminateが実用的なイベントハンドらに見えてしまいます。が、実のところそうではないと…。

そもそも、Androidのアプリケーションは、ユーザーが明示的かつクリーンに終了することを考えていません。これは、「どのアプリケーション(タスク)を終了するかは、OSが決めること」という思想のもとに成り立っています。その恩恵として、アプリのアイコンをタップすると、一時停止状態にあったアプリケーションのアクティビティがサッと起動するように見えるわけですから。

初心者本であれば、まず例外なくどの本にも書かれているアクティビティのライフサイクルですが、「Androidでは、アプリケーションではなくアクティビティ単位でライフサイクルを管理しているんだ」という視点で一度は読んでみることをおすすめします。

このライフサイクルの言わんとしていることが分かれば「System.exit()使えばアプリケーション終了できるじゃん」なんて言う考えは起きないかと。

まとめ:Androidで明示的にアプリケーションを終わるなんていう考えは捨てましょう。習うより慣れろとは言いますが、慣れるより習うことが大事な場合もあります。