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 のデベロッパーコンソール でスタックトレースが確認できる様になる。

android_market01

オートフォーカス失敗

カメラプレビューで、画面をタップするとオートフォーカスするように実装していたのだが、オートフォーカス対応でない機種の場合、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 普及の足枷にならないことを願うとともに、なにかしかの解決策があるとよいなぁと感じました。以上。