一点の見解:Androidイベント配信メカニズム(二)

9450 ワード

点见解:Androidイベント配信メカニズム(一)-基本概念解釈点见解:Androidイベント配信メカニズム(二)-分析ViewGroup一点见解:Androidイベント配信メカニズム(三)-分析View
本文は主にイベント配布メカニズムの伝達経路と伝達規則を分析し、ViewGroupの分析に重点を置いた.
ソースコードの分析について、みんなはいつも具体的なソースコードを見つけることができると仮定して、だから肝心な部分だけを貼って分析します.
配布元の論理分析
最初から一番はっきりしている.
イベントは最初にシステムによってActivity#dispatchTouchEvent(MotionEvent ev)に配布されます.ActivityViewではないため、コントロール間のイベント配信ロジックにはまだ入っていないことに注意する.では、Activityはどのように事件を最初のViewに伝えたのか、誰に伝えたのか、ソースコードを見てみましょう.
// Activity#dispatchTouchEventpublic 
boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) { 
        onUserInteraction(); 
    } 
    if (getWindow().superDispatchTouchEvent(ev)) {//         
        return true;
    } 
    return onTouchEvent(ev);
}
Activityは単なる中継局であるため、コードは多くなく、キーコードはgetWindow().superDispatchTouchEvent(ev)である.getWindow()Windowの抽象クラスを返します.Androidでは、この抽象クラスを継承している唯一のクラスはPhoneWindowなので、ここで実際に呼び出されたのはPhoneWindow#superDispatchTouchEvent(MotionEvent ev)で、同じPhoneWindowViewではありません.だから、PhoneWindowソースコードも見なければなりません.
// PhoneWindow.javaprivate DecorView mDecor;
@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) { 
    return mDecor.superDispatchTouchEvent(event);
}
// PhoneWindow.DecorView    
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    public boolean superDispatchTouchEvent(MotionEvent event) { 
        return super.dispatchTouchEvent(event); 
    }
}

