Android事件分發機制原始碼分析之Activity篇
在之前的事件分發分析中,曾提及到View的事件是由ViewGroup分發的,然而ViewGroup的事件我們只是稍微帶過是由Activity分發的。而我們知道,事件產生於使用者按下螢幕的一瞬間,事件生成後,經過一系列的過程來到我們的Activity層,那麼事件是怎樣從Activity傳遞到根ViewGroup的呢?
注:建議先閱讀 Android事件分發機制原始碼分析之View篇 與Android事件分發機制原始碼分析之ViewGroup篇 。
例項程式碼
我們依舊用回之前用過的程式碼。對MainActivity稍作修改。
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private CustomButton mbtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mbtn = (CustomButton) findViewById(R.id.button);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG, "dispatchTouchEvent" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public void onUserInteraction() {
Log.i(TAG, "onUserInteraction");
super.onUserInteraction();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}
}
我們看一下當點選CustomButton的列印情況:
11-01 15:47:52.738 30414-30414/com.example.pc.myapplication I/MainActivity: dispatchTouchEvent0
11-01 15:47:52.738 30414-30414/com.example.pc.myapplication I/MainActivity: onUserInteraction
11-01 15:47:52.739 30414-30414/com.example.pc.myapplication I/CustomLayout: dispatchTouchEvent0
11-01 15:47:52.739 30414-30414/com.example.pc.myapplication I/CustomLayout: onInterceptTouchEvent0
11-01 15:47:52.739 30414-30414/com.example.pc.myapplication I/CustomButton: dispatchTouchEvent0
11-01 15:47:52.739 30414-30414/com.example.pc.myapplication I/CustomButton: onTouchEvent0
11-01 15:47:52.799 30414-30414/com.example.pc.myapplication I/MainActivity: dispatchTouchEvent1
11-01 15:47:52.799 30414-30414/com.example.pc.myapplication I/CustomLayout: dispatchTouchEvent1
11-01 15:47:52.799 30414-30414/com.example.pc.myapplication I/CustomLayout: onInterceptTouchEvent1
11-01 15:47:52.799 30414-30414/com.example.pc.myapplication I/CustomButton: dispatchTouchEvent1
11-01 15:47:52.799 30414-30414/com.example.pc.myapplication I/CustomButton: onTouchEvent1
我們再看一下點選非CustomButton範圍的列印情況:
11-01 15:54:17.025 30414-30414/com.example.pc.myapplication I/MainActivity: dispatchTouchEvent0
11-01 15:54:17.025 30414-30414/com.example.pc.myapplication I/MainActivity: onUserInteraction
11-01 15:54:17.025 30414-30414/com.example.pc.myapplication I/CustomLayout: dispatchTouchEvent0
11-01 15:54:17.025 30414-30414/com.example.pc.myapplication I/CustomLayout: onInterceptTouchEvent0
11-01 15:54:17.025 30414-30414/com.example.pc.myapplication I/CustomLayout: onTouchEvent0
11-01 15:54:17.025 30414-30414/com.example.pc.myapplication I/MainActivity: onTouchEvent0
11-01 15:54:17.051 30414-30414/com.example.pc.myapplication I/MainActivity: dispatchTouchEvent2
11-01 15:54:17.051 30414-30414/com.example.pc.myapplication I/MainActivity: onTouchEvent2
11-01 15:54:17.059 30414-30414/com.example.pc.myapplication I/MainActivity: dispatchTouchEvent2
11-01 15:54:17.060 30414-30414/com.example.pc.myapplication I/MainActivity: onTouchEvent2
11-01 15:54:17.060 30414-30414/com.example.pc.myapplication I/MainActivity: dispatchTouchEvent1
11-01 15:54:17.060 30414-30414/com.example.pc.myapplication I/MainActivity: onTouchEvent1
從結果現象中,我們假設它的ACTION_DOWN事件首先觸發dispatchTouchEvent,然後觸發onUserInteraction,再次onTouchEvent,接著的ACTION_UP事件觸發dispatchTouchEvent後觸發了onTouchEvent,也就是說ACTION_UP事件時不會觸發onUserInteraction。
下面我們去看看原始碼的情況。
原始碼分析。原始碼參考Android API 23 Platform
我們依舊先看一下Activity的dispatchTouchEvent()。
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
好像程式碼比想象中要少一點。首先在第一個條件判斷中,我們可以看到只有是ACTION_DOWN的動作才會去執行onUserInteraction(),驗證了上面列印臺在ACTION_UP動作中沒有執行onUserInteraction()。另外當我們點開onUserInteraction()時,發現它是一個空方法:
/**
* Called whenever a key, touch, or trackball event is dispatched to the
* activity. Implement this method if you wish to know that the user has
* interacted with the device in some way while your activity is running.
* This callback and {@link #onUserLeaveHint} are intended to help
* activities manage status bar notifications intelligently; specifically,
* for helping activities determine the proper time to cancel a notfication.
*
* <p>All calls to your activity's {@link #onUserLeaveHint} callback will
* be accompanied by calls to {@link #onUserInteraction}. This
* ensures that your activity will be told of relevant user activity such
* as pulling down the notification pane and touching an item there.
*
* <p>Note that this callback will be invoked for the touch down action
* that begins a touch gesture, but may not be invoked for the touch-moved
* and touch-up actions that follow.
*
* @see #onUserLeaveHint()
*/
public void onUserInteraction() {
}
從註釋中我們可以瞭解到,當此activity在棧頂時,觸屏點選按home,back,menu鍵等都會觸發此方法。下拉statubar、旋轉螢幕、鎖屏不會觸發此方法。所以它會用在屏保應用上,因為當你觸屏機器 就會立馬觸發一個事件,而這個事件又不太明確是什麼,正好屏保滿足此需求。
繼續往下看。到了第二個條件判斷,先是getWindow()返回的是Window物件,物件mWindow在Activity類中是
mWindow = new PhoneWindow(this);
這個例項化的。而Window是一個抽象類,所以superDispatchTouchEvent()相當於是PhoneWindow的其中一個方法。我們看一下Window中的superDispatchTouchEvent():
/**
* Used by custom windows, such as Dialog, to pass the touch screen event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
從註釋我們看到,不需要實現該方法。我們去看一下PhoneWindow裡看下Window抽象方法的實現吧,如下:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
你會發現在PhoneWindow的superDispatchTouchEvent方法裡又直接返回了另一個mDecor物件的superDispatchTouchEvent方法,mDecor是啥?繼續分析。這參考工匠若水的分析。
在PhoneWindow類裡發現,mDecor是DecorView類的例項,同時DecorView是PhoneWindow的內部類。而我們發現:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
這裡實現的類名稱中說是root view,我們可以通過Android App開發技巧中關於UI佈局優化使用的SDK工具Hierarchy Viewer(用虛擬機器)來驗證一下。
在上面的圖看到我們的xml佈局放置在FrameLayout的佈局中。(從其他的Demo也是統一的效果)。那就說明,我們的xml佈局是載入在一個FrameLayout中。
這裡我們繼續分析一下,上面PhoneWindow的superDispatchTouchEvent直接返回了DecorView的superDispatchTouchEvent,而DecorView又是繼承FrameLayout,FrameLayout又是繼承ViewGroup的。我們看一下DecorView類的superDispatchTouchEvent方法:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
那麼我們可以理清了嗎?方法調來調去,在getWindow().superDispatchTouchEvent(ev)這句中其實本質執行的是一個ViewGroup的dispatchTouchEvent()方法。(這個ViewGroup是Activity特有的root view,也就是id為content的FrameLayout佈局),ViewGroup的dispatchTouchEvent()的內容就是我們前兩篇分析的內容。
然後我們繼續往下看Activity的onTouchEvent()方法。
/**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
我們看一下條件判斷裡面的mWindow.shouldCloseOnTouch(this, event),我們去Window看一下shouldCloseOnTouch():
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}
這裡判斷mCloseOnTouchOutside變數的值及是否為ACTION_DOWN事件,同時判斷event的x、y座標是不是超出Bounds,然後檢查FrameLayout的content的id的DecorView是否為空。其實沒啥太重要的,這只是對於處理window邊界之外的Touch事件有判斷價值而已。一般都是返回false。也就是說onTouchEvent()一般也是返回false。
那麼我們Activity的事件分發基本分析完。總結一下:
我們參照著流程圖來總結:
- 事件到達Activity時,會呼叫Activity#dispatchTouchEvent方法,在這個方法,會把事件傳遞給Window,然後Window把事件傳遞給DecorView根佈局,我們所設定的佈局是它的一個子View。最後再從DecorView傳遞給我們的根ViewGroup。所以在Activity傳遞事件給ViwGroup的流程是這樣的:Activity->Window->DecorView->ViewGroup
- dispatchTouchEvent方法中如果是ACTION_DOWN的情況下會接著觸發onUserInteraction方法。
- 若Activity下面的子view攔截了TouchEvent事件(返回true)則Activity.onTouchEvent方法就不會執行。