聊一聊安卓WallpaperService壁纸窗口触摸事件接收原理-第一篇

背景:

在使用android手机时候,大家壁纸可能一般都是使用的静态壁纸,静态壁纸一般就是设置一张静态,这种静态壁纸因为是固定的一张图片,所以对壁纸触摸交互这块需求比较少,但是如果设置的是一个动态壁纸,那么这个触摸交互需求就会大大增加。具体可以看下面的一个整体旋转的动态壁纸触摸场景:
在这里插入图片描述
明显可以看到这里的有一个正方体一直在旋转着,在鼠标触摸桌面时候,不仅仅桌面有响应事件,也发现Wallpaper也会绘制一个触摸点为圆心的圆,那么下面就来剖析一下Wallpaper壁纸窗口为啥可以实现触摸事件的接收原理,下面是桌面和壁纸窗口图层的位置,桌面肯定是位于壁纸上面的。
在这里插入图片描述

学习了马哥input课程就知道,正常逻辑的话上面有图层覆盖而且可以接受事件的话,那么底部的窗口肯定无法接受事件,但是上面看到动态壁纸情况下,明显可以看到桌面响应了触摸事件,壁纸也有响应,这个事件透传我们下一篇来分析input汇总原理,本节先来剖析上层WallpaperService的事件接受。

剖析动态壁纸事件接收代码

动态壁纸要接收触摸事件,还是需要一些额外设置的,具体的设置方法如下:

     @Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);

            // By default we don't get touch events, so enable them.
            setTouchEventsEnabled(true);
        }

一般动态壁纸要继承WallpaperService时候会重写onCreate,在onCreate中需要调用 setTouchEventsEnabled(true)让壁纸服务可以接受对应的触摸事件,如果设置为true,默认是不可以收到触摸事件。
上面设置完成后就可以让系统把触摸事件传递到WallpaperSerivce了,具体哪个方法接收呢?
这里其实WallpaperSericce的内部类Engine也有个onTouchEvent方法,它可以实现对触摸事件的接受
在这里插入图片描述
一般会重写这个方法进行相关的事件处理,比如上面正方体动态壁纸的处理如下:

       /*
         * Store the position of the touch event so we can use it for drawing later
         */
        @Override
        public void onTouchEvent(MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_MOVE) {
                mTouchX = event.getX();
                mTouchY = event.getY();
            } else {
                mTouchX = -1;
                mTouchY = -1;
            }
            super.onTouchEvent(event);
        }

这里的处理只是记录了一下坐标,记录坐标目的是为了在正方体绘制时候用这个坐标来绘制一个圆:

        /*
         * Draw one frame of the animation. This method gets called repeatedly
         * by posting a delayed Runnable. You can do any drawing you want in
         * here. This example draws a wireframe cube.
         */
        void drawFrame() {
            final SurfaceHolder holder = getSurfaceHolder();

            Canvas c = null;
            try {
                c = holder.lockCanvas();
                if (c != null) {
                    // draw something
                    drawCube(c);
                    drawTouchPoint(c);//绘制触摸点的圆
                }
            } finally {
                if (c != null) holder.unlockCanvasAndPost(c);
            }

            // Reschedule the next redraw
            mHandler.removeCallbacks(mDrawCube);
            if (mVisible) {
                mHandler.postDelayed(mDrawCube, 1000 / 25);
            }
        }

上面就是靠drawTouchPoint方法来绘制触摸点的圆:

     /*
         * Draw a circle around the current touch point, if any.
         */
        void drawTouchPoint(Canvas c) {
            if (mTouchX >=0 && mTouchY >= 0) {
                c.drawCircle(mTouchX, mTouchY, 80, mPaint);
            }
        }

整体使用也是非常简单,那么下面来看看这块WallpaperSerice可以接受触摸事件的一个初步原理。

WallpaperService触摸事件接收原理

