首页 > 其他分享 >Android知识笔记:记录 2 个 “容易误解” 的Android 知识点

Android知识笔记:记录 2 个 “容易误解” 的Android 知识点

时间:2023-06-28 12:32:23浏览次数:54  
标签:知识点 return MotionEvent 笔记 DecorView 事件 Activity Android event


Android知识笔记:记录 2 个 “容易误解” 的Android 知识点_触摸事件

今天分享两个之前我们可能都搞错的Android知识点,我们还是要追求极致,把不懂的问题搞懂的~

1. 事件到底是先到DecorView还是先到Window的?

有天早上看到事件分发的一个讨论:

Android知识笔记:记录 2 个 “容易误解” 的Android 知识点_数据_02

那么事件到底是先到DecorView还是先到Window(Activity,Dialog)的呢,引发出两个问题:

1. touch相关事件在DecorView,PhoneWindow,Activity/Dialog之间传递的顺序是什么样子的?

2. 为什么要按照1这么设计?

答:事件先到DecorView

Input系统

当用户触摸屏幕或者按键操作,首次触发的是硬件驱动,驱动收到事件后,将该相应事件写入到输入设备节点,这便产生了最原生态的内核事件。

接着,输入系统取出原生态的事件,经过层层封装后成为KeyEvent或者MotionEvent ;

最后,交付给相应的目标窗口(Window)来消费该输入事件。

1、当屏幕被触摸,Linux内核会将硬件产生的触摸事件包装为Event存到/dev/input/event[x]目录下。

2、Input系统—InputReader线程:loop起来让EventHub调用getEvent()不断的从/dev/input/文件夹下读取输入事件。然后转换成EventEntry事件加入到InputDispatcher的mInboundQueue。

3、Input系统—InputDispatcher线程:从mInboundQueue队列取出事件,转换成DispatchEntry事件加入到connection的outboundQueue队列。再然后开始处理分发事件 (比如分发到ViewRootImpl的WindowInputEventReceiver中),取出outbound队列,放入waitQueue.

4、Input系统—UI线程:创建socket pair,分别位于”InputDispatcher”线程和focused窗口所在进程的UI主线程,可相互通信。

这里只说大概,详情请看gityuan的这篇文章Input系统—事件处理全过程,文章3.3.3小节讲的是input系统事件从Native层分发Framework层的InputEventReceiver.dispachInputEvent()。

http://gityuan.com/2016/12/31/input-ipc/

Framework层

//InputEventReceiver.dispachInputEvent()
private void dispatchInputEvent(int seq, InputEvent event) {
    mSeqMap.put(event.getSequenceNumber(), seq);
    onInputEvent(event); 
}

ViewRootImpl.WindowInputEventReceiver

Native层通过JNI执行Framework层的InputEventReceiver.dispachInputEvent(),而真正调用的是继承了InputEventReceiver的ViewRootImpl.WindowInputEventReceiver。

所以这里执行的WindowInputEventReceiver的dispachInputEvent():

final class WindowInputEventReceiver extends InputEventReceiver {
    public void onInputEvent(InputEvent event) {
       enqueueInputEvent(event, this, 0, true);
    }
    ...
}

ViewRootImpl

void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
        ...
        if (processImmediately) {
            //关键点:执行Input事件
            doProcessInputEvents();
        } else {
            //走一遍Handler延迟处理事件
            scheduleProcessInputEvents();
        }
    }

    void doProcessInputEvents() {
        while (mPendingInputEventHead != null) {
            QueuedInputEvent q = mPendingInputEventHead;
            mPendingInputEventHead = q.mNext;
            if (mPendingInputEventHead == null) {
                mPendingInputEventTail = null;
            }
            q.mNext = null;

            mPendingInputEventCount -= 1;
            Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                    mPendingInputEventCount);

            long eventTime = q.mEvent.getEventTimeNano();
            long oldestEventTime = eventTime;
            if (q.mEvent instanceof MotionEvent) {
                MotionEvent me = (MotionEvent)q.mEvent;
                if (me.getHistorySize() > 0) {
                    oldestEventTime = me.getHistoricalEventTimeNano(0);
                }
            }
            mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
            //关键点:进一步派发事件处理
            deliverInputEvent(q);
        }
        ...
    }

    private void deliverInputEvent(QueuedInputEvent q) {
        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
                q.mEvent.getSequenceNumber());
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
        }

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

        if (stage != null) {
            //关键点:上面决定将事件派发到那个InputStage中处理
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }

ViewRootImpl.ViewPostImeInputStage

前面事件会派发到ViewRootImpl.ViewPostImeInputStage中处理,它的父类InputStage.deliver()方法会调用apply()来处理Touch事件:

@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) {
            //关键点:执行分发touch事件
            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;
    ...
    //关键点:mView分发Touch事件,mView就是DecorView
    boolean handled = mView.dispatchPointerEvent(event);
    maybeUpdatePointerIcon(event);
    maybeUpdateTooltip(event);
    ...
}

