2011年04月29日

[Android] SDカードを示すディレクトリ

先日、OreillyのLinuxハンドブックという本を読んでいて、printenvというコマンドを記憶しました。
その後、Androidアプリの開発を行っていて、「adb shell」でシェルを使うことがあったので、そのときたまたま覚えていたprintenvを使ってみました。
そんなに多くはないけれど、環境変数がずらずらっと表示されました。
その中にひとつ気になる環境変数が。
それが$EXTERNAL_STORAGEです。
初めてその環境変数に気づいたときは、Desireで実行していました。
パスは /mnt/sdcard/ を示していました。

以前、仕事でAndroidアプリの開発を行っていたとき、SMT-i9100(au の タブレット端末)のSDカードのディレクトリを取得するのに困ったことを思い出しました。
SMT-i9100は(というかSamsung製端末?)は、SDカードのパスがちょっと特殊なのです。
通常、SDカードのパスはEnvironment.getExternalStorageDirectory()で取得します。
Desireで実行すればもちろん「/mnt/sdcard/」を返してくれます。「/mnt/sdcard/」はSDカードのルートディレクトリ(っていうの?)を指します。

ところが、SMT-i9100で同じように実行すると、そうはいかないのです。
SMT-i9100のSDカードは「/mnt/sdcard/external_sd/」にあります。
ところが上記APIでパスを取得しようとすると、「/mnt/sdcard/」を返します。
これはSMT-i9100の内部メモリになります。
ファイルの読み書きは普通にできますので、アプリが単体で動く場合には問題ないのですが、物理的なSDカードにアクセスすることを前提としたアプリでは困ることがあります。

そこでこの環境変数です。
SMT-i9100でこの変数の中身が一体何を指しているか。
確認してみたところ、なんと「/mnt/sdcard/external_sd/」が設定されていました。
もし、これがどのAndroid端末も共通の仕様であれば、SDカードのパスを取得するのに困ることがなくなります。
すごい大発見だ、と思ったのですが、一人で確認するのはちょっと難しそうです。
一応、確認用のアプリを作ったので、もし確認いただける方がいらっしゃいましたら、ご協力ください。

