当前位置: 首页 > news >正文

android事件分发机制源码分析

  • 没什么用的前言
  • 责任链设计模式
  • 流程图
  • 源码分析

没什么用的前言

事件分发机制是面试中一道必问的题目,而我的应对方式则是,在网络上找一些博客看看,然后做一些笔记,最后在面试时将我自己记住的内容说出来。这种方式本身没有太大的问题,因为在看博客的过程中也学到了知识。但每次看完博客,我都没办法很好地理解整个流程,所以决定自己看一下源码,看完之后,决定通过博客这种形式将自己的笔记输出出来。

责任链设计模式

提到事件分发机制,永远都绕不开责任链设计模式,只有理解了责任链涉及模式,才能更好地理解整个分发流程。
责任链设计模式的定义(来自百度百科):责任链模式是一种设计模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
这里的重点有:

  • 每个对象只持有下一个对象的引用,而不会持有更下级别的引用
  • 发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求

假设有对象A、B、C,A持有B的引用,B持有C的引用。当A获取到请求后,会判断自己是否需要处理该请求,如果不需要处理,就将请求交给B处理,B也会做一样的判断。
假设请求最终是由C处理,则可以看到,A并没有持有C的引用,但请求最终却可以在由C处理。这就是责任链设计模式的优势。

代码举例

// AbstractTask
public abstract class AbstractTask {private AbstractTask next;public AbstractTask(){}public AbstractTask(AbstractTask next){this.next = next;}public void handleRequest(Request request){// 如果当前的level可以处理,就交给自己处理if(getTaskLevel() == request.getTaskLevel()) {handle(request);}else {// 如果自己没办法处理,并且存在next,就交给next处理if(next != null) {next.handleRequest(request);}else {System.out.println("没有找到处理对象");}}}public void setNext(AbstractTask next){this.next = next;}protected abstract int getTaskLevel();protected abstract void handle(Request request);
}// ATask
// 还有B和C task,不过代码都差不多,我就不贴出来了
// B的taskLevel为2,C的taskLevel为3
public class ATask extends AbstractTask{@Overrideprotected int getTaskLevel() {return 1;}@Overrideprotected void handle(Request request) {System.out.println("这里是A task, request: " + request.toString());}
}// Request
public class Request {private final int taskLevel;private Object content;public Request(int taskLevel){this.taskLevel = taskLevel;}public void setContent(Object content){this.content = content;}public int getTaskLevel(){return taskLevel;}public Object getContent(){return content;}@Overridepublic String toString() {return "Request{" +"taskLevel=" + taskLevel +", content=" + content +'}';}
}

测试代码

public class Test {public static void main(String[] args) {ATask aTask = new ATask();BTask bTask = new BTask();CTask cTask = new CTask();aTask.setNext(bTask);bTask.setNext(cTask);Request request = new Request(3);request.setContent("new Request(3)");aTask.handleRequest(request);}
}

最后打印出来的是:这里是C task, request: Request{taskLevel=3, content=new Request(3)}。
可以看到,A没有持有C的引用,但执行了C的handle方法。
有一个必须说清楚的是,持有下一级的引用不只有链表这种方式,还有其他方式。比如用一个数组来存储,这就是ViewGroup的做法。

再看看ViewGroup几行寻找targetView的代码

// 成员变量
private View[] mChildren;// ViewGroup.dispatchTouchEvent
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);....    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {....}
}

可以看到,ViewGroup是从children里面寻找的,而children是一个数组。所以没必要将子对象局限于链表这种方式,如果有更好的方式,可以使用更好的方式来实现。
而这里的dispatchTransformedTouchEvent方法最终会调用View的dispatchTouchEvent方法,如果调用的View是一个ViewGroup,就会执行相同的代码,直到调用的是一个View。这就是责任链设计模式在事件分发机制上的实现。可能有人会问,如果该ViewGroup没有子View呢?这个问题留到下面的源码分析,这里只是讲解责任链设计模式在事件分发上的应用。
从这里也可以想象得到,A ViewGroup持有B ViewGroup的引用,B ViewGroup持有C View的引用,而A ViewGroup没有直接持有C View的引用,和上面的例子有点像。

流程图

在这里插入图片描述
图里面,省略了DecorView等其他界面。

如果给View2设置onClick并且点击View2,那将发生。

  1. 传递一个ACTION_DOWN的event给Activity
  2. Activity调用dispatchTouchEvent最后将事件传递给ViewGroup
  3. ViewGroup调用dispatchTouchEvent
  4. 调用ViewGroup的onInterceptTouchEvent判断是否拦截该事件,如果否,则执行第5步
  5. 从上面的源码可以看到,ViewGroup是从最后一个View向第0个View的顺序调用。所以会判断View3的范围内是否包含MotionEvent的x值和y值,发现不包含,调用View2判断。
  6. 发现View2是目标View,则调用该View的onTouchEvent方法,并且将后续的事件都给该View
  7. 上面的第4步,如果确定拦截,则会调用该ViewGroup的onTouchEvent, No touch targets so treat this as an ordinary view。在源码搜索这句注释就能看到相应的代码,可以发现,最后调用的就是super的dispatchTouchEvent方法。

总结一下流程:Activity dispatchTouchEvent -> ViewGroup dispatchTouchEvent ->ViewGroup onInterceptTouchEvent->View dispatchTouchEvent ->View onTouchEvent

这个的就不画出来了,一看就知道怎么调用,下面写完源码分析之后,将画一个较为完整的图

以上就是点击一个View后的一个大概流程,接下来开始源码分析,从源码的角度去还原一个完整的调用流程。

源码分析

从上面的流程可以看到

  • ViewGroup的调用顺序是:dispatchTouchEvent -> onInterceptTouchEvent -> onTouchEvent
  • View的调用顺序是:dispatchTouchEvent ->onTouchEvent

所以先看ViewGroup的dispatchTouchEvent方法,再根据流程分析其他方法的代码。