DecorView

如果你熟悉安卓的Window,Activity和Dialog对应的ViewRootImpl成员mView就是DecorView,View的dispatchPointerEvent()代码如下:

//View.java
public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        //分发Touch事件
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

因为DecorView继承FrameLayout,上面所以会调用DecorView的dispatchTouchEvent():

@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都被Activity和Dialog实现,所以变量cb可能就是Activity和Dialog。

Activity

当上面cb是Activity时,执行Activity的dispatchTouchEvent():

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

如果你熟悉安卓的Window,Activity的getWindow()拿到的就是PhoneWindow,下面是PhoneWindow的代码:

//PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    //调用DecorView的superDispatchTouchEvent
    return mDecor.superDispatchTouchEvent(event);
}

下面是DecorView.superDispatchTouchEvent()代码:

//DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
    //调用ViewGroup的dispatchTouchEvent()开始我们常见的分发Touch事件
    return super.dispatchTouchEvent(event);
}

流程图

Android知识笔记:记录 2 个 “容易误解” 的Android 知识点_数据_03

答:为什么要DecorView -> Activity -> PhoneWindow -> DecorView传递事件?

解耦!

ViewRootImpl并不知道有Activity这种东西存在!它只是持有了DecorView。

所以,不能直接把触摸事件送到Activity.dispatchTouchEvent();

那么,既然触摸事件已经到了Activity.dispatchTouchEvent()中了,为什么不直接分发给DecorView,而是要通过PhoneWindow来间接发送呢?

因为Activity不知道有DecorView!但是,Activity持有PhoneWindow ,而PhoneWindow当然知道自己的窗口里有些什么了,所以能够把事件派发给DecorView。

在Android中,Activity并不知道自己的Window中有些什么,这样耦合性就很低了。

我们换一个Window试试?

不管Window里面的内容如何,只要Window仍然符合Activity制定的标准,那么它就能在Activity中很好的工作。当然,这就是解耦所带来的扩展性的好处。

以上回答感谢:蔡徐坤打篮球。


2. RecyclerView卡片中持有的资源,到底该什么时候释放?

之前我们讨论过 View的onAttachedToWindow ,onDetachedFromWindow 调用时机 。

这个机制在RecyclerView卡片中还适用吗?

例如我们在RecyclerView的Item的onBindViewHolder时,利用一个CountDownTimer去做一个倒计时显示 / 或者是有一个属性动画效果?

到底在什么时候可以cancel掉这个倒计时/ 动画,而不影响功能了(滑动到用户可见范围内,倒计时/动画 运作正常)?

有什么方法可以和onBindViewHolder 对应吗?就像onAttachedToWindow ,onDetachedFromWindow这样 。

答:

onAttachedToWindow和onDetachedFromWindow在RecyclerView中还适用吗?

在RecyclerView中,Item的这两个方法分别会在【首次出现】和【完全滑出屏幕】(即在屏幕中完全不可见)时回调(在Adapter中也可以重写同名方法,用来监听ViewHolder的出现和消失)。

至于说适不适用,还是看具体需求,比如列表中的视频播放,在onDetachedFromWindow回调时暂停/停止还是合理的。

但是像题目说的倒计时和属性动画效果,就不合适了,为什么呢?

我们先粗略地温习一下RecyclerView的回收机制:

RecyclerView在布局(自然滑动其实也是反复布局子View)时,会回收一些符合条件的ViewHolder,它会根据ViewHolder的状态来决定临时存放在哪个地方,且把这些临时存放ViewHolder的集合看作两种:

不需要经过onBindViewHolder能直接重用的(mAttachedScrap、mCachedViews);

需要经过onBindViewHolder重新绑定数据的(mRecyclerPool.mScrap);

mAttachedScrap,正常情况下,它会在RecyclerView每次布局时都用到:在布局子View时,会把全部子View所属的Holder,都临时放里面,计算好了每个子View的新位置后,会一个个从mAttachedScrap中取出来,当然了不一定是全部都会取出来的,因为可能本次布局,一些旧Item已经完全滑出屏幕了。

那么,这些留在mAttachedScrap中没有被取出来的ViewHolder会怎么样呢?

正常情况下,它们会被扔到mCachedViews里面去(注意从mCachedViews中取出来时也是不用重新绑定数据的,即不会经过onBindViewHolder方法)。

刚刚说过,当Item被完全滑出屏幕时,Adapter的onDetachedFromWindow和该Item的onDetachedFromWindow会被回调,也就是说,当onDetachedFromWindow被回调时,ViewHolder并没有真正被回收!如果这时候把倒计时/动画取消掉了,那么在它们再次出现在屏幕中的时候,就不会动了,因为是直接重用,不会重新绑定数据的。

那应该在什么时候取消?

Adapter中有个onViewRecycled方法,看名字就知道是当Item被回收后回调的。。。

