Android 事件分發溯源詳解 | 開發者說·DTalk

語言: CN / TW / HK

本文原作者: BennuC, 原文 釋出於: BennuCTech。

Android 事件分發機制大家都非常熟悉,大部分文章對這個過程的描述都是開始於 Activity,但是事件是怎麼傳到 Activity 的?

這裡就涉及到幾個重要的部分: Window,WMS,ViewRoot 和 DecorView。

如果要理解事件分發的源頭,就需要搞明白他們之間的關係,所以我們先來看看它們到底有什麼關係?

Window

Window 是我們比較熟悉的,那麼它是如何建立的?

我們來看 Activity 的 attach 函式:

@UnsupportedAppUsage
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
attachBaseContext(context);
...


mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
...


mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
...
mWindowManager = mWindow.getWindowManager();
...
}

我這裡只展示一部分關鍵程式碼。當我們的 activity 建立完成後會執行 attach ,這時可以看到建立了 PhoneWindow,同時也設定了 WindowManager。

注意 mWindow.setCallback(this); 這行程式碼,Activity 本身實現了 Window.Callback 介面:

public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, ... {

這裡將 activity 賦值給 window 的 callback,在後面的流程中會發揮作用。

DecorView

DecorView 是整個佈局的最頂端的 view,也就是根佈局。它很容易與 ViewRoot 搞混,ViewRoot 其實不是 View,後面再來說它。

DecorView 是如何建立的,這一切要從 setContentView 說起:

public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

可以看到執行了 window 的 setContentView ,它的原始碼:

    @Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}


...
}

可以看到一開始就執行 installDecor ,這裡就是初始化 DecorView:

   private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);


...
}
}

可以看到先通過 generateDecor 建立了 DecorView:

    protected DecorView generateDecor(int featureId) {
Context context;
...
return new DecorView(context, featureId, this, getAttributes());
}

建立時將 Window 也傳入了,所以 DecorView 中儲存了一份 Window 的引用。

然後回到 installDecor 程式碼中,又執行了 generateLayout ,這裡建立了 mContentParent :

    protected ViewGroup generateLayout(DecorView decor) {
...


ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...


return contentParent;
}

可以看到這個 mContentParent 就是 ID_ANDROID_CONTENT,所以它才是真正裝載我們通過 setContentView 所設定的佈局那個 ViewGroup。所以這個層級應該是:

DecorView -> mContentParent -> 實際佈局

ViewRoot

通過上面可以看出,Window 的建立是在 attach 環節,而 DecorView 則是在 create 環節。目前雖然建立了 DecorView,但是還沒有真正新增到 Window 中,而且 ViewRoot 還沒有創建出來,這兩步實際上是一起的,下面來看一下。

Activity 建立完成後會通知 AMS,AMS 處理一些事務後會通知 Activity 顯示,這樣就會執行 ActivityThread handleResumeActivity() :

    @Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
...


if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
} else if (!willBeVisible) {
...
}


...
}

這裡會通過 WindowManager addView 函式將 DecorView 新增到螢幕上。 WindowManager 的實現類是 WindowManagerImpl ,它的函式程式碼如下:

    @Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}

實際上是執行了 WindowManagerGlobal addView :

    public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
...


ViewRootImpl root;
View panelParentView = null;


synchronized (mLock) {
...


root = new ViewRootImpl(view.getContext(), display);


view.setLayoutParams(wparams);


mViews.add(view);
mRoots.add(root);
mParams.add(wparams);


// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
...
}
}
}

這裡我們看到建立了 ViewRootImpl ,這就是 ViewRoot。然後將 DecorView 也傳入了,這樣 ViewRoot 就持有了 DecorView。

那麼 ViewRoot 到底是什麼?

我們可以看成是管理 DecorView 的一個類,比如它初始化的時候得到了一個 WindowSession:

    public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(),
false /* useSfChoreographer */);
}

通過 WindowSession 可以與 WMS 進行通訊實現一些視窗資訊的傳遞。

另外在它的 setView 中還建立了一個 WindowInputEventReceiver :

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
...
InputChannel inputChannel = null;
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();
}
...
try {
...
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
mTempControls);
...
} catch (RemoteException e) {
...
} finally {
...
}
...
if (inputChannel != null) {
...
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());


...
}
}
}
}

這個就是用來接收事件的,下面我們來順著這個看看事件是如何分發到 view 的。

事件源頭

上面建立 WindowInputEventReceiver 時,可以看到傳入了一個 InputChannel ,它建立之後又傳入了 WindowSession,即 WMS。 InputChannel 就是底層通知上層事件的核心,系統服務捕獲到螢幕事件後,會通過它通知到上層,也就是 WindowInputEventReceiver