// ViewGroup dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {...// 这个变量也是重点,不过先看完其他代码,再回头分析这个变量boolean handled = false;if (onFilterTouchEventForSecurity(ev)) {final int action = ev.getAction();// 这里的masked可以当action使用// 执行一次mask运算之后,就可以保证该action变量不存在action以外的数值final int actionMasked = action & MotionEvent.ACTION_MASK;// 如果是ACTION_DOWN,则清除和重置一些变量if (actionMasked == MotionEvent.ACTION_DOWN) {cancelAndClearTouchTargets(ev);resetTouchState();}// Check for interception.final boolean intercepted;// 该if是用于判断是否执行onInterceptTouchEvent// 当ACTION_DOWN找到目标之后,mFirstTouchTarget就不会为空// 所以进入if有两种可能,1:action为ACTION_DOWN 2:找到消费该事件的Viewif (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {// ViewGroup里面有一个方法,为requestDisallowInterceptTouchEvent,调用这个方法就可以修改mGroupFlags的值,具体看下面提供的源码// 如果为true,则将包含FLAG_DISALLOW_INTERCEPT这个flag,此时执行这里的&运算将为true,所以将不执行onInterceptTouchEvent方法final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action); // restore action in case it was changed} else {intercepted = false;}} else {// There are no touch targets and this action is not an initial down// so this view group continues to intercept touches.intercepted = true;}...// Check for cancelation.// 判断是否为cancelfinal boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;// 这里的mouseEvent和split不是分析的重点,所以就不解释了,只将代码贴出来// Update list of touch targets for pointer down, if needed.final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0 && !isMouseEvent;TouchTarget newTouchTarget = null;boolean alreadyDispatchedToNewTouchTarget = false;// 上面之所以会将cancel贴出来,是因为这里需要用到// 如果没有cancel也没有interceptedif (!canceled && !intercepted) {...// 如果是ACTION_DOWN或者其他情况,这里的其他情况不包含ACTION_MOVE、ACTION_UP这些// 至于ACTION_MOVE、ACTION_UP这些,则会传递给消费了ACTION_DOWN的View// 而下面的代码,有一个重要的任务,就是寻找目标View// 也就是,只有ACTION_DOWN才会寻找目标View,而其他action则不会寻找if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {final int actionIndex = ev.getActionIndex(); // always 0 for down// down时getPointerId将返回0,并且这里的split为true,所以最后的结果为1// 该变量主要是用于解决多点触控的问题,这里不作讨论,只要知道这里的值是1即可final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex): TouchTarget.ALL_POINTER_IDS;removePointersFromTouchTargets(idBitsToAssign);final int childrenCount = mChildrenCount;// 如果有子Viewif (newTouchTarget == null && childrenCount != 0) {// 获取触摸的x值和y值final float x =isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);final float y = isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);// 代码已经贴再下面了,想看的话可以看看// 这里解释一下,从接收的类型上可以看到,该方法放回的是一个child List// 如果ViewGroup里面的任何一个child的getZ()不为0,就会返回一个非空的数组, 否则就会返回空数组// 该方法的作用是,按child的z值进行排序,z值最大的child放到list的最后final ArrayList<View> preorderedList = buildTouchDispatchChildList();// child drawing代码已经贴再下面// 该方法默认是false,可以调用方法设置为true,但设置的方法访问修饰符为protected// 从方法名称可以知道,该方法是用于判断是否支持按照定义的顺序final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();// private View[] mChildren;final View[] children = mChildren;// 这里需要注意,这里的遍历是倒序遍历,即从最后一个往第一个遍历for (int i = childrenCount - 1; i >= 0; i--) {// 上面的注释也提到,customOrder默认为false,false将返回传入的index,true的情况就不讨论了final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);// 如果preorderedList不为null,就从preorderedList取数据,否则就从children取数据final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);// 两个方法的代码都贴在下面// 第一个方法,判断View是否可见或虽不可见但在执行动画,只要有一个为true,就不会执行continue。而如果返回true,则会执行第二个方法// 第二个方法,判断x值和y值是否在child里面// 如果child不可见并且正在执行动画或者View可见但没有动画 && xy值在child里面,才不会执行continueif (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {continue;}// getTouchTarget()的作用是查找child是否存在于mFirstTouchTarget的单链表中。  // 如果为true,则返回相应的TouchTarget对象,否则返回nullnewTouchTarget = getTouchTarget(child);resetCancelNextUpFlag(child);// 重点!!!该方法会调用View的dispatchTouchEvent// 如果是一个ViewGroup,那就执行相同的代码,最后执行到这里// 如果是一个View,则会根据具体情况,判断是否执行onTouchEvent和其他代码// 代码我已经放到了下面,建议把该方法的代码看一看,这个方法真的很重要。在看该方法之前,请记住,action是ACTION_DOWN,cancel是false,child不为空// 如果不想看,那就必须知道,该方法会调用View的dispatchTouchEvent方法,尝试将event分发给View// 如果返回true,则说明child消费该down事件。返回true的情况有多种,可以是child的dispatchTouchEvent返回true// 也可以onTouchListener返回true,或者是onTouchEvent返回trueif (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...// 找到child就将child添加到TouchTarget的链条里面,并返回新生成的target对象// 这里要记住,此时,newTouchTarget已经是一个非空对象,并且在调用该方法之后,会给mFirstTouchTarget变量赋值,所以此时的mFirstTouchTarget也不为空newTouchTarget = addTouchTarget(child, idBitsToAssign);// 该变量也要记住,这里是在该if里面唯一一行设置为true的代码alreadyDispatchedToNewTouchTarget = true;// 既然找到touchTarget,那就跳出循环把,没必要继续循环下去break;}ev.setTargetAccessibilityFocus(false);}if (preorderedList != null) preorderedList.clear();}if (newTouchTarget == null && mFirstTouchTarget != null) {// Did not find a child to receive the event.// Assign the pointer to the least recently added target.newTouchTarget = mFirstTouchTarget;while (newTouchTarget.next != null) {newTouchTarget = newTouchTarget.next;}newTouchTarget.pointerIdBits |= idBitsToAssign;}}}// 如果上面的ACTION_DOWN没有找到消费该event的view,这里的mFirstTouchTarget则为nullif (mFirstTouchTarget == null) {// 此时,可以继续带着参数看该方法,这里的canceled是false,child是null// 不过我还是解释一下吧,由于child是null,最终调用的是自己的super.dispatchT...方法// 也就是说ViewGroup将调用View的dispatchTou...方法// 如果返回true,则表明该事件被该ViewGroup消费,以后的事件都会执行下面的else代码// 为了防止有人懂不懂什么会执行下面的代码,我说清楚一点,一个ViewGroup也会被其他ViewGroup调用// 所以我的意思是,其他ViewGroup会在上面的for循环找到target,然后执行下面的else代码handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} else {// 如果mFirstTouchTarget不为空,则会执行这里的代码// ACTION_DOWN不为空时,表示找到了消费事件的View// 其他action则是,在ACTION_DOWN找到消费的ViewTouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;// 只有ACTION_DOWN时,并且找到消费的View,该变量才会为true,其他情况都是false// 由于ACTION_DOWN已经执行了dispatchTou...,所以这里没有执行是正确的if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;} else {// 看了一下代码,只有调用performButtonActionOnTouchDown,reset方法才有可能返回true,而intercepted则不用说,只要ViewGroup不拦截该event,就wieldfalfinal boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;// cancelChild上面提到,child则是消费了ACTION_DOWN的Viewif (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}// 下面的if和cancel有关,不做讨论// 总结一下上面的if和else,ACTION_DOWN时,如果有View消费了该事件,上面的if就为true,就会将handled置为true// 并且在寻找目标View时,会调用View的dispatchTou..,所以上面的if不会调用dispatchTou...// 在此之上,其他action则会执行else的代码,从intercept可以看到,即便找到目标View,也可以拦截其他事件// 而不管是否拦截,都会执行View的dsipatchTou...方法,只不过如果拦截了,View拿到的action是ACTION_CANCEL,而不是event原本的actionif (cancelChild) {if (predecessor == null) {mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;continue;}}predecessor = target;target = next;}}// Update list of touch targets for pointer up or cancel, if needed.if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {resetTouchState();} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {final int actionIndex = ev.getActionIndex();final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);removePointersFromTouchTargets(idBitsToRemove);}}if (!handled && mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);}return handled;
}