・Environment.getExternalStorageDirectory()と$EXTERNAL_STORAGEの値を表示するアプリ
ExternalStorageSample.apk
(ソースはこちら→ExternalStorageSample.zip

現状、以下の端末は$EXTERNAL_STORAGEが正しくSDカードのパスを指すことが分かっています。
・HTC Desire
・SMT-i9100
・Galaxy S

MotorolaのXOOMに関してはSDカードがまだ使用できないせいか、どちらも「/mnt/sdcard/」を指していました。
こちらについてはアップデートを待ってもう一度確認してみたい(させてもらいたい)と思っています。
※6/24追記 コメントで情報いただきまして、アップデートしてAndroid3.1になったXOOM(SDカードが使える)でも「/mng/sdcard/」を指しているとのこと。
環境変数に正しいSDカードへのパスが格納されているんじゃないか、という推測は外れてしまいました。
きちんとしたSDカードへのパスを取得したい人はどうしたら良いのでしょうかね…。


ちなみにアプリから環境変数を取得するには以下のコードで取得できます。
System.getenv("EXTERNAL_STORAGE");
↑は$EXTERNAL_STORAGEを取得するときのコードですので、別の環境変数を取得する場合は適宜変数名を変えてください。

device.png
※こちらは同僚のGalaxy Sで試させてもらったときのスクリーンショット。
タグ:android
posted by t2low at 02:22| Android

2011年04月19日

[Android] AsyncTaskのバグ?

前回(http://blog.tappli.com/article/44407843.html)記事にしたAsyncTaskのバグについてですが、調べても調べても具体的な内容はネタ元と思われる以下のサイトしか出てきません…。

http://groups.google.com/group/android-developers/browse_thread/thread/07ea01892ee7a5f4/9f71428217c2cd44

あと、AsyncTaskの修正履歴を以下から確認してみたのですが、2010/09/10の修正ではThreadPoolExecuterのKeepAliveTimeを10から1に変更しただけで、その他の修正は行われていないっぽいのです。
http://android.git.kernel.org/?p=platform/frameworks/base.git;a=history;f=core/java/android/os/AsyncTask.java;h=5fb1d7c32879352170abbd6c56c1aa81f5fae175;hb=refs/heads/gingerbread
この修正で前回記事にした内容の不具合が直るとは思えないんだけれど…。

もしかしてネタ元が英語だったために、僕が読み間違えている可能性もあると思い、がんばって翻訳してみました。
英語は得意でないので、間違っているところとか、日本語としておかしいところは多々あるかと思いますが、それでも話の大筋としては、下記の翻訳で間違っちゃいないと思っています。
そうするとやっぱり不具合はあったということで、一体どういうことなのよ?という気分です。
onDestroy()でAsyncTaskのcancel()を実行しても、onPostExecute()が呼ばれるというのは本当に起こり得るのかしら?
Froyo以前にバグが残っているとしたら困るなぁ…。

--------------------------------
As far as I can tell, AsyncTask's cancel method doesn't work as advertised.
私が言える範囲では、AsyncTaskのcancel()メソッドは(公示通りに)きちんと動かない。

I can't tell whether this is a bug, or me not understanding AsyncTask's cancel() method.
私はこれがバグかどうかは言えない、AsyncTaskのcancel()メソッドについて理解していない。

I'm asking here instead of StackOverflow because I suspect it's a bug.
私はStackOverflowに代わってここで質問する。バグを疑っているから。


I understand from past discussions here that cancel() isn't meant to actually kill a thread
ここでの過去の議論から、cancel()が実際にスレッドを殺すわけではないことは分かっている。

(though the description of the mayInterruptIfRunning flag sure implies it is).
(mayInterruptIfRunningフラグの説明は、それを意味しているはずなのに)


But I'm also checking for isCancelled() (which is returning false),
私はisCancelled()も確認して(それはfalseを返す)、

and trying to use the onCancelled callback (which isn't getting called), to no avail.
onCancelled()コールバックを使おうとした(それは呼ばれなかった)が、無駄に終わった。

My situation is that I have a (short-running, one small network call) AsyncTask that begins in my Activity's onCreate.
状況しては、私は(1回小さな通信する短い処理の)AsyncTaskをActivityのonCreate()で開始する。

If the user backs out of the screen before it finishes though,
例えば、ユーザが画面を消して終了させるとき、

I need to either cancel the AsyncTask entirely,
完全にAsyncTaskをキャンセルするか、

or at least make sure its onPostExecute doesn't do any meaningful work.
少なくともonPostExecute()が意味のある(?)仕事をしないことを確認したい。

In my activity's onDestroy handler, I call cancel() on the task,
私のActivityのonDestroy()では、タスクのcancel()を呼んでいて、

and then inside the task I have an onCancelled handler that sets a boolean.
そして、そのときタスク内ではonCancelled()を受けてbooleanの値を設定している。

In my onPostExecute handler, I check isCancelled(),
onPostExecute()では、私はisCancelled()をチェックしていて、

and I also check to see whether the boolean has been set from onCancelled, and neither are.
そして、onCancelled()で設定されたはずの真偽値をもチェックしているのに、どちらもダメ。

I'm using the debugger, and the order of operations is:
私がデバッガを使っているときの操作手順は:

1) onDestroy(), calls task.cancel()
1) onDestroy()でAsyncTaskのcancel()を呼ぶ

2) task's onPostExecute runs, isCancelled() returns false,
2) タスクのonPostExecute()が実行され、isCancelled()はfalseを返すので、

so I have no conditional to stop the flow and that's it. onCancelled never runs.
私はタスクを停止させるための条件を得られない。onCancelled()は実行されない。


What am I doing wrong here?
私のやっていることは何か間違っていましたか?

I can't find any way to tell my task not to run its work in onPostExecute.
私はonPostExecute()でタスクが処理を行わないように命令する方法を見つけられない。
--------------------------------
タグ:android AsyncTask
posted by t2low at 09:03| Android

2011年04月18日

[Android] AsyncTask のバグ

AsyncTaskにバグがあると職場の同僚に聞いたのでググってみました。

http://d.hatena.ne.jp/kinneko/20100730/p9
http://amay077.posterous.com/asynctask-cancel-froyo22

症状としては、ActivityのonDestroy()でAsyncTaskのcancel()を呼ぶと、正しくonCancelled()が呼ばれずに、onPostExecute()が呼ばれてしまうということらしいです。
最初のバグ報告(?)はGoogle group(英語)に投稿されたものっぽいのですが、英語がろくに読めないこともあって、不具合の詳しい内容がわかりません…。
ちなみに、バグの修正は2.3でやるよ(やったよ?)ということのようです。