没错了,这个方法回调时,表示这个Holder已经被扔进mRecyclerPool.mScrap里了,也就是再次取出的时候会经过onBindViewHolder方法重新绑定数据。

倒计时/动画在这里取消的话,是完全没问题的(但记得保存当前进度,以便下次恢复)。

所以与onBindViewHolder对应的方法,就是这个onViewRecycled了。

最后,以上的阐述没办法保证一定是非常严谨的,所以请抱着学习以及批判的态度学习,有问题就指出,争取把一个个技术点尽可能搞清楚,大家一起进步。

最后

最后我想说:对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

标签:知识点,return,MotionEvent,笔记,DecorView,事件,Activity,Android,event
From: https://blog.51cto.com/u_16163453/6570437

相关文章

  • 记一次Android奇葩面试经历:因为没去过BAT,我被面试官“轰”出门外
    最近面试了几家大规模的公司,也遇到了各种各种的问题,技术方面的,管理方面的都有涉及。让我印象最深刻的是某上市公司,自称是阿里的控股子公司,创始人团队来自于阿里,感觉很高大上的样子。进门之后就是填表,然后就是技术负责人面试,问了一些项目中的问题。有的没的扯一大堆,对技术不是很看中......
  • Android 中高级面试原理:热修复与插件化基础—Java与Android虚拟机
    一、Java虚拟机(JVM)1、JVM整体结构使用javac将java文件编译成class文件。类加载器(ClassLoader)将class字节码加载进JVM对应的内存中。JVM将内存分配给方法区、堆区、栈区、本地方式栈4个部分,这4个部分分别存储字节码不同的部分。垃圾回收器(gc)会管理整个内存空间中的垃圾。2、Java代码......
  • BAT 大厂Android研发岗必刷真题:Android异常与性能优化相关面试问题
    今天来讲一讲在面试中碰到的Android异常与性能优化相关问题:1、anr异常面试问题讲解a)什么是anr?应用程序无响应对话框b)造成anr的原因?**主线程中做了耗时操作c)android中那些操作是在主线程呢?activity的所有生命周期回调都是执行在主线程的Service默认是执行在主线程的BroadcastR......
  • Android LayoutManager高端玩家,实现花式表格!
    如果你对RecyclerView原理还不是特别了解,非常建议你读一下。本文的项目也是学习自定义LayoutManager绝佳资料,大家有需要的可以好好拜读。前言表格是自打我进公司以后就使用的控件,起初使用的是ScrollablePanel,从一开始的被花式吊打,到后期的熟练使用。大佬写的控件确实给我的工作带来......
  • Android ‘Handler()‘ is deprecated
    privateHandlerhandler=newHandler();Handler()此构造函数在Android11/R之后已弃用。在Handler构造期间隐式选择Looper会导致操作无声地丢失(如果Handler不期待新任务并退出)、崩溃(如果有时在没有Looper活动的线程上创建处理程序)或竞争条件,处理程序关联的线程不......
  • Android线程管理之ExecutorService线程池
    为什么要引入线程池?   1.)newThread()的缺点每次newThread()耗费性能调用newThread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。不利于扩展,比如如定时执行、定期执行、线程中断  2.)采用线程池的优点重用存在的......
  • css grid布局(网格布局)笔记
    Grid布局网格布局的基本概念CSS网格布局引入了二维网格布局系统,可用于布局页面主要的区域布局或小型组件。什么是网格?网格是一组相交的水平线和垂直线,它定义了网格的列和行。我们可以将网格元素放置在与这些行和列相关的位置上。网格布局的特点:固定的轨道尺寸或者弹性......
  • 真·随笔(三)《政治学通识》笔记
    读书太少了,还天天鉴证,没底子。看点东西充实一下。中国政治观:中国古代(孔子、韩非)、海国图志、孙中山、当代。孙:管理众人的事便是政治。孔子对曰:“政者,正也。子帅以正,孰敢不正?”……翻译成现代政治学语言,可以表述为“政府是社会的道德榜样”。借助这种视角,大家可以理解目前中国......
  • 【笔记】Inception-v4, Inception-ResNet and the Impact of Residual Connections on
    论文:https://arxiv.org/abs/1602.07261代码:https://github.com/gu-yan/mlAlgorithms/blob/master/inception_impl/Inception_resnet_v2_temsorflow.py先贴出模型:InceptionV4:https://raw.githubusercontent.com/titu1994/Inception-v4/master/Architectures/Inception-v4.pngInecpt......
  • 知识点总结--6月27日
    JDK提供的编译器是什么?javac.exe标识符的概念是什么?标识符的概念:给类、接口、方法、变量取名字时使用到的字符序列组成部分:大小写字母、数字、_、$、中文例如:注意事项:不能以数字开头 譬如:123name就是不合法的区分大小写但是可以包含关键字和保留字。如:不可以使用void......