其他代码可不看 ,还是建议看看dispatchTransformedTouchEvent方法的代码。里面关键代码还是很多,不看的话,肯定没办法理解整体的执行流程。
方法列表

  • requestDisallowInterceptTouchEvent
  • buildTouchDispatchChildList
  • isChildrenDrawingOrderEnabled
  • canReceivePointerEvents和isTransformedTouchPointInView
  • dispatchTransformedTouchEvent

既然return handled的代码在末尾,那就在这里总结一下,哪些地方可能为true。首先,默认值是false,所以必须手动置为true。
第一次,这段代码不能一定会置为true,只能说可能会,但还是有必要拿出来

if (mFirstTouchTarget == null) {handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);

当ACTION_DOWN找不到target时,就会执行这里的代码,最终会调用super.dispatchTou…方法。此时,如果该ViewGroup要消费该事件,就可以返回true。
第二次,当ACTION_DOWN找到target view时,直接置为true

if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;

第三次,这个if的else

} else {final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}

再探讨一下,为什么找到target view时,ViewGroup需要返回true。
我的理解是:既然是该ViewGroup的child消费事件,那也可以说是该ViewGroup消费了该事件。只不过上一级并不知道具体消费的对象。但这不重要,只要知道该VIewGroup需要就行,不需要理会具体是哪个对象。
可以思考一下,如果返回false会怎么样。如果返回false,那ViewGroup在调用dispatchTra…方法的时候,就没办法找到target view。所以ViewGroup必须返回true,然后再由该ViewGroup去调用自己的child,将事件传递给target view。

上面的dispatchTransformedTouchEvent方法一定要看,里面有很多很重要的代码。
简单梳理一下执行流程

  1. Activity接收到触摸事件之后,会调用ViewGroup的dispacthTouchEvent方法,让ViewGroup自己将触摸事件分发出去
  2. 如果是ACTION_DOWN,就清空touch相关的数据。并从最后一个到第一个寻找需要消费的子View。寻找的方式为,判断触摸的x值和y值是否在子View里面,并且子View的dispacthTouchEvent方法是否返回true。如果返回true,则会用该View的信息构建一个TouchTarget并将TouchTarget的值赋给mFirstTocuhTarget
  3. ACTION_DOWN的代码执行完成之后,将判断mFirstTouchTarget的值是否为空。如果不为空,就说明找到需要消费的View,ViewGroup的dispatchTouchEvent方法将返回true,表示该事件被该ViewGroup消费了。如果为空,则会调用super.dispatchTouchEvent方法,尝试将该事件分配给自己。
  4. ACTION_DOWN以外的事件将不寻找消费的子View,而是判断mmFristTouchTarget是否为空。如果不为空,继续调用child的dispatchTouchEvent方法并返回true。如果为空,则会调用当前ViewGroup的super.dipatchTouchEvent方法。
  5. 有一点需要注意,如果不是ACTION_DOWN并且没有找到消费的子View或者是调用了disallowIntercept方法,则不会调用onInterceptTouchEvent。其他情况都会调用onInterceptTocuhEvent方法,所以即使找到了消费的子View,也会尝试拦截触摸事件。
  6. 关于View的dispatchTouchEvent,在调用onTouchEvent之前,会判断onTouchListener是否为空。如果不为空,并且onTouch方法返回true,则会返回true,表明消费了该事件。否则会执行onTouchEvent方法。
  7. 在onTouchEvent方法里面,如果没有设置click或longClick或tooltip,就会返回false。longClick和tooltip的触发逻辑是一样的,都是在down的时候,向RunQueue放入一个延迟任务,如果延迟结束就会执行longClick。如果延迟还没结束就松开手指,即执行了up事件,则会将该任务移除。如果up时延迟还没有结束,或者没有设置longClick,并且设置了click,就会触发onClick。如果同时设置了longClick和click,并且长按超过400毫秒,就会执行longClick,此时松开手指也不会执行click。也就是,longClick的优先级是高于click。

最后根据上面分析的执行流程,重新画一个简单流程图。先说清楚,这个流程图只是用于理解,所以像第7步的longClick、click不会包含在这里面。
在这里插入图片描述

方法列表

requestDisallowInterceptTouchEvent

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {// We're already in this state, assume our ancestors are tooreturn;}if (disallowIntercept) {mGroupFlags |= FLAG_DISALLOW_INTERCEPT;} else {mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;}...
}

回到源码分析

buildTouchDispatchChildList