先从 setTouchEventsEnabled(true)作为切入点开始剖析

      /**
         * Control whether this wallpaper will receive raw touch events
         * from the window manager as the user interacts with the window
         * that is currently displaying the wallpaper.  By default they
         * are turned off.  If enabled, the events will be received in
         * {@link #onTouchEvent(MotionEvent)}.
         */
        public void setTouchEventsEnabled(boolean enabled) {
            mWindowFlags = enabled
                    ? (mWindowFlags&~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
                    : (mWindowFlags|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
            if (mCreated) {
                updateSurface(false, false, false);
            }
        }

可以看到这里主要是对mWindowFlags进行了设置,如果enable就是不进行设置FLAG_NOT_TOUCHABLE,可以通过dump确认。

dumpsys  window windows

默认的静态壁纸,不可以触摸
在这里插入图片描述

自定义动态壁纸,可以触摸,就没有flag为NOT_TOUCHABLE
在这里插入图片描述
那么setTouchEventsEnabled(true)后,事件又是怎么到传递到WallpaperService的呢?
可以看看updateSurface方法,在窗口添加时候创建好对应的InputChannel
在这里插入图片描述
所以上面就可以看得出来无论是否WallpaperService是否enable Touch,都会创建好对应的InputChannel即在InputDispatcher中都会有对应的InputWindow,只不过这个时候窗口InputWindow因为设置了NOT_TOUCHABLE导致不会进行派发到这个窗口。
可以通过dumpsys input来看看静态壁纸情况:

dumpsys input

在这里插入图片描述再看看事件的怎么到的WallpaperSerice的onTouchEvent
先看看WallpaperInputEventReceiver接收
frameworks/base/core/java/android/service/wallpaper/WallpaperService.java

       final class WallpaperInputEventReceiver extends InputEventReceiver {
            public WallpaperInputEventReceiver(InputChannel inputChannel, Looper looper) {
                super(inputChannel, looper);
            }

            @Override
            public void onInputEvent(InputEvent event) {
                boolean handled = false;
                try {
                    if (event instanceof MotionEvent
                            && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                        MotionEvent dup = MotionEvent.obtainNoHistory((MotionEvent)event);
                        dispatchPointer(dup);//这里会调用dispatchPointer派发
                        handled = true;
                    }
                } finally {
                    finishInputEvent(event, handled);
                }
            }
        }

再看看dispatchPointer

 private void dispatchPointer(MotionEvent event) {
            if (event.isTouchEvent()) {
                synchronized (mLock) {
                    if (event.getAction() == MotionEvent.ACTION_MOVE) {
                        mPendingMove = event;
                    } else {
                        mPendingMove = null;
                    }
                }
                Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);//主要是发送了msg
                mCaller.sendMessage(msg);
            } else {
                event.recycle();
            }
        }

再看看MSG_TOUCH_EVENT的消息处理:

  case MSG_TOUCH_EVENT: {
                    boolean skip = false;
                    MotionEvent ev = (MotionEvent)message.obj;
                    if (ev.getAction() == MotionEvent.ACTION_MOVE) {
                        synchronized (mEngine.mLock) {
                            if (mEngine.mPendingMove == ev) {
                                mEngine.mPendingMove = null;
                            } else {
                                // this is not the motion event we are looking for....
                                skip = true;
                            }
                        }
                    }
                    if (!skip) {
                        if (DEBUG) Log.v(TAG, "Delivering touch event: " + ev);
                        mEngine.onTouchEvent(ev);//调用到mEngine的onTouchEvent
                    }
                    ev.recycle();

上面就展示了,最后事件到了WallpaperSerice内部类Engine的onTouchEvent方法。

具体事件是如何可以穿透壁纸上面窗口图层Launcher的,下一篇文章再给大家分析。

壁纸触摸事件总结:

1、壁纸窗口本身在创建时候就已经有创建好对应的InputChannel,而且也会和正常窗口一样会有InputWindow在InputDispatcher,而且位置一般处于最底层

2、要让壁纸可以接收事件,需要调用 setTouchEventsEnabled(true),主要是把Window不进行设置NOT_TOUCHABLE的flag。

3、事件在InputDispatcher派发时候,可以透过Launcher传递到Wallpaper的窗口,但如果有NOT_TOUCHABLE的flag那么就不会传递,具体如何可以透传下一篇分析。

更多framework实战开发干货,请关注下面“千里马学框架”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千里马学框架

帮助你了,就请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值