所以 WindowInputEventReceiver 是整個事件的源頭:

    final class WindowInputEventReceiver extends InputEventReceiver {
...


@Override
public void onInputEvent(InputEvent event) {
...
if (processedEvents != null) {
if (processedEvents.isEmpty()) {
// InputEvent consumed by mInputCompatProcessor
finishInputEvent(event, true);
} else {
for (int i = 0; i < processedEvents.size(); i++) {
enqueueInputEvent(
processedEvents.get(i), this,
QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
}
}
} else {
enqueueInputEvent(event, this, 0, true);
}
}


...
}

事件進入它的 onInputEvent 後會執行 enqueueInputEvent :

    void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
...
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}

這裡通過判斷立即執行還是延遲處理,結果差不多,來看立即執行的程式碼:

    void doProcessInputEvents() {
// Deliver all pending input events in the queue.
while (mPendingInputEventHead != null) {
...
deliverInputEvent(q);
}


...
}

進入 deliverInputEvent :

    private void deliverInputEvent(QueuedInputEvent q) {
...
try {
...


InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}


...


if (stage != null) {
handleWindowFocusChanged();
stage.deliver(q);
} else {
finishInputEvent(q);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

可以看到通過 stage 進行了 deliver ,這個 stage 是什麼?

setView 的最後有這麼一段程式碼:

// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);


mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;

可以看到是一個套一個,我們重點來看 ViewPostImeInputStage 這個:

    final class ViewPostImeInputStage extends InputStage {
...


@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}


...


private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;


mAttachInfo.mUnbufferedDispatchRequested = false;
mAttachInfo.mHandlingPointerEvent = true;
boolean handled = mView.dispatchPointerEvent(event);
...
return handled ? FINISH_HANDLED : FORWARD;
}


...
}

InputStage 的 deliver 最終會通過 onProcess 來區分事件處理 (這塊就不細說了,沒意義),其中我們最關心的事件交給了 processPointerEvent 來處理。在這裡可以看到執行了 mView.dispatchPointerEvent(event) ,這個 View 是我們提到的 DecorView。這樣我們總算找到了源頭,下面看看是怎麼傳遞下去的。

事件傳遞

dispatchPointerEvent 這個函式是 View 的一個函式,原始碼:

public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}

到了我們熟悉的 dispatchTouchEvent 了,這樣直接就將事件分發到各個 View 了?並不是,來看看 DecorView 中這塊是如何處理的:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

可以看到它通過 Window 獲取了 callback,然後執行了 callback 的 dispatchTouchEvent

不知道大家 是否 還記得我們一開始分析 Window 的建立的時候提到過 (不記得可以回到文章開始看一下),Activity 本身實現了 Window.Callback 介面,並設定給了 window 的 callback。所以這裡的 callback 其實就是 Activity,這樣事件就傳遞到了 Activity:

public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

Activity 中之間將事件傳遞給了 Window,呼叫了它的 superDispatchTouchEvent 函式,實際上是 PhoneWindow 的實現:

    @Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}

這樣就又傳遞迴 DecorView 了:

public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}

在 DecorView 中執行了 super.dispatchTouchEvent(event); ,它的父類就是 ViewGroup,這樣就進入了我們熟悉的 ViewGroup 分發事件的環節了。

setCallBack

通過上面的分析,我們徹底理解了事件是怎麼傳遞到 Activity,然後又如何分發到 View 上的。

但是這裡有一個需要注意的點,就是 Window 的 setCallBack 函式是對外的,我們可以設定一個自定義的 CallBack,但是這樣導致 Activity 這個 CallBack 被擠掉,結果就是 Activity 無法接收到事件。

那麼事件還能不能分發下去了呢?我們來看看:

getWindow().setCallback(new Window.Callback() {
...


@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return false;
}


...
});

這裡不論我們返回 true 還是 false,事件都不會分發下去。根據上面分析我們知道,在 Activity 中是呼叫了 getWindow().superDispatchTouchEvent(event); 才讓事件繼續分發的。所以這裡我們也可以加上這樣的程式碼,當然最好是呼叫 Activity 的 dispatchTouchEvent ,保證流程的完整。

總結

經過上面的分析,我們知道事件傳遞路徑大致是:

ViewRoot -> DecorView -> Activity(Window.CallBack) -> Window -> DecorView -> ViewGroup -> ...

後面就是我們熟悉的事件分發流程。

長按右側二維碼

檢視更多開發者精彩分享

"開發者說·DTalk" 面向 中國開發者們徵集 Google 移動應用 (apps & games) 相關的產品/技術內容。歡迎大家前來分享您對移動應用的行業洞察或見解、移動開發過程中的心得或新發現、以及應用出海的實戰經驗總結和相關產品的使用反饋等。我們由衷地希望可以給這些出眾的中國開發者們提供更好展現自己、充分發揮自己特長的平臺。我們將通過大家的技術內容著重選出優秀案例進行 谷歌開發技術專家 (GDE) 的推薦。

  點選屏末  |  閱讀原文  | 即刻報名參與  " 開發者說 · DTalk"