/*** Provide custom ordering of views in which the touch will be dispatched.** This is called within a tight loop, so you are not allowed to allocate objects, including* the return array. Instead, you should return a pre-allocated list that will be cleared* after the dispatch is finished.* @hide*/
public ArrayList<View> buildTouchDispatchChildList() {return buildOrderedChildList();
}/*** Populates (and returns) mPreSortedChildren with a pre-ordered list of the View's children,* sorted first by Z, then by child drawing order (if applicable). This list must be cleared* after use to avoid leaking child Views.** Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated* children.*/
ArrayList<View> buildOrderedChildList() {final int childrenCount = mChildrenCount;if (childrenCount <= 1 || !hasChildWithZ()) return null;if (mPreSortedChildren == null) {mPreSortedChildren = new ArrayList<>(childrenCount);} else {// callers should clear, so clear shouldn't be necessary, but for safety...mPreSortedChildren.clear();mPreSortedChildren.ensureCapacity(childrenCount);}final boolean customOrder = isChildrenDrawingOrderEnabled();for (int i = 0; i < childrenCount; i++) {// add next child (in child order) to end of listfinal int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View nextChild = mChildren[childIndex];final float currentZ = nextChild.getZ();// insert ahead of any Views with greater Zint insertIndex = i;while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {insertIndex--;}mPreSortedChildren.add(insertIndex, nextChild);}return mPreSortedChildren;
}

回到源码分析

isChildrenDrawingOrderEnabled

protected boolean isChildrenDrawingOrderEnabled() {return (mGroupFlags & FLAG_USE_CHILD_DRAWING_ORDER) == FLAG_USE_CHILD_DRAWING_ORDER;
}protected void setChildrenDrawingOrderEnabled(boolean enabled) {setBooleanFlag(FLAG_USE_CHILD_DRAWING_ORDER, enabled);
}

回到源码分析

canReceivePointerEvents和isTransformedTouchPointInView

protected boolean canReceivePointerEvents() {return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
}// 这里的outLocalPoint应该是想将x值和y值设置到该变量里面,所以传入了一个引用
protected boolean isTransformedTouchPointInView(float x, float y, View child,PointF outLocalPoint) {final float[] point = getTempLocationF();point[0] = x;point[1] = y;transformPointToViewLocal(point, child);final boolean isInView = child.pointInView(point[0], point[1]);if (isInView && outLocalPoint != null) {outLocalPoint.set(point[0], point[1]);}return isInView;
}

回到源码分析

dispatchTransformedTouchEvent

// 在dispatchTouchEvent里面,由三个地方调用该方法,其中两个是child调用的,一个是当前的VieweGroup调用的
// child调用时:child不为空
// 当前ViewGroup调用时:child为null
// cancel就都当作false吧
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;// Canceling motions is a special case.  We don't need to perform any transformations// or filtering.  The important part is the action, not the contents.final int oldAction = event.getAction();// 虽说当作canel为false,但还是说一下代码的执行逻辑// 除了action是cancel之外,如果再ACTION_DOWN找到target view,并且当前的action不是ACTION_DOWN,并且ViewGroup的onInterceptTouchEvent返回true// cancel的值也是true的,此时,也会进入到该if,所以在进入之后,需要手动设置为cancel,因为进入之前的action不是cancelif (cancel || oldAction == MotionEvent.ACTION_CANCEL) {event.setAction(MotionEvent.ACTION_CANCEL);// 可以看到,如果child为空,就会调用ViewGroup自己的super.dispatchTou...方法。而如果不为空,则会调用child的该方法。// 而如果是调用super.dispatchTou...方法,则方法逻辑就是View的该方法,下面会将View的该方法的代码贴出来,分析代码的执行流程if (child == null) {handled = super.dispatchTouchEvent(event);} else {handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;}// Calculate the number of pointers to deliver.final int oldPointerIdBits = event.getPointerIdBits();final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;// If for some reason we ended up in an inconsistent state where it looks like we// might produce a motion event with no pointers in it, then drop the event.if (newPointerIdBits == 0) {return false;}// If the number of pointers is the same and we don't need to perform any fancy// irreversible transformations, then we can reuse the motion event for this// dispatch as long as we are careful to revert any changes we make.// Otherwise we need to make a copy.final MotionEvent transformedEvent;// 和多点触控有关,一般为true,上面的注释也写得很清楚。并且大部分情况下,会在该if里面执行if (newPointerIdBits == oldPointerIdBits) {// 不明白hasIdentityMatrix的作用,而且代码是native,看不了。总之,只要知道,该方法一般返回true// 所以如果child不为空,是可以进入到if的if (child == null || child.hasIdentityMatrix()) {// 下面的代码没什么好说的, 接下来看看View的dispatchTou...方法// 代码贴在这个方法的下面,滑下去就行了if (child == null) {handled = super.dispatchTouchEvent(event);} else {final float offsetX = mScrollX - child.mLeft;final float offsetY = mScrollY - child.mTop;event.offsetLocation(offsetX, offsetY);handled = child.dispatchTouchEvent(event);event.offsetLocation(-offsetX, -offsetY);}return handled;}transformedEvent = MotionEvent.obtain(event);} else {transformedEvent = event.split(newPointerIdBits);}// Perform any necessary transformations and dispatch.if (child == null) {handled = super.dispatchTouchEvent(transformedEvent);} else {final float offsetX = mScrollX - child.mLeft;final float offsetY = mScrollY - child.mTop;transformedEvent.offsetLocation(offsetX, offsetY);if (! child.hasIdentityMatrix()) {transformedEvent.transform(child.getInverseMatrix());}handled = child.dispatchTouchEvent(transformedEvent);}// Done.transformedEvent.recycle();return handled;
}// View的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {// 这里是accessibility相关的代码...// result可以当作ViewGroup的handled,都是默认为false,再根据情况改为true// 而如果为true,则表明该View要消费该事件,所以下面那些将resule设置为true的代码,都是表明要消费该事件// 不过有一点需要记住,一个View如果想要消费move、up这些事件,就需要在down时表明需要该事件// 不过也有其他解决方案,比如从ViewGroup入手,毕竟不是所有情况都需要down事件boolean result = false;...final int actionMasked = event.getActionMasked();if (actionMasked == MotionEvent.ACTION_DOWN) {// Defensive cleanup for new gesturestopNestedScroll();}if (onFilterTouchEventForSecurity(event)) {// 如果enable为true,并且正在拖拽,就直接将resule设置为trueif ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}// noinspection SimplifiableIfStatement// li就是保存各种listener的类,比如onClick、onLongClick、onTouchListener等// 如果onTouchListener不为空,并且onTouch返回true,就将result设置为trueListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {result = true;}// 如果上面的if没有执行,则会调用onTouchEvent代码// 此时,来回答一道在面试时会被问的题目。onTouch和onTouchEvent哪个会先被调用?// 从代码上看,答案已经出来了。不过如果没有看源码,可能就只知道这回事,但不知道为什么// 滑下去看onTouchEvent的代码// 这里还是提醒一下,如果onTouchEvent返回true,则表明消费该事件if (!result && onTouchEvent(event)) {result = true;}}if (!result && mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);}// Clean up after nested scrolls if this is the end of a gesture;// also cancel it if we tried an ACTION_DOWN but we didn't want the rest// of the gesture.if (actionMasked == MotionEvent.ACTION_UP ||actionMasked == MotionEvent.ACTION_CANCEL ||(actionMasked == MotionEvent.ACTION_DOWN && !result)) {stopNestedScroll();}return result;
}// View的dispatchTouchEvent
public boolean onTouchEvent(MotionEvent event) {final float x = event.getX();final float y = event.getY();final int viewFlags = mViewFlags;final int action = event.getAction();// 代码有点长,不看也可以。该变量如果为true,那就可以click或者longClickfinal boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;// 如果View为disable,即enable为falseif ((viewFlags & ENABLED_MASK) == DISABLED) {if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {setPressed(false);}mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;// A disabled view that is clickable still consumes the touch// events, it just doesn't respond to them.// 上面的注释已经说明了一切。说一下我的理解,觉得就算一个View disabled,只要设置了click或者longClick,那就必须消费该事件。// 只是,是否响应看的是View是否disablereturn clickable;}// 如果设置了delegate,则执行delegate的代码,并且当delegate返回true时,直接return。如返回false,那就继续执行下面的代码。if (mTouchDelegate != null) {if (mTouchDelegate.onTouchEvent(event)) {return true;}}// 只有设置了click或者tooltip才能进入if。记住,只要进入if,就一定会返回true,即消费该事件。没有进入则返回false// tooltip是android 26新出的一个功能,给一个View设置tooltip(String)之后,长按该View,就会出现一个提示// 所以tooltip就是一个longClick,这不是我瞎说的。实际上,tooltip和longClick就是调用同一段代码,具体可以看下面的源码分析// tooltip的设置方式:调用setTooltipText。如果参数是一个空字符串,则会清空flag,否则会设置tooltip的flagif (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {// 只分析ACTION_DOWN和ACTION_UP,可以先看ACTION_DOWN的代码,再回头看ACTION_UP的代码case MotionEvent.ACTION_UP:mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;if ((viewFlags & TOOLTIP) == TOOLTIP) {handleTooltipUp();}// 如果不能点击,则表明是因为设置了tooltip// 而如果看了checkForLongClick的代码,就会知道,显示tooltips不是在ACTION_UP执行的,而是由Runnable自己完成的// 所以到了这里直接移除callback是没有问题的。假设在up之前就显示tooltip,那移除肯定没问题。而up之后才需要执行,那更要移除,因为都已经松开手指了if (!clickable) {removeTapCallback();removeLongPressCallback();mInContextButtonPress = false;mHasPerformedLongPress = false;mIgnoreNextUpEvent = false;break;}boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;// 在ACTION_DWON,有一句代码调用了setPressed(true, x, y),并且执行了mPrivateFlags |= PFLAG_PRESSED,所以这里为trueif ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {// take focus if we don't have it already and we should in// touch mode.boolean focusTaken = false;if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {focusTaken = requestFocus();}if (prepressed) {// The button is being released before we actually// showed it as pressed.  Make it show the pressed// state now (before scheduling the click) to ensure// the user sees it.setPressed(true, x, y);}// 在checkForLongClick我有提到,如果有handle longClick,该变量就会变为true,所以如果没有handle,并且不忽略up event,就可以进入eventif (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {// This is a tap, so remove the longpress check// 如果是clickable,上面的break则不会执行,所以需要在这里执行removeLongPressCallback();// Only perform take click actions if we were in the pressed state// 如果没有focus,就执行clickif (!focusTaken) {// Use a Runnable and post this rather than calling// performClick directly. This lets other visual state// of the view update before click actions start.if (mPerformClick == null) {mPerformClick = new PerformClick();}// Returns true if the Runnable was successfully placed in to the message queue.  // Returns false on failure, usually because the looper processing the message queue is exiting.// 上面的注释是post方法的注释,不是原本写在这里的注释// 意思是,如果没办法成功向消息队列放入Runnable,则会执行下面的performClick方法// 而PerformClick从类名也看得出,就是用来执行click,所以效果是一样的if (!post(mPerformClick)) {performClickInternal();}}}if (mUnsetPressedState == null) {mUnsetPressedState = new UnsetPressedState();}if (prepressed) {postDelayed(mUnsetPressedState,ViewConfiguration.getPressedStateDuration());} else if (!post(mUnsetPressedState)) {// If the post failed, unpress right nowmUnsetPressedState.run();}removeTapCallback();}mIgnoreNextUpEvent = false;break;case MotionEvent.ACTION_DOWN:...// ACTION_UP时,该变量用于判断是否调用onClick,只有false时,才可以调用onClickmHasPerformedLongPress = false;// 如果不能点击, 那就只有tooltip这种可能性了,直接break就行。记住,我上面提到了,只要break,就会返回true// 为什么现在就可以return,可以滑下去看看该方法的代码。顺便一提,该方法和longClick调用的是同一个方法,只要看一遍就可以了if (!clickable) {checkForLongClick(ViewConfiguration.getLongPressTimeout(),x,y,TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);break;}// Performs button-related actions during a touch down event. True if the down was consumed.if (performButtonActionOnTouchDown(event)) {break;}// Walk up the hierarchy to determine if we're inside a scrolling container.boolean isInScrollingContainer = isInScrollingContainer();// For views inside a scrolling container, delay the pressed feedback for// a short period in case this is a scroll.// 如果正在滚动,则将延迟100ms执行if (isInScrollingContainer) {mPrivateFlags |= PFLAG_PREPRESSED;if (mPendingCheckForTap == null) {mPendingCheckForTap = new CheckForTap();}mPendingCheckForTap.x = event.getX();mPendingCheckForTap.y = event.getY();// 点击查看ViewConfiguration.getTapTimeout()的代码,可以发现,获取到的值是100mms// 最终,mPendingCheckForTap也会执行else里面的checkFor...代码,所以逻辑是一样的postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());} else {// Not inside a scrolling container, so show the feedback right awaysetPressed(true, x, y);checkForLongClick(ViewConfiguration.getLongPressTimeout(),x,y,TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);}break;case MotionEvent.ACTION_CANCEL:...break;case MotionEvent.ACTION_MOVE:...break;}return true;}return false;
}// View的checkForLongClick
private void checkForLongClick(long delay, float x, float y, int classification) {if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {mHasPerformedLongPress = false;if (mPendingCheckForLongPress == null) {mPendingCheckForLongPress = new CheckForLongPress();}mPendingCheckForLongPress.setAnchor(x, y);mPendingCheckForLongPress.rememberWindowAttachCount();mPendingCheckForLongPress.rememberPressedState();mPendingCheckForLongPress.setClassification(classification);// mPendingCheckForLongPress是一个Runnable,看run方法就行// 再结合下面的run方法可以知道,如果设置了tooltip或longClick,按下去之后,不管是否有move,只要时间到了,就会触发设置的事件postDelayed(mPendingCheckForLongPress, delay);}
}// CheckForLongPress的run
public void run() {if ((mOriginalPressedState == isPressed()) && (mParent != null)&& mOriginalWindowAttachCount == mWindowAttachCount) {recordGestureClassification(mClassification);// 调用longClick,具体代码我就不贴出来了,只要知道有这件事就行了// 不过调用了,不代表一定就会执行longClick,具体要看是否设置了longClickListener或者tooltip// 当然了,有人可能会思考。在longClick响应之前就松开手指,会怎么办?这个就需要看ACTION_UP的代码,ACTION_UP有一行代码会将callback移除掉,以保证不会触发longClickif (performLongClick(mX, mY)) {// 可以看到longClick有handle,就会将mHasPerformedLongPress置为true,这很重要mHasPerformedLongPress = true;}}
}

回到源码分析

相关文章:

android事件分发机制源码分析

没什么用的前言责任链设计模式流程图源码分析 没什么用的前言 事件分发机制是面试中一道必问的题目&#xff0c;而我的应对方式则是&#xff0c;在网络上找一些博客看看&#xff0c;然后做一些笔记&#xff0c;最后在面试时将我自己记住的内容说出来。这种方式本身没有太大的…...

今天,小灰37岁了!

人们常常说&#xff0c;35岁是互联网人的中年危机。现在&#xff0c;小灰已经跨过了中年危机&#xff0c;倒不是因为小灰财务自由了&#xff0c;而是因为今天是小灰37岁的生日。年轻时候&#xff0c;小灰总觉得30岁是一个很遥远的年龄&#xff0c;而现在&#xff0c;小灰距离40…...

基于.NET 7 + iView 的前后端分离的通用后台管理系统开源框架

更多开源项目请查看&#xff1a;一个专注推荐.Net开源项目的榜单 今天给大家推荐一套前后端分离通用后台管理系统开源框架。 项目简介 这是基于.Net 7 Vue.js开发的、前后端分离框架&#xff0c;前端UI框架采用iView&#xff0c;该项目只有基础功能模块&#xff0c;不包含具…...

新一代通信协议—— RSocket

一、简介 RSocket 是一种二进制字节流传输协议&#xff0c;位于 OSI 模型中的5~6层&#xff0c;底层可以依赖 TCP、WebSocket、Aeron 协议。最初由 Netflix 开发&#xff0c;支持 Reactive Streams。其开发背后的动机是用开销更少的协议取代超文本传输协议(HTTP)&#xff0c;H…...

【编程实践】这个代码命名规范是真优雅呀!代码如诗!!(多读优秀的开源代码,多实践,你也可以一样优秀!)

目录 管理类命名 传播类命名 回调类命名 监控类命名 内存管理类命名 过滤检测类命名 结构类命名 常见设计模式命名 解析类命名 网络类命名 CRUD命名 其他 End 管理类命名 写代码&#xff0c;少不了对统一资源的管理&#xff0c;清晰的启动过程可以有效的组织代码…...

Linux->进程终止和等待

目录 1. 进程终止场景 1.1 进程退出码 1.2 进程常见退出方式 2. 进程等待 2.1 进程等待的必要性 2.2 进程等待的方式 wait()方式 waitpid()方式 options参数 status参数 1. 进程终止场景 代码运行完毕&#xff0c;结果正确 代码运行完毕&#xff0c;结果不正确 代码异…...

超店有数分享:tiktok数据分析工具推荐,助你成功出海!

现阶段的跨境电商人都纷纷入局tiktok&#xff0c;这是风口也是发展趋势。Tiktok的下载量已经超过了35亿&#xff0c;每月都有10亿用户活跃&#xff0c;在154国家/地区使用。Tiktok用户每天在平均花1小时左右进行浏览&#xff0c;打开率也很高。如今&#xff0c;tiktok也越来越成…...

2022 第十四届蓝桥杯模拟赛第三期(题解与标程)

第十四届蓝桥杯模拟赛第三期1. 最小的十六进制问题描述答案提交参考答案2. Excel的列问题描述答案提交参考答案3. 相等日期问题描述答案提交参考答案4. 多少种取法问题描述答案提交参考答案5. 最大连通分块问题描述答案提交参考答案6. 哪一天问题描述输入格式输出格式样例输入样…...

「TCG 规范解读」PC 平台相关规范(1)

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Alli…...

HNU工训中心:直流电路测量分析实验报告

工训中心的牛马实验 实验目的 1.熟悉直流电路的测量和分析方法。 2.熟悉直流电源、电压表、电流表的使用法及其特性。 实验仪器和器材 1.实验仪器 直流稳压电源型号:IT6302 台式多用表型号:UT805A 2.实验&#xff08;箱&#xff09;器材 电路实验箱 元器件&#xff1a;电阻…...

tensorflow2.4--1.框架介绍

前言 虽然1.x版本tensorflow有很多项目都基于此构建&#xff0c;然而随着2.x版本的推出&#xff0c;很多架构已经发生了改变&#xff0c;代码发生了改变&#xff0c;同时很多模组已经废弃不用或者更新,tensorflow1.x已经不能再兼容最新的项目,与时俱进是必要的&#xff0c;因此…...

c++11 关键字 final 使用

写在最前。。。 请支持原创~~ 1. 功能 用以指定一个 virtual function 不能被派生类重写&#xff1b;或者指定一个 class 不能被继承&#xff1b;2. 语法 对于类中成员函数有两种情况&#xff1a; 只声明时&#xff0c;final 紧跟参数的右括号&#xff0c;如果是纯虚函数&a…...

力扣(LeetCode)426. 将二叉搜索树转化为排序的双向链表(2023.02.28)

将一个 二叉搜索树 就地转化为一个 已排序的双向循环链表 。 对于双向循环列表&#xff0c;你可以将左右孩子指针作为双向循环链表的前驱和后继指针&#xff0c;第一个节点的前驱是最后一个节点&#xff0c;最后一个节点的后继是第一个节点。 特别地&#xff0c;我们希望可以…...

华为OD机试真题Python实现【玩牌高手】真题+解题思路+代码(20222023)

玩牌高手 题目 给定一个长度为N的整数数组,表示一个选手在N轮内选择的牌面分数, 选手基于规则选牌,请计算所有轮结束后其可以获得的最高总分数。 选择规则如下: 在每轮里选手可以选择获取该轮牌面,则其总分数加上该轮牌面分数为其新的总分数选手也可不选择本轮牌面,直接…...

“速通“ 老生常谈的HashMap [实现原理源码解读]

&#x1f473;我亲爱的各位大佬们好&#x1f618;&#x1f618;&#x1f618; ♨️本篇文章记录的为 HashMap 实现原理&&源码解读 相关内容&#xff0c;适合在学Java的小白,帮助新手快速上手,也适合复习中&#xff0c;面试中的大佬&#x1f649;&#x1f649;&#x1f…...

Linux系统介绍及熟悉Linux基础操作

一、什么是Liunx Linux&#xff0c;全称GNU/Linux&#xff0c;是一种免费使用和自由传播的类UNIX操作系统&#xff0c;其内核由林纳斯本纳第克特托瓦兹&#xff08;Linus Benedict Torvalds&#xff09;于1991年10月5日首次发布&#xff0c;它主要受到Minix和Unix思想的启发&am…...

mysql数据库limit的四种用法

文章目录前言一、语法二、参数说明三、常用示例-4种用法总结前言 mysql数据库中limit子句可以被用于强制select语句返回指定的记录数。limit接受一个或两个数字参数。参数必须是一个整数常量。如果给定两个参数&#xff0c;第一个参数指定第一个返回记录行的偏移量&#xff0c…...

嵌入式 linux 系统开发网络的设置

目录 一、前言 二、linux网络静态地址设置 前言 为什么要对linux系统下的ubuntu进行网络设置呢&#xff1f; 因为我们在嵌入式开发中&#xff0c;我们要保证windows系统、linux系统、开发板的ip要处于同一个网段&#xff0c;而默认ubuntu下的linux系统的ip是动态分配的&#…...

算法设计与分析——十大经典排序算法一(1--5)

目录 算法设计与分析——十大经典排序算法 第1关&#xff1a;冒泡排序 参考代码 第2关&#xff1a;选择排序 参考代码 第3关&#xff1a;插入排序 参考代码 第4关&#xff1a;希尔排序 参考代码 第5关&#xff1a;归并排序 参考代码 作者有言 一个不知名大学生&#x…...

六.慕课的冲击:知识何以有力量?

6.1知识就是力量?【单选题】关于技术进步,以下说法错误的是&#xff08; &#xff09;。A、技术进步可以不依靠知识积累B、知识的力量推动技术进步C、技术黑箱换句话说即是天上掉馅饼D、专利保护产生的垄断利润,构成创新动力我的答案&#xff1a;A【判断题】罗伯特索洛认为,技…...

SQL基础

sql基础笔记 DATEDIFF() 函数返回两个日期之间的时间。 DATEDIFF&#xff08;parameter1&#xff0c;parameter2&#xff0c;parameter3&#xff09; parameter1&#xff1a;可为 年月日时分秒或周 parameter2&#xff0c;parameter3&#xff1a;合法的日期 如&#xff1a…...

脏牛复现(CVE2016-5195)

nmap扫描全网段&#xff0c;发现存货主机&#xff0c;ip为192.168.85.141nmap 192.168.85.0/24nmap 扫描端口&#xff0c;发现80端口&#xff0c;访问该网站nmap -p1-65535 192.168.85.141扫描该网站目录&#xff0c;什么也没扫出来 &#xff0c;dirb扫描目录的字典在usr/share…...

Redis源码---内存友好的数据结构该如何细化设计

目录 前言 内存友好的数据结构 SDS 的内存友好设计 redisObject 结构体与位域定义方法 嵌入式字符串 压缩列表和整数集合的设计 节省内存的数据访问 前言 Redis 是内存数据库&#xff0c;所以&#xff0c;高效使用内存对 Redis 的实现来说非常重要而实际上&#xff0c;R…...

获取 本周、本月、本年 的开始或结束时间

获取 本周、本月、本年 的开始或结束时间 public class DateTimeUtil{// 获取 本周、本月、本年 的开始或结束时间/// <summary>/// 获取开始时间/// </summary>/// <param name"TimeType">Week、Month、Year</param>/// <param name&quo…...

算法训练营 day58 动态规划 判断子序列 不同的子序列

算法训练营 day58 动态规划 判断子序列 不同的子序列 判断子序列 392. 判断子序列 - 力扣&#xff08;LeetCode&#xff09; 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而…...

优思学院|DFMEA是全球制造业的必修课!

DFMEA&#xff08;Design Failure Mode and Effects Analysis&#xff09;是一种分析技术&#xff0c;在产品设计的早期阶段识别和解决潜在的失效问题。它通过分析设计的各个方面&#xff0c;识别潜在的失效模式和影响&#xff0c;并提出相应的改进措施&#xff0c;以减少失效的…...

【Day02数据结构 空间复杂度】

最近太忙了都好久没有更新博客了,太难了,抽空水一篇文章,大佬们多多支持. 上篇:时间复杂度分析 目录 前言 一、空间复杂度概念&#xff1f; 二、实例展示 三、.有复杂度要求的算法题练习 1.题目链接&#xff1a;力扣--消失的数字 2.题目链接&#xff1a;力扣--旋转数组 总结: 1…...

多数据库管理工具哪家强?ChatGPT点评,第一位并不是Navicat

SQL逐渐成为职场必备的编程语言&#xff0c;相信大家都不陌生。SQL是一种结构化查询语言&#xff0c;是用于数据库之间通信的编程语言。每个数据库都有着自己独特的访问规则&#xff0c;但大体上是遵循SQL标准。 因此&#xff0c;辗转于不同的数据库之间&#xff0c;开发者或D…...

UnityShader常用函数(UnityShader内置函数、CG和GLSL内置函数等)

空间变换函数函数名描述float4 UnityWorldToClipPos(float3 pos )把世界坐标空间中某一点pos变换到齐次裁剪空间float4 UnityViewToClipPos(float3 pos )把观察坐标空间中某一点pos变换到齐次裁剪空间float3 UnityObjectToViewPos(float3 pos或float4 pos)模型局部空间坐标系中…...

Springboot自定义注解-1

注解用于修饰其他的注解(纪委&#xff1a;管干部的干部) ① Retention&#xff1a;定义注解的保留策略 Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中&#xff0c;在class字节码文件中不包含 Retention(RetentionPolicy.CLASS) …...

经纬度标定及大地坐标系相关概念(一)

经纬度标定及大地坐标系相关概念&#xff08;一&#xff09;一、背景二、经纬度的概念三、大地坐标系四、大地坐标系的分类五、各类坐标系介绍5.1 我国地理坐标系5.1.1 北京54坐标系5.1.2 1980西安坐标系5.1.3 2000国家大地坐标系5.2 世界大地坐标系5.1.1 WGS84坐标系5.3 加密坐…...

synchronized关键字原理

synchronized原理 1、基本特点 基于锁策略&#xff0c;可以知道synchronized具有以下特性&#xff1a; 1.开始时候是乐观锁&#xff0c;如果锁冲突频繁就转换为悲观锁 2.开始是轻量级锁&#xff0c;如果锁持有的时间较长&#xff0c;就转换成重量级锁 3.实现轻量级锁的时候…...

面试被问死怎么办?学会这四招,通过的机率提升30%

软件工程师面试很难&#xff0c;难到什么程度呢&#xff1f;有一句话可以来形容&#xff1a; 面试造飞机&#xff0c;上班拧螺丝 没错&#xff0c;面试的时候各种问你原理、机制&#xff0c;而这些在实际工作中却很难用到。于是乎&#xff0c;很多工程师面试的时候就非常害怕…...

Android TV UI开发常用知识

导入依赖 Google官方为Android TV的UI开发提供了一系列的规范组件&#xff0c;在leanback的依赖库中&#xff0c;这里介绍一些常用的组件&#xff0c;使用前需要导入leanback库。 implementation androidx.leanback:leanback:$version常用的页面 这些Fragment有设计好的样式&…...

Xshell 下载及安装

文章目录下载安装连接服务器Xshell 是一个强大的安全终端模拟软件&#xff0c;它支持SSH1, SSH2, 以及Microsoft Windows 平台的TELNET 协议。Xshell 通过互联网到远程主机的安全连接以及它创新性的设计和特色帮助用户在复杂的网络环境中享受他们的工作。 Xshell可以在Windows界…...

【LeetCode】剑指 Offer(12)

目录 题目&#xff1a;剑指 Offer 30. 包含min函数的栈 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a; 题目&#xff1a;剑指 Offer 30. 包含m…...

vue在history模式下打包部署问题解决

引言 项目使用的模板是element-template&#xff0c;由于业务需要&#xff0c;我将路由的hash模式更改为了history模式&#xff0c;然后在打包部署项目时就出现了问题 个人发现是资源的访问路径有问题&#xff0c;在部署之后发现每次访问的js资源路径前都会自动携带上我路由的…...

Java中常见性能优化策略的总结

文章目录1. 代码优化2. 数据库层面优化SQL调优架构层面的调优连接池调优3. 网络优化4. 缓存缓存分类使用场景选型考虑什么时候更新缓存&#xff1f;如何保障更新的可靠性和实时性&#xff1f;缓存是否会满&#xff0c;缓存满了怎么办&#xff1f;缓存是否允许丢失&#xff1f;丢…...

c++日志库log4cplus使用

项目中需要打印log&#xff0c;方便程序调试和问题定位分析。C实现的log4cplus日志库是一种易于使用的C 日志记录API&#xff0c;可提供线程安全&#xff0c;灵活且任意粒度的日志管理和配置控制。 下面介绍一下在linux中安装log4cplus库过程 下载地址&#xff1a;https://gi…...

什么是接口测试,我们如何实现接口测试?

1. 什么是接口测试 顾名思义&#xff0c;接口测试是对系统或组件之间的接口进行测试&#xff0c;主要是校验数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及相互逻辑依赖关系。其中接口协议分为HTTP,WebService,Dubbo,Thrift,Socket等类型&#xff0c;测试类型又主…...

随机森林在sklearn中的实现

目录 一.集成算法 二.sklearn中的集成算法模块ensemble 三.RandomForestClassifier(随机森林分类器) 四.重要参数 1.基评估器参数 2.随机森林参数 五.重要属性和接口 六.Bagging的另一个必要条件 七.RandomForestRegressor(随机森林回归器) 八.机器学习中调参的基本思…...

[论文总结] 深度学习在农业领域应用论文笔记11

深度学习在农业上的应用笔记11 最近发表的相关论文数量不多&#xff0c;质量普遍也不尽如人意&#xff0c;尤其是《Computers and Electronics in Agriculture》这个期刊。这些论文的方法都很简单&#xff0c;只是强行将深度学习应用于某个问题上&#xff0c;而没有考虑到农业…...

Android 9.0 SystemUI 状态栏屏蔽弹出的悬浮式通知

1.概述 在9.0的系统ROM产品定制化开发中,在systemui的状态栏中,会在有闹钟 wifi连接等特殊弹窗通知的时候,会在接收到系统通知时,弹窗悬浮式弹窗通知,然后过几秒中, 就消失了,所以像这样的悬浮式通知,在有些产品中是不需要的,要求屏蔽掉,这就需要按照悬浮式流程来分析…...

商简智能计划与排程SPS在纺织行业中的应用

企业背景 某织造、染色及后整理一体化工艺的纺织面料企业&#xff0c;主要从事户外功能运动服装、内衣、泳衣、汽车内饰等面料的研发和销售&#xff0c;年产值在20亿左右&#xff0c;是迪卡侬运动面料最优质供应商之一。 纺织行业特点 印染具有典型的流程行业特性&#xff0c…...

549、RocketMQ详细入门教程系列 -【消息队列之 RocketMQ(三)】 2023.02.28

目录一、Spring 整合 RocketMQ1.1 消息生产者1.2 消息消费者1.3 Spring 配置文件1.4 运行实例程序二、参考链接一、Spring 整合 RocketMQ 不同于 RabbitMQ、ActiveMQ、Kafka 等消息中间件&#xff0c;Spring 社区已经通过多种方式提供了对这些中间件产品集成&#xff0c;例如通…...

如何使用SpringBoot ⽇志?

Spring Boot自定义日志的打印:在一个类中先获取到打印日志对象&#xff08;日志框架提供的日志对象&#xff0c;而日志框架默认已经集成到Spring Boot里了&#xff0c;springboot默认使用 slf4jlogback);注意&#xff1a;得到日志对象Logger ->来自于slf4j2、使用目志对象提…...

山东大学数字图像处理实验:MATLAB的图像显示方法

文章目录MATLAB 学习实验目的实验原理及方法实验内容MATLAB的图像显示方法实验目的实验内容MATLAB 学习 实验目的 了解 MATLAB 的基本功能及操作方法。掌握典型离散信号的 Matlab 产生和显示。 实验原理及方法 在 MATLAB 中, 序列是用矩阵向量表示, 但它没有包含采样信息, …...

Java缓存面试题——Redis解决方案

文章目录1、什么是缓存击穿&#xff1f;该如何解决2、什么是缓存穿透&#xff1f;该如何解决3、什么是缓存雪崩&#xff1f;该如何解决4、什么是BigKey&#xff1f;该如何解决bigkey的危害发现bigkey解决bigkey5、redis过期策略都有哪些&#xff1f;6、讲一讲Redis缓存的数据一…...

Flink:The generic type parameters of ‘Collector‘ are missing 类型擦除

类型擦除问题处理报错日志描述问题描述报错解决其他方法方法一&#xff1a;TypeInformation方法二&#xff1a;TypeHint报错日志描述 报错日志&#xff1a; The generic type parameters of Collector are missing. In many cases lambda methods dont provide enough informa…...

MySQL查询操作

系列文章目录前言一、简单查询SELECT子句SELECT后面之间跟列名DISTINCT,ALL列表达式列更名WHERE子句WHERE子句中可以使用的查询条件比较运算BETWEEN...AND...集合查询&#xff1a;IN模糊查询LIKE空值比较&#xff1a;IS NULL多重条件查询SELECT 的基本结构ORDER BY子句排序聚集…...