Android 5.1触摸事件的传递机制深度剖析(上)

非原创,转自blog.waynell.com,转载请注明原出处

在Android的View系统中,TouchEvent消息的传递是非常重要的,只有深入TouchEvent消息的传递机制才能更好的理解一些View组件的原理和编写自定义View控件。本文基于Android 5.1源码进行分析。

Android系统的消息处理过程,大致可以如下

触摸消息 ---> 消息处理前端 ---> 窗口管理系统 ---> 应用窗口

当用户通过触摸屏等输入设备产生触摸信息,该消息会首先被消息处理前端转换为更明确的消息,比如DOWN/UP消息。然后通过窗口管理系统(WindowManagerService)根据消息的位置坐标去匹配所有的窗口,判断该坐标的落地窗口的哪个区域中,则把该消息下发给相应的窗口。最后窗口把该消息下发给根View,如果根View没有消费该消息则下发给子View,这样层层传递下去。

从源码开始分析触摸消息的传递机制

Let’s reading the fucking souce code.

首先从Activity开始分析,看WindowManagerService是如何将消息分发给应用层的。

  • Activity   它有两个关键点

    • Window mWindow 成员变量,Window是一个顶级窗口抽象类,它的实现类是 PhoneWindow,在Activity的 attach() 函数中通过 mWindow = PolicyManager.makeNewWindow(this) 获得,PhoneWindow有一成员变量 DecorView mDecor

    • 实现了 Window.Callback 接口,它包含了若干事件回调函数,其中就有 dispatchTouchEvent(MotionEvent event) 函数,并在Activity的 attach() 函数中通过 mWindow.setCallback(this) 注册了回调

  • DecorView   它继承于FrameLayout,它是Activity的顶级Layout,通过 setContentView() 加入到Activity的View其实都是被加入到该Layout中,它负责了触摸消息的向下分发

  • ViewRootImpl   在Android3.0以前叫ViewRoot,它不是一个View,它是用来沟通WindowManagerService,它想wms中注册了消息接收通道(InputChannel)处理来自底层的事件消息。这些都在函数ViewRoot.setView中完成

/**
 * ViewRootImpl.java
 */
public final class ViewRootImpl implements ViewParent, 
    View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {

    /**
     * We have one child
     */
    public void setView (View view, WindowManager.LayoutParams attrs, View panelParentView){
        // ......
        mView = view;

        // Schedule the first layout -before- adding to the window manager, to make sure we do the relayout before receiving any other events from the system.

        requestLayout();
        if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
            mInputChannel = new InputChannel();
        }
        try {
            mOrigWindowType = mWindowAttributes.type;
            mAttachInfo.mRecomputeGlobalAttributes = true;
            collectViewAttributes();
            res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(),
                    mAttachInfo.mContentInsets, mInputChannel);
        } catch (RemoteException e) {
            mAdded = false;
            mView = null;
            mAttachInfo.mRootView = null;
            mInputChannel = null;
            mFallbackEventHandler.setView(null);
            unscheduleTraversals();
            setAccessibilityFocus(null, null);
            throw new RuntimeException("Adding window failed", e);
        } finally {
            if (restore) {
                attrs.restore();
            }
        }
        // ......
        // 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;
    }
  }
}

setView() 方法首先将对 mView 赋值,然后调用 requestLayout() 函数来通知InputManager,这个Activity窗口是当前被激活的窗口,最后当窗口属性没有 INPUT_FEATURE_NO_INPUT_CHANNEL 标志时创建 mInputChannel 并通过 mWindowSession.addToDisplay() 函数注册通道。

在RootViewImpl中的函数通道是各种策略(InputStage)的组合,各策略负责的任务不同,如SyntheticInputStage、ViewPostImeInputStage、NativePostImeInputStage等待,这些策略以链表结构结构起来,当一个策略者没有消费事件时,就传递个下一个策略者。其中触摸事件由ViewPostImeInputStage处理。

final class ViewPostImeInputStage extends InputStage {
    //......
    @Override
    protected int onProcess(QueuedInputEvent q) {
        if (q.mEvent instanceof KeyEvent) {
            return processKeyEvent(q);
        } else {
            // If delivering a new non-key event, make sure the window is
            // now allowed to start updating.
            handleDispatchDoneAnimating();
            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;
        boolean handled = mView.dispatchPointerEvent(event);
        if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
            mUnbufferedInputDispatch = true;
            if (mConsumeBatchedInputScheduled) {
                scheduleConsumeBatchedInputImmediately();
            }
        }
        return handled ? FINISH_HANDLED : FORWARD;
    }
}

onProcess() 函数中如果事件不是按键事件且是触摸点事件则调用 processPointerEvent() 函数,在该函数中调用 mView.dispatchPointerEvent() 函数来下发该事件

DecorView是PhontWindow的内部类,在DecorView的 dispatchTouchEvent() 的函数实现中可以看到它直接调用了callback的回调函数,而这个函数的实现就是Activity了

public class PhoneWindow extends Window implements MenuBuilder.Callback {

    /**
     * Set the Callback interface for this window, used to intercept key
     * events and other dynamic operations in the window.
     *
     * @param callback The desired Callback interface.
     */
    public void setCallback(Callback callback) {
        mCallback = callback;
    }

    /**
     * Return the current Callback interface for this window.
     */
    public final Callback getCallback() {
        return mCallback;
    }

    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
        // ......
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            final Callback cb = getCallback();
            return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
                    : super.dispatchTouchEvent(ev);
        }
    }
}

通过对RootViewImpl的分析,我们知道了触摸事件的下发关键在与mView这个对象,那mView这个对象是在何时被赋值的呢?

它在ActivityThread.handleResumeActivity()函数中被赋值的,首先获得Window对象 r.window = r.activity.getWindow() ,然后再通过该Windowd对象的 r.window.getDecorView()函数 获得顶级View,最后通过WindowManager的实例对象的 wm.addView(decor, l) 函数将View添加到窗口中

/**
 * ActivityThread.java
 */
public final class ActivityThread {
    final void handleResumeActivity(IBinder token,
          boolean clearHide, boolean isForward, boolean reallyResume) {
            // ......
            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;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
                // ......
            }
    }
}

再来看WindowManager实例中的 addView() 函数,它直接调用了WindowManagerGlobal的addView()函数,从这能看出,WindowManagerGlobal才真正是WindowManager的实现类,WindowManagerImpl只不过代理了一下

/**
 * WindowManagerImpl.java
 */
public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    //......
    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    //......
}
{% endhighlight %}

最后,WindowManagerGlobal的 `addView()` 函数中直接new了一个ViewRootImpl对象,然后在这个对象上直接调用了ViewRootImpl的 `setView()` 函数,就这样把Activity的顶级View赋值给了它的成员变量

{% highlight java %}
/**
 * WindowManagerGlobal.java
 */
public final class WindowManagerGlobal {
    //......
    public void addView(View view, ViewGroup.LayoutParams params, 
        Display display, Window parentWindow) {

        ViewRootImpl root;
        View panelParentView = null;

        // ......

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

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }
}

至此,已经完全分析了Activity是怎么通过ViewRootImpl这个对象来接收底层的事件消息,下一节中,我会对从Activity出发,进一步分析事件的传递机制。

本人才疏学浅,有分析错误的地方,还请大家纠正。另外,本文中,有些逻辑分析是来自老罗的blog,非常感谢老罗,他写的Android文章十分有用,大家也不妨去看看。

Android应用程序键盘(Keyboard)消息处理机制分析

Android应用程序窗口设计框架介绍

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值