抜粋したソースコードから結論が得られる
システムは、イベントをActivityに配布する、PhoneWindowに伝達し、PhoneWindowの例のDecorViewに伝達する、DecorViewView(FrameLayoutを継承)であり、その後、イベントはコントロール間伝達ロジックに入る.
ソースBonus
  • は、Activity#dispatchTouchEvent(MotionEvent ev)がイベント配信の開始点であるため、この方法を書き換えるPhoneWindow#superDispatchTouchEvent(MotionEvent ev)を呼び出すことなく、Activity内のコントロール全体がイベントを受信ことができないようにすることができる.実際には、キーボードのクリックイベントなどをブロックできるdispatchXXXEvent()の方法がいくつかある.
  • Activity#dispatchTouchEvent(MotionEvent ev)の中にonUserInteraction()が現れ、この方法はコールバックと見なすことができ、任意のユーザ操作が開始する前にキーボード操作を含めてこの方法が呼び出されるので、この方法を書き換えてユーザ操作の開始ノードを傍受することができる.もう一つの対応方法onUserLeaveHint()
  • Activity#dispatchTouchEvent(MotionEvent ev)においてPhoneWindow#superDispatchTouchEvent(MotionEvent ev)がこのイベントを消費すると、Activity#onTouchEvent(MotionEvent event)が消費イベントを試みるように呼び出される.

  • ViewGroupから
    上記の分析から分かるように、最初にイベントを受信したViewの方法はDecorView#superDispatchTouchEvent(MotionEvent event)で、中にはsuper.dispatchTouchEvent()が直接呼び出され、DecorViewは直接FrameLayoutを継承し、追跡して結論を得ることができる.
    コントロール間イベント転送の開始方法はViewGroup#dispatchTouchEvent()です.
    指摘に値するのは、ViewにもdispatchTouchEvent()があり、後で言う.
    方法の命名から分かるように、この方法の役割はイベントを配布することであるため、イベント配布メカニズムの実現論理はこの方法の中にあり、この部分のコードは200行以上あり、その中の多くのコードはコントロールの状態を一致させたり、多点タッチの問題を処理したりしている.本文はこの部分の実現に関心がないので、分析前に明確に分析する鍵が必要である.
  • イベントを次のコントロールにどのように渡すか、前に述べたブロックなど
  • を含む.
  • 戻り値はイベントが消費されたかどうかを識別するので、戻り値をどのように決定するかは、コードをより明確にするために、ソースコードをセグメントごとに分析し、以下のソースコードはすべてViewGroup#dispatchTouchEvent
  • から来ている.
    伝達規則
    イベントをサブコントロールに配布するため、このメソッドでは必ずサブコントロールを巡回するので、まずこの部分の巡回コードを見つけます.以下のようにします.
    for (int i = childrenCount - 1; i >= 0; i--) {
        //        child    ,           
        children[childIndex] final View child = (preorderedList == null) 
                                    ? children[childIndex] : preorderedList.get(childIndex); 
        //               
        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//      
            // ... 
            newTouchTarget = addTouchTarget(child, idBitsToAssign);//      
            // ... break; 
        }
    }
    

    以上、遍歴の過程で重要な判断の中でViewGroup#dispatchTransformedTouchEventの方法を実行すると、コードが貼られず、一連の判断があるが、結局childパラメータが空であるか否かを判断し、空であるsuper.dispatchTouchEvent()を実行し、空でないchild.dispatchTouchEvent()を実行し、ここでchildは必ず空ではないので、ここでイベントをサブコントロールに伝達する.
    サブコントロールに渡された後、trueに戻って、サブコントロールがこのイベントを受信したことを証明します.注意してください.ここには、現在のこの受信イベントのサブコントロールをViewGroup#addTouchTargetオブジェクトに変換し、TouchTargetに値を付与する別のキーメソッドmFirstTouchTargetがあります.なぜですか.
    この遍歴コードは1つの3重条件判断に含まれているので、実行されない可能性があると言って、判断の条件を見てみましょう.
    if (!canceled && !intercepted) { 
        // ...
        if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 
            // ... 
            if (newTouchTarget == null && childrenCount != 0) { 
                //      
            } 
        }
    }
    

    第1の再判断は、命名に基づいてACTION_CANCELイベントと現在のコントロールによってブロックされたイベントと推定され、その後に議論される.第3の判断は、サブコントロールが存在する限りtrueである.重要なのは第2の判断であり、イベントがACTION_DOWNACTION_POINTER_DOWNまたはACTION_HOVER_MOVEでなければ遍歴コードに入ることができないことを制限している(配布メカニズムについてはACTION_DOWNイベントにしか注目していないので、後で他の2つを省略する)、つまりイベントがACTION_MOVEなどの中間イベントである場合、遍歴コードを直接実行することはなく、サブコントロールにイベントを配布することもなく、ViewGroup#dispatchTransformedTouchEventを呼び出し、残りのコードを探して見つける場所もあります.
    if (mFirstTouchTarget == null) { 
        handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
    } else { 
        // ... 
        TouchTarget target = mFirstTouchTarget; 
        while (target != null) { 
            // ... 
            final TouchTarget next = target.next; 
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { 
                handled = true; 
            } else { 
                final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; 
                if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { 
                    handled = true; 
                } 
                // ... 
            }
            // ... 
            target = next;  
        }
    }
    

    このコードは常に実行され、重要な判断根拠はmFirstTouchTargetが空であるかどうかであり、空である場合、最後にView#dispatchTouchEventが呼び出されます.そうしないと、mFirstTouchTargetに対応するサブコントロールにイベントが渡されます.上記の分析と結びつけて、サブコントロールがACTION_DOWNなどのイベントを受信した場合にのみ、空ではありません.つまり、
    サブコントロールがACTION_DOWNを受信(すなわち、View#dispatchTouchEventACTION_DOWNtrueを返さない)場合、後続のイベントはこのサブコントロールに配布されない.
    空でない場合、mFirstTouchTargetは実際にはチェーンテーブルであり、チェーンテーブルのすべてのサブコントロールにイベントを配布します.これはマルチタッチの処理であり、本稿で注目している問題ではありません.分析しないで、他のACTION_DOWN以外のイベントの伝達がすべてのサブコントロールを再遍歴しないことを知っておく必要があります.ACTION_DOWNは操作全体(一連のイベント)の起点です.このとき、後続のイベントが伝達するサブコントロールが決定される.
    ここまで分析すると,イベント配布メカニズムがどのようにコントロール間でイベントを伝達するかが分かった.
    親コントロールは、すべての子コントロールがACTION_DOWNのイベントを受信するかどうかを尋ねるサブコントロールをチェーンテーブルに保存し、後続のイベントの配布対象を決定し、他のイベントが親コントロールに渡されたときにチェーンテーブルのサブコントロールに直接イベントを渡す.サブコントロール受信ACTION_DOWNがない場合、View#dispatchTouchEventが実行される
    ブロックイベントViewGroupのイベント配信のもう一つのポイントは、上述の遍歴の第1の再判断におけるintercepted変数である、この変数がtrueであれば、ACTION_DOWNイベントであってもサブコントロールに問い合わせを遍歴することはない、このときmFirstTouchTargetチェーンテーブルは必ず空であり、後続のすべてのイベントはView#dispatchTouchEventに伝達され、サブコントロールに伝達されない.つまり、親コントロールは、渡されたイベントをブロックします.
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 
        if (!disallowIntercept) { 
            intercepted = onInterceptTouchEvent(ev);
            // ...
        } else { 
            intercepted = false; 
        }
    } else { 
        intercepted = true;
    }
    

    一般にinterceptedの値はViewGroup#onInterceptTouchEventによって決定するが、Viewにはこの方法はないことが指摘され、Viewには他のサブコントロールがないため、イベントをブロックする必要がないため理解しやすい.
    次にViewGroup#onInterceptTouchEventを見て、中は直接falseに戻って、デフォルトはイベントをブロックしないので、ViewGroup#onInterceptTouchEventを書き換えることによって、特定のイベントをブロックすることができる
    しかし、遮断方法には条件判断がある.
  • は、ACTION_DOWNイベント、またはmFirstTouchTargetが空ではなく、mFirstTouchTargetが最初の消費イベントのサブコントロールである必要があるため、サブコントロールがイベントを消費した場合、その後、ViewGroup#onInterceptTouchEventが呼び出され、親コントロールはイベントをブロックする機会があり、親コントロール自身がACTION_DOWNイベントを消費した場合、ViewGroup#onInterceptTouchEvent
  • を呼び出すことはありません.
  • は、FLAG_DISALLOW_INTERCEPTのフラグビットを設ける必要があり、関連する方法ViewGroup#requestDisallowInterceptTouchEvent、すなわち、この方法を呼び出すことによってブロック機構を無効にすることができる.

  • これでViewGroupの配布メカニズムに関する方法の大まかな分析が完了した.
    ソースBonus
  • は、ViewGroup#requestDisallowInterceptTouchEventによってブロッキング機構を無効にすることができる.
  • は、サブコントロールにAccessibilityFocusedを設定ことにより、サブコントロールを巡回する際に、そのサブコントロールがACTION_DOWNイベントを受信するか否かを優先的に問い合わせることができる.
  • のデフォルトの遍歴順序は、レイアウトにおけるサブコントロールのZ軸値に基づいて決定するが、ViewGroup#getChildDrawingOrderを書き換えてデフォルトのサブコントロール遍歴順序を変更することができる.

  • 本明細書では、サブコントロールがイベントを受信するかどうかにかかわらず、イベントはView#dispatchTouchEventに伝達されることが分かるので、次編ではViewクラスを分析する