Android 機種互換性問題対応顛末記
はじめに
ちょっと前に、スティーブ・ジョブスが、Android と iPhone の関係を、Google が言うように、オープン 対 クローズド なのではなく、分断 対 統合 なんだと言っていた。
『ジョブズ、Google Androidとの競争を語る:オープン対クローズドではなく分断 vs 統合』
分断と統合とは、Android は複数OSバージョン × 各種端末 と環境が「分断」されているのに対し、iPhone は、現バージョンと一つ前のバージョンのOS × Apple 1社 に「統合」されていると。
要するに Android はバラバラで統一されていなくて開発しにくいし不便なんだよと。
まぁジョブスが、例としてあげた、TweetDeck の開発チームがそんなことないと反論してたりもするんですけど。
『ジョブズに援用されたアプリ開発者、「Android分断化の悪夢」を否定』
自分も、どちらかと言えば、オープン 対 クローズド 、もっと具体的に言えば、開発の敷居 低い 対 高いという意味で、Android 派。
開発の敷居が高いとは、言語が、Objective C 云々ということでは無くて、開発に Mac が必要だったり(買えない)、アプリ公開に年間1万円もかかったり、公開に審査があったりという意味で。
Android は、PC (Windows、Linux) OK、アプリ公開初回のみ 2,500円、公開に審査無しと非常に「ゆるい」
ではあるのだけれど、、、
自作の手書きメモアプリを最近電器屋さんに増えてきた Android 端末のホットモックで、Android マーケットからインストールして試してみた(よい子はそんなことしちゃいけません)ら、まぁうごかないうごかない。
まだ、練習段階的なアプリなので、そんな特殊なことをしているつもりは無いのだが・・・うーむ。
とりあえず、どんな風に動かないかというと、
機種 | 動かない具合 |
HTC Desire HD | カメラで強制終了 |
SHARP GALAPAGOS | そもそもメモがとれない |
Dell Streak | 線の太さが異常に細い |
SHARP IS03 | そもそもメモがとれない |
REGZA Phone T-01C | 線の太さが変わらない |
開発時には、HTC Desire(X06HT)で動作確認しているのだが、その他機種では、こんな具合に全滅でびっくりしました。
個人で実機をこんなにそろえる訳にもいかないし・・・メジャーな機種ぐらいサポートしたいし・・・ 悩ましい。
日本アンドロイドの会のメーリングリストでも、『Android Market アプリに「対応機種指定」を』 とトピックに上がっているが、何らかのシステム的な対応があるとよいなぁと思う。
で、そんな中、何とか、電器屋で発生したエラーログを採取したり、友人にIS03実機デバッグさせてもらったりと対応したなかで、気づいた点をメモ。
カメラで強制終了について
カメラのプレビューでもはまったが、機種によっては写真自体の縮小がうまく機能していないようだ。
そのあたりは、調整しながらデバッグする必要があるので、電器屋でデバッグするには無理がある。現時点では目をつぶる。
強制終了が発生したタイミングで情報を送信すると、Android Market のデベロッパーコンソール でスタックトレースが確認できる様になる。
オートフォーカス失敗
カメラプレビューで、画面をタップするとオートフォーカスするように実装していたのだが、オートフォーカス対応でない機種の場合、RuntimeException が発生するようだ(当たり前か・・・)
java.lang.RuntimeException: autoFocus failed
at android.hardware.Camera.native_autoFocus(Native Method)
at android.hardware.Camera.autoFocus(Camera.java:440)
at info.typea.shujiroid.core.CameraActivity$CameraControlView.onTouchEvent(CameraActivity.java:214)
at android.view.View.dispatchTouchEvent(View.java:3778)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:958)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:958)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1716)
at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1124)
at android.app.Activity.dispatchTouchEvent(Activity.java:2125)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1700)
at android.view.ViewRoot.handleMessage(ViewRoot.java:1802)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:143)
at android.app.ActivityThread.main(ActivityThread.java:5068)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
at dalvik.system.NativeStart.main(Native Method)
これは、camera.autoFocus(this); を try ~ catch で囲む対応。例外発生時に、Toast を表示しておくようにした。
パラメーター設定失敗
- java.lang.RuntimeException: setParameters failed
- at android.hardware.Camera.native_setParameters(Native Method)
- at android.hardware.Camera.setParameters(Camera.java:657)
- at info.typea.shujiroid.core.CameraActivity$CameraControlView.surfaceChanged(CameraActivity.java:264)
- at android.view.SurfaceView.updateWindow(SurfaceView.java:538)
- at android.view.SurfaceView.dispatchDraw(SurfaceView.java:339)
- at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
- at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
- at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
- at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
- at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
- at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
- at android.view.View.draw(View.java:6743)
- at android.widget.FrameLayout.draw(FrameLayout.java:352)
- at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:1906)
- at android.view.ViewRoot.draw(ViewRoot.java:1411)
- at android.view.ViewRoot.performTraversals(ViewRoot.java:1167)
- at android.view.ViewRoot.handleMessage(ViewRoot.java:1731)
- at android.os.Handler.dispatchMessage(Handler.java:99)
- at android.os.Looper.loop(Looper.java:123)
- at android.app.ActivityThread.main(ActivityThread.java:4627)
- at java.lang.reflect.Method.invokeNative(Native Method)
- at java.lang.reflect.Method.invoke(Method.java:521)
- at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:876)
- at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:634)
- at dalvik.system.NativeStart.main(Native Method)
カメラの setParameters() で例外が発生している。不正なパラメータを設定したようだ。
もとのソースは、
- Camera.Parameters p = camera.getParameters();
- p.setRotation(90);
- p.setFlashMode(currentFlashMode);
- camera.setParameters(p);
のように、カメラから、Parameters を取得し、変更した後、再度セットし直していたのだが、これだと同時に複数の値を setParameters() するため、一つでも不正な値があると、すべての設定失敗してしまう。
もう少しましな書き方(事前チェックのような)が出来ないものかと思いつつ、以下の様に、一つずつ設定し、失敗したら Toast を表示しながら、パラメータをセットしていく方式に修正した。例外をキャッチしたブロックで、再度getParameters() を呼ぶと、不正な値がセットされていないパラメータが取得出来るため、失敗した値以外はきちんと設定される。
- Camera.Parameters p = camera.getParameters();
- try {
- p.setRotation(90);
- camera.setParameters(p);
- } catch (Exception e) {
- Context context = getApplicationContext();
- (Toast.makeText(context,
- context.getString(R.string.msg_err_rotate_fail),
- Toast.LENGTH_SHORT)).show();
- p = camera.getParameters();
- }
- try {
- p.setFlashMode(currentFlashMode);
- camera.setParameters(p);
- } catch (Exception e) {
- Context context = getApplicationContext();
- (Toast.makeText(context,
- context.getString(R.string.msg_err_flash_fail),
- Toast.LENGTH_SHORT)).show();
- p = camera.getParameters();
- }
IS03 で実機デバッグ
SHARP の機種は、そもそもメモ自体がとれなくて、原因はわからないわ、Android マーケットのコメントで、「IS03 で動きません」と、最低評価されるわ、だったので、友人に実機でバッグさせてもらった。
MotionEvent.getHistorySize() が 0 ?
ACTION_MOVE イベントが発生した場合、イベントが発生する間隔よりも、座標を取得指定間隔の方が短いため、前回のイベントから、今回のイベントまでの間の軌跡が1つ以上格納されている。
線をなめらかに表示するために、ACTION_MOVEイベントが起きた時点の座標だけでなく、その軌跡を利用していたのだが、どうも IS03 (SHARPの機種全般?) では、この値が常に 0 となってしまうように見受けられた。
- public boolean onTouchEvent(MotionEvent event) {
- switch(event.getAction()) {
- :
- case MotionEvent.ACTION_MOVE:
- int n = event.getHistorySize();
- for (int i=0; i < n; i++) {
- line.add(event.getHistoricalX(i),
- event.getHistoricalY(i),
- event.getHistoricalPressure(i),
- event.getHistoricalSize(i));
- }
- :
なので、最初、上記の様に、履歴を線で結ぶコードだったのだが、履歴が存在しないため、」画面を指でなぞっても、line オブジェクトに座標が設定されていなかった。
- public boolean onTouchEvent(MotionEvent event) {
- switch(event.getAction()) {
- :
- case MotionEvent.ACTION_MOVE:
- int n = event.getHistorySize();
- if (n == 0) {
- line.add(event.getX(),
- event.getY(),
- event.getPressure(),
- 1);
- } else {
- for (int i=0; i < n; i++) {
- if (this.isPressureEmurate) {
- pressureEmu += emudiff;
- }
- line.add(event.getHistoricalX(i),
- event.getHistoricalY(i),
- event.getHistoricalPressure(i),
- event.getHistoricalSize(i));
- }
- }
- :
よって、getHistorySize() が得られない(履歴が無い)場合、直接座標情報を設定するように修正。
MotionEvent.getPressure() が 固定値?
この値により、線の太さが変わるように実装してあるのだが、画面をどう押しても、線の太さが変わらない(REGZA Phone も同様な挙動)。IS03でログに値を出力してみたところ、0.39 ・・・ とどうやら固定値が取得されるようだ。対応していないのかしら?
これに関しては、アプリの設定を追加し、疑似筆圧として、ACTION_MOVE中、MotionEvent.getPressure() の戻り値から値を徐々に減じていくという逃げを打つ対応。
Dell Streak の線の太さ対応
最後に、Dell Streak この機種は、線の太い細いは感知している(MotionEvent.getPressure() の値は固定値ではなさそう)のだが、やたら線が細くなってしまう。SDKのリファレンスを見ると、MotionEvent.getPressure() は、 0 から 1 の間の値を返すとのことなのだが、取る値の幅が小さい(?)のだろう。
これもアプリに、感度設定を追加し、MotionEvent.getPressure() で取得した値を増幅することを可能にして逃げた。
まとめ
とりあえず、以上で、カメラ撮影した画像の縮小、画面にフィットさせる機能がおかしい件には目をつぶりつつ、他の不具合には一応対応したつもり。。。
Android アプリ というかモバイル端末用の アプリだと、センサーとかカメラとかの使うところが面白いと思うし、醍醐味だと思うのだが、結構互換性がない(稚拙なプログラミングというか経験不足も多々あると思うが)なぁという印象と懸念を覚えた。
まぁ、SDK のリファレンスに、例えば、0 ~ 1 の間の値を返すとしか書いていないので、固定値 0.3 を返そうが、筆圧によって、値が変わろうが、仕様の範囲ではあるのだろうけれど。
このあたりの互換性のなさが、Android 普及の足枷にならないことを願うとともに、なにかしかの解決策があるとよいなぁと感じました。以上。
確かに、iPhone の敷居の高さは感じます(苦笑)
Apple Store を経由しないと、自作ソフトを自分の実機にすらインストール出来ないのも考えてみるとおかしな話です。