Unmotivated

やる気はない

Android のタッチイベントを理解する(その2)

Activity にタッチイベントが流れてくるまで

前回は View のヒエラルキーのなかをどうやってタッチイベントが伝搬するかを追いました。
参考にした資料には、 Activity#dispatchTouchEvent() からイベントが始まるとありますが、折角なのでそこまではどうなっているかも確認します。

先に概要としてまとめてしまうとこんな雰囲気でした。

Activity に渡るまでのタッチイベント概要

View システムの根っこの部分をきちんと理解できてないので、勘違いがあるかもしれません。 (親子関係が特に怪しい)

参考にしたスタックトレース

206SH(Android 4.2.2) でとったものなので、完全に純正なコードでない可能性もありますが参考にしつつ追っていきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
12-02 22:58:25.726: D/TOUCH_TEST(27219):
    at onTouch(TouchTest.java:35)
    at dispatchTouchEvent(View.java:7319)
    at dispatchTransformedTouchEvent(ViewGroup.java:2205)
    at dispatchTouchEvent(ViewGroup.java:1948)
    at dispatchTransformedTouchEvent(ViewGroup.java:2205)
    at dispatchTouchEvent(ViewGroup.java:1948)
    at dispatchTransformedTouchEvent(ViewGroup.java:2205)
    at dispatchTouchEvent(ViewGroup.java:1948)
    at dispatchTransformedTouchEvent(ViewGroup.java:2205)
    at dispatchTouchEvent(ViewGroup.java:1948)
    at dispatchTransformedTouchEvent(ViewGroup.java:2205)
    at dispatchTouchEvent(ViewGroup.java:1948)
    at dispatchTransformedTouchEvent(ViewGroup.java:2205)
    at dispatchTouchEvent(ViewGroup.java:1948)
    at dispatchTransformedTouchEvent(ViewGroup.java:2205)
    at dispatchTouchEvent(ViewGroup.java:1948)
    at superDispatchTouchEvent(PhoneWindow.java:1974)
    at superDispatchTouchEvent(PhoneWindow.java:1426)
    at dispatchTouchEvent(Activity.java:2417)
    at dispatchTouchEvent(PhoneWindow.java:1922)
    at dispatchPointerEvent(View.java:7504)
    at deliverPointerEvent(ViewRootImpl.java:3356)
    at deliverInputEvent(ViewRootImpl.java:3301)
    at doProcessInputEvents(ViewRootImpl.java:4436)
    at enqueueInputEvent(ViewRootImpl.java:4415)
    at onInputEvent(ViewRootImpl.java:4507)
    at dispatchInputEvent(InputEventReceiver.java:179)
    at nativePollOnce(MessageQueue.java:-2)
    at next(MessageQueue.java:125)
    at loop(Looper.java:124)
    /**
     * ↑ActivityThread の Looper にイベントが登録されてくるようなので、ここからみていく
     * ----
     * ↓ここまでは Activity が Zygote によって立ち上げられている関係上、スタックトレースにのってきているっぽい
     */
    at main(ActivityThread.java:5159)
    at invokeNative(Method.java:-2)
    at invoke(Method.java:511)
    at run(ZygoteInit.java:810)
    at main(ZygoteInit.java:577)
    at main(NativeStart.java:-2)

Activity 配下の View へとタッチイベントがわたるまで

このあたりのメソッドの引き回し方は、マイナバージョンの変更でもちょこちょこ変わっているようで、たとえば 4.2.2 と 4.3 でも違いがありました。
大枠の流れまでは変わっていなそうでしたので、スタックトレースと比較するために 4.2.2 でコードを追っています。

  • どこかの誰かが ActivityThread の Looper にイベントを登録してくる(どこの誰かは今のところ追えてません)
  • ActivityThread の Looper がメッセージを dispatch していくなかで、入力イベントを ViewRootImpl の onInputEvent() に渡す
  • ViewRootImpl にて、入力イベントのキューを順に処理していく
ViewRootImpl#deliverInputEvent()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void deliverInputEvent(QueuedInputEvent q) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent");
    try {
        if (q.mEvent instanceof KeyEvent) {
            deliverKeyEvent(q);
        } else {
            final int source = q.mEvent.getSource();
            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                // タッチイベントは SOURCE_CLASS_POINTER に分類されるため、 deliverPointerEvent() が呼ばれる
                // ここに分類されるのはマウス、ペン、タッチ、トラックボールなどらしい
                deliverPointerEvent(q);
            } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                deliverTrackballEvent(q);
            } else {
                deliverGenericMotionEvent(q);
            }
        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}
  • キューからイベントを抽出して、 DecorView にポインタイベントとして流す
ViewRootImpl#deliverPointerEvent()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void deliverPointerEvent(QueuedInputEvent q) {
    final MotionEvent event = (MotionEvent)q.mEvent;

    // 中略

    // タッチイベントを View ヒエラルキーに流していく
    // 通常、 mView には Activity のルートビューとなる DecorView が入っているはず
    boolean handled = mView.dispatchPointerEvent(event);
    if (MEASURE_LATENCY) {
        lt.sample("B Dispatched PointerEvents ", System.nanoTime() - event.getEventTimeNano());
    }

    // タッチイベントの処理が返ってきたら、イベントキューを完了させる
    // handled で分岐しているが native method に入ってしまうので、今回は追ってません
    if (handled) {
        finishInputEvent(q, true);
        return;
    }

    // Pointer event was unhandled.
    finishInputEvent(q, false);
}
  • DecorView の親クラス(View)でタッチイベントと判定して dispatch する
View#dispatchPointerEvent()
1
2
3
4
5
6
7
8
public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        // 今回はタッチイベントなのでこちら
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}
  • DecorView にて Callback として登録されている Activity に、一旦イベントを流す
PhoneWindow.DecorView#dispatchTouchEvent()
1
2
3
4
5
6
public boolean More dispatchTouchEvent(MotionEvent ev) {
    // callback には通常 Activity が登録されている
    final Callback cb = getCallback();
    return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
            : super.dispatchTouchEvent(ev);
}
  • Activity にてユーザからの入力があったことを表す onUserInteraction() などを呼びながら、再度 DecorView にイベントを dispatch し、子供の View (Activity#addContentView()したViewたち)へとタッチイベントを流していく
Activity#dispatchTouchEvent()
1
2
3
4
5
6
7
8
9
10
11
12
public boolean dispatchTouchEvent(MotionEvent ev) {
    // キータッチの開始なら onUserInteraction() が呼ばれる
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    // ここで一旦 DecorView(FrameLayoutを継承) にイベントを戻し、ここから ViewGroup の dispatchTouchEvent() が呼ばれて、タッチイベントの旅が始まる
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    // イベントがキャンセルされずにココまで到達したら、最後に Activity のタッチイベントが呼ばれる
    return onTouchEvent(ev);
}

Comments