そうなると、2.2までのAsyncTaskを使ったアプリはそれを回避する方法を考慮した作りにしなければなりません。
が、やっぱり詳しい内容がよくわかっていません…。
発生条件はonDestroy()でcancel()を呼んだときだけなのか。onPause()で呼んだら本当に問題ないのか。
回避方法としては、それを考慮しておけばそれだけで大丈夫なのか。
探し方が悪いのかも知れませんが、バグをきちんと理解するだけの情報が得られませんでした。

そこでAsyncTaskをonCreate()で開始させ、onDestroy()でcancel()する簡単なプログラムを書いてみました。
イベントごとにLogを出力するようにしてあります。
試したのはDesire(2.2)です。

確認に使用したソース→AsyncTaskBug.zip

onDestroy()でキャンセルしたとき
04-18 00:30:28.970: VERBOSE/AsyncTaskBug(6820): Activity.onCreate:
04-18 00:30:28.970: VERBOSE/AsyncTaskBug(6820): onPreExecute:
04-18 00:30:29.990: VERBOSE/AsyncTaskBug(6820): onProgressUpdate:0
04-18 00:30:30.990: VERBOSE/AsyncTaskBug(6820): onProgressUpdate:1
04-18 00:30:31.990: VERBOSE/AsyncTaskBug(6820): onProgressUpdate:2
04-18 00:30:32.990: VERBOSE/AsyncTaskBug(6820): onProgressUpdate:3
04-18 00:30:33.990: VERBOSE/AsyncTaskBug(6820): onProgressUpdate:4
04-18 00:30:35.000: VERBOSE/AsyncTaskBug(6820): onProgressUpdate:5
04-18 00:30:35.590: VERBOSE/AsyncTaskBug(6820): Activity.onDestroy:
04-18 00:30:35.600: VERBOSE/AsyncTaskBug(6820): onCancelled
04-18 00:30:36.000: VERBOSE/AsyncTaskBug(6820): onProgressUpdate:6

…。
簡単にバグの確認ができるかと思ったのですが、きちんとキャンセルされてしまいました…。
きちんとといってもonCancelledのあとにonProgressUpdateが来ているのもおかしい気がしますが。
ついでなので、onPause()でcancel()を呼ぶよう修正して再度確認してみました。


onPause()でキャンセルしたとき
04-18 00:31:53.570: VERBOSE/AsyncTaskBug(6907): Activity.onCreate:
04-18 00:31:53.580: VERBOSE/AsyncTaskBug(6907): onPreExecute:
04-18 00:31:54.580: VERBOSE/AsyncTaskBug(6907): onProgressUpdate:0
04-18 00:31:55.580: VERBOSE/AsyncTaskBug(6907): onProgressUpdate:1
04-18 00:31:56.580: VERBOSE/AsyncTaskBug(6907): onProgressUpdate:2
04-18 00:31:57.591: VERBOSE/AsyncTaskBug(6907): onProgressUpdate:3
04-18 00:31:58.590: VERBOSE/AsyncTaskBug(6907): onProgressUpdate:4
04-18 00:31:59.590: VERBOSE/AsyncTaskBug(6907): onProgressUpdate:5
04-18 00:32:00.260: VERBOSE/AsyncTaskBug(6907): Activity.onPause:
04-18 00:32:00.290: VERBOSE/AsyncTaskBug(6907): onCancelled
04-18 00:32:00.600: VERBOSE/AsyncTaskBug(6907): onProgressUpdate:6
04-18 00:32:00.790: VERBOSE/AsyncTaskBug(6907): Activity.onDestroy:

…。
変わりません…。タイミングがちょっと早くなったくらいです…。


うーん、発生条件が違うのか…、それとも発生確率が低いのか…、はたまたDesireでは修正が適用されたものを使っているのか…。
よくわかりません。コードを書けばすぐに確認できると思っていただけに残念です。

というわけで、こんな記事にしておきながら、結局詳しいことはわかりませんでした。
この件についてどなたか教えていただけるととてもありがたいです。
バグの詳細が書かれているサイトがあればURLを教えていただけるだけでも大変助かります。
よろしくお願いします。
タグ:android AsyncTask
posted by t2low at 01:20| Android