2011年12月16日金曜日

Honeycomb以降ではMotionEventのACTION_DOWNでevent.get(1)すると死ぬ

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

まさかこんな実装をしているわけがない、と思います。僕くらいです。はい。おおonTouchメソッドよ死んでしまうとは情けない。

こうすると死ぬ


常識的に考えて以下のコードは気持ち悪いですよね。でもAndroid3.0未満、つまりGingerbread以前では問題なく動作します。しかしHoneycomb以降ではjava.lang.IllegalArgumentExceptionが発生してしまいます。エラーメッセージはpointerIndex out of rangeです。なんででしょうね?!

@Override
public boolean onTouchEvent(MotionEvent event) {
 switch(event.getAction() & MotionEvent.ACTION_MASK){
 case MotionEvent.ACTION_DOWN:
  int x = event.getX(1);
  break;
 }
}


ソースを読む


MotionEventのソースを見ると・・・。げぇ全然違うぜぇえええ。

Android2.3.3_r1
public final float getX(
 int return mDataSamples[mLastDataSampleIndex
  + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_X] + mXOffset;
}

Android4.0.1
public final float getX(int pointerIndex) {
        return nativeGetAxisValue(mNativePtr, AXIS_X, pointerIndex, HISTORY_CURRENT);
    }


どう変わったか


上記の4.0.1のソースではもはやnativeメソッド。さすがに実装まで追うのはしんどいんで省略しますが、2.3.3_r1では配列アクセスをしている事がわかります。この配列はMotionEventのコンストラクタで初期化されます。配列サイズはコンストラクタに渡したpointerCount, sampleCountと定数NUM_SAMPLE_DATAを乗じた数となります。

private MotionEvent(int pointerCount, int sampleCount) {
 mPointerIdentifiers = new int[pointerCount];
 mDataSamples = new float[pointerCount * sampleCount * NUM_SAMPLE_DATA];
 mEventTimeNanoSamples = new long[sampleCount];
}

現タッチ数は内部でmNumPointersで管理しています。2.3.3_r1のgetX(int)では利用していません。つまりgetX(int)でアクセスするインデックスに影響を与えません。MotionEventはイベントを受け取るViewで使い回しているので(一応MotionEventのhasCode()で確認した)、コンストラクタで確保された配列をオーバーしない限りは死なない事になります。

操作 実タッチ数 Gingerbread以前 Honeycomb以降
event.getX(0) 2
event.getX(2) 2 ×
event.getX(100000) 2 × ×


結論


今までギリギリ動いていたけどHoneycomb、ICSで爆死、そんな事があるかもしれませんね。怖いわー。




2 件のコメント:

  1. 初心者です。よろしくお願いします。

    見事にはまってしまいました。
    回避策のコーティングを、わかりやすく、お教えいただければ、幸いです。

    返信削除
  2. 通りすがりです。
    ICSから厳しくなったからマルチタッチの数(指の数を数えて)処理を追加してね。

    int count = event.getPointerCount();//指の数を取得
    if(count == 2 && touchMode == TOUCH_MULTI) {
    ・・・・
    }

    返信削除