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 を経由しないと、自作ソフトを自分の実機にすらインストール出来ないのも考えてみるとおかしな話です。