Android View系列(二):事件分发机制源码解析

概述

在介绍点击事件规则之前,我们需要知道我们分析的是MotionEvent,即点击事件,所谓的事件分发就是对MotionEvent事件的分发过程,即当一个MotionEvent生成以后,系统需要把这个事件传递给具体的View,而这个传递过程就是分发过程,MotionEvent我们上节已经介绍过

事件分发主要涉及以下几个方法:

  • dispatchTouchEvent:用来进行事件的分发,如果事件可以传递到当前View那么此方法一定会被调用,返回结果受当前View的onTouchEvent和子View的dispatchTouchEvent方法影响,表示是否消耗当前事件
  • onInterceptTouchEvent:在上个方法内部调用,用来判断是否拦截事件,如果当前View拦截了事件,那么在同一时间序列内,此方法不会再次被调用,返回结果表示是否拦截事件
  • onTouchEvent:在dispatchTouchEvent方法中调用,用于事件的处理,返回值表示是否消耗事件,如果不消耗当前View无法再次接受到事件

这三个方法到底有什么关系?

我们先简述一下他们之间的关系,之后再进行源码的详细分析

当一个事件传递给一个根ViewGroup之后,这时他的dispatchTouchEvent就会被调用,进行事件的分发,如果该ViewGroup的onInterceptTouchEvent返回true,表示他要拦截此事件,接着这个事件就会交给ViewGroup处理,即他的onTouchEvent就会被调用,如果他的onInterceptTouchEvent返回fasle就表示不拦截此事件,这时就会把此事件传递给他的子View,接着子View的dispatchTouchEvent就会被调用,如此反复直到事件最终被处理

源码分析

当一个事件产生后,他的传递遵循如下顺序Activity→Window→View,即事件总是县传递给Activity,然后Activity传递给Window,最后Window传递给顶级View,顶级View接收到事件后,就会按照事件分发机制分发事件

Activity对事件的分发

当一个点击操作发生时,事件最先传递给当前的Activity,由Activity的dispatchTouchEvent进行分发,我们看下Activity的dispatchTouchEvent的源码

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

上面代码表示,Activity会把事件交给Window处理,如果Window的分发返回true,表示事件就此结束,返回false,表示没有人处理,那么Activity的onTouchEvent就会被调用

Window对事件的分发

那么Window是怎么分发事件的呢?我们看下Window的源码,我们发现Window其实是一个抽象类,superDispatchTouchEvent也是一个抽象方法

public abstract boolean superDispatchTouchEvent(MotionEvent event);
复制代码

那么Window的实现类是什么?其实是PhoneWindow,那我们看一下PhoneWindow是怎么处理事件的

 @Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}
复制代码

PhoneWindow直接把事件交给了DecorView,DecorView其实就是最顶层的View我们setContentView的View就是DecorView的一个子View,DecorView继承自FrameLayout,这个时候事件已经分发到了ViewGroup上

ViewGroup事件的分发

现在我们看一下ViewGroup的dispatchTouchEvent方法的源码

    @Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {...
//--------TAG=1-------------------这里是一开始---------------------------------------------------//如果是Action_down 就对其先前所有的状态进行重置if (actionMasked == MotionEvent.ACTION_DOWN) {cancelAndClearTouchTargets(ev);resetTouchState();}
//--------TAG=2-----------------这里开始进行拦截验证-----------------------------------------------//如果是ACTION_DOWN,或者mFirstTouchTarget != null,就进行拦截验证final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {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;}
//------------------------------------------------------------------------------------------------------------------------------....//----------TAG=3----------------这里看是遍历子view---------------------------------------------------------------//如果不拦截,并且不是cancel事件,就进行遍历子view分发事件if (!canceled && !intercepted) {...//当ACTION_DOWN和ACTION_POINTER_DOWN和ACTION_HOVER_MOVE时候才会遍历子viewif (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {//找到可以接受触摸事件孩子,从前向后遍历查找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);...//判断触摸点是否在此View的范围中,是否在移动if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue;}...//分发事件,如果事件被子view消费,就跳出循环,不再继续分发给其他viewif (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...//addTouchTarget内部赋值mFirstTouchTarget=当前viewnewTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;}}
//-----------TAG=4-----------------这里已经遍历完了子view--------------------------------------------// //遍历完所有的子View后,还没有处理事件,就自己处理if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} else {//Action_Down之外的事件直接分发给目标viewTouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;//如果上方遍历已经传递过改事件,则跳过本次事件if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;} else {final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}...}}
//------------------------------------------------------------------------------------------------------------------------------// 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;}
复制代码

首先我们分析一下拦截事件的源码

   			//如果是ACTION_DOWN,或者mFirstTouchTarget != null,就进行拦截验证final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOW || mFirstTouchTarget != null) {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;}
复制代码

这段代码我们可以看到,有俩种情况会判断是否要拦截当前事件,事件类型是Action_Down,或者mFirstTouchTarget != null,ACTION_DOWN我们可以理解,mFirstTouchTarget != null代表什么呢?

我们从后面的代码可以看出,事件由ViewGroup的子元素处理成功时,mFirstTouchTarget被赋值并指向该子元素,也就是说当ViewGroup不拦截事件交由子元素处理时mFirstTouchTarget != null

一旦ViewGroup拦截事件mFirstTouchTarget != null就不成立,而当ACTION_MOVE ,ACTION_UP到来时,由于(actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)这个判断为false,ViewGroup的onInterceptTouchEvent不在会被调用,并且同一序列的其他事件,会默认交给ViewGroup处理

这里还有一种特殊情况,FLAG_DISALLOW_INTERCEPT标志位,这个标志位是通过requestDisallowInterceptTouchEvent来设置的,一般用于子View中,一旦FLAG_DISALLOW_INTERCEPT标志为被设置后,ViewGroup将无法拦截,除了ACTION_DOWN之外的其他事件,为什么要除了ACTION_DOWN呢,因为每当ACTION_DOWN带来都会重置FLAG_DISALLOW_INTERCEPT这个标记位,ACTION_DOWN事件总会调用自己的onInterceptTouchEvent询问是否拦截

强调一点requestDisallowInterceptTouchEvent,这个方法并不是万能的,执行他的前提是子View必须获取事件,假如父View的Down事件的onInterceptTouchEvent就返回true,拦截事件,那么子View做任何操作也不可能获取到事件

从上面分析我们可以得出结论

  • 当ViewGroup决定拦截事件的时候,那么后续的点击事件将默认交给他,不再调用onInterceptTouchEvent
  • FLAG_DISALLOW_INTERCEPT作用是让ViewGroup不再拦截事件,前提是ViewGroup不拦截Action_Down事件
  • onInterceptTouchEvent不是每次都会调用的,如果我们要提前处理点击事件需要在dispatchTouchEvent
  • 当我们遇到滑动冲突的时候,可以考虑FLAG_DISALLOW_INTERCEPT来处理

我们看一下ViewGroup不拦截的事件的情况

先看一下源码,这个是删减后的源码,看起来比较清楚

            //如果不拦截,并且不是cancel事件,就进行遍历子view分发事件if (!canceled && !intercepted) {...//当ACTION_DOWN和ACTION_POINTER_DOWN和ACTION_HOVER_MOVE时候才会遍历子viewif (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {//找到可以接受触摸事件孩子,从前向后遍历查找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);...//判断触摸点是否在此View的范围中,是否在移动if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue;}...//分发事件,如果事件被子view消费,就跳出循环,不再继续分发给其他viewif (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...//addTouchTarget内部赋值mFirstTouchTarget=当前viewnewTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;}}
复制代码

首先遍历ViewGroup的所有子元素,然后判断判断子元素是否能接收到点击事件,是否能接收到点击事件主要由俩点来衡量

  • 点击的坐标是否落在了子元素的区域内
  • 子元素是否在播放动画

如果子元素满足这俩个条件,那么事件将传递给他处理,分发事件其实dispatchTransformedTouchEvent是这个方法做的,我们看一下dispatchTransformedTouchEvent源码

 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;//先记住这一段判断cancel的源码,很重要下面分析if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {event.setAction(MotionEvent.ACTION_CANCEL);if (child == null) {handled = super.dispatchTouchEvent(event);} else {handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;}....if (child == null) {handled = super.dispatchTouchEvent(event);} else {...handled = child.dispatchTouchEvent(event);}.....return handled;}
复制代码

这里面主要代码如果 if (cancel || oldAction == MotionEvent.ACTION_CANCEL) 为false,这个判断的意思是,如果不是ACTION_CANCEL,外部传入的cancel也为fasle,就进行下面的判断,而下面的判断主要是根据传入的child是否为null来判断的,如果child不为null,那么就调用child的dispatchTouchEvent方法,这个事件就交给子元素去处理,这就完成一轮的事件分发

如果child的dispatchTouchEvent返回为true,先不考虑事件怎么在子元素中分发,那么mFirstTouchTarget就被赋值,跳出for循环

    //分发事件,如果事件被子view消费,就跳出循环,不再继续分发给其他viewif (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...//addTouchTarget内部赋值mFirstTouchTarget=当前viewnewTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;}
复制代码

上面的代码完成了,给mFirstTouchTarget赋值,并且跳出for循环,终止对子元素的遍历,如果子元素的dispatchTouchEvent返回fasle,那么就会继续遍历子元素,把事件传递给下一个合适的子元素(如果还有合适的子元素的话)

mFirstTouchTarget赋值是在addTouchTarget方法内部完成的,mFirstTouchTarget是一个单链表结构

  private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);//注意这里这里很重要,target.next =null,然后 mFirstTouchTarget = target;也就是说这时候的 mFirstTouchTarget.next=nulltarget.next = mFirstTouchTarget;mFirstTouchTarget = target;return target;}
复制代码

如果遍历所有的子元素事件都没有合适的处理,这里包含俩种情况,一种就是ViewGroup没有子元素,第二种就是子元素的dispatchTouchEvent返回了fasle,这俩种情况下ViewGroup会自己处理事件

       //遍历完所有的子View后,还没有处理事件,就自己处理if (mFirstTouchTarget == null) {handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);}
复制代码

注意这里child参数传入的是null,根据之前的分析就会调用 super.dispatchTouchEvent(event);由于ViewGroup也是继承自View,这里就会转到View的dispatchTouchEvent,即点击事件交给View处理

注意敲黑板了啊

我看了很多博客,都没有对这种情况进行分析,这个问题一度卡了我很久

现在考虑一种情况,如果父View的onInterceptTouchEvent的Down事件返回false不拦截,move up事件返回true拦截,这个效果就是子View只能收到Down事件而收不到Up和Move事件

那么我们现在分析一下这种情况,按照我们上方的分析,父View的Down事件不拦截,那么mFirstTouchTarget就会被赋值,第二次Move和Up事件要拦截,但是由于mFirstTouchTarget被赋值了,所以是走不到下面这步的

    // //遍历完所有的子View后,还没有处理事件,就自己处理if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);}
复制代码

那么父View是怎么拦截Move和Up事件的呢? 当地一个Move事件传递给父View后,此时mFirstTouchTarget不为null,所以走拦截这一步代码

		//如果是ACTION_DOWN,或者mFirstTouchTarget != null,就进行拦截验证final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOW || mFirstTouchTarget != null) {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;}
复制代码

拦截返回true后,不走遍历子Vew代码,直接到最后的判断代码

    // //遍历完所有的子View后,还没有处理事件,就自己处理if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} else {//Action_Down之外的事件直接分发给目标viewTouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;//如果上方遍历已经传递过改事件,则跳过本次事件if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;} else {final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}if (cancelChild) {if (predecessor == null) {mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;continue;}}}
复制代码

由于mFirstTouchTarget在Down的时候已经赋值不为null,会走下边代码

       final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;
复制代码

由于拦截事件,cancelChild为true,也就是说下面这个分发dispatchTransformedTouchEvent的方法传入的是true

     if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}
复制代码

在这个分发方法里,有判断Cancel事件的代码

 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;//先记住这一段判断cancel的源码,很重要下面分析if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {event.setAction(MotionEvent.ACTION_CANCEL);if (child == null) {handled = super.dispatchTouchEvent(event);} else {handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;}...return handled;}
复制代码

由于传入的cancel为true, 会重新定义事件为Cancel事件event.setAction(MotionEvent.ACTION_CANCEL);child不为null所以会调用child.dispatchTouchEvent(event);也就是说第一个Move事件,父View不会拦截,但会给子View发送一个Cancel事件

接下来会继续走代码

  TouchTarget target = mFirstTouchTarget;final TouchTarget next = target.next;
...if (cancelChild) {...mFirstTouchTarget = next;...}
复制代码

上面已经分析过cancelChild为true,进入方法给mFirstTouchTarget重新赋值mFirstTouchTarget.next,那么mFirstTouchTarget.next等于什么?看下面一段代码

  private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);//注意这里这里很重要,target.next =null,然后 mFirstTouchTarget = target;也就是说这时候的 mFirstTouchTarget.next=nulltarget.next = mFirstTouchTarget;mFirstTouchTarget = target;return target;}
复制代码

其实mFirstTouchTarget.next=null,那整合起来就是把mFirstTouchTarget重新赋值为null,从这里开始,第二个Move事件就会直接传递给父View完成了拦截

    if (mFirstTouchTarget == null) {handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} 
复制代码

总结

当父View不拦截Down事件,但要拦截Move和Up事件时,第一个Move事件会重新赋值为Cancel事件发送给子View,然后mFirstTouchTarget赋值为null,第二次开始的Move事件就会交给父View

View的事件分发源码

View对事件的处理比较简单,注意这里的View不包括ViewGroup,先看他的dispatchTouchEvent

 public boolean dispatchTouchEvent(MotionEvent event) {...boolean result = false;ListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {result = true;}if (!result && onTouchEvent(event)) {result = true;}}...return result;}
复制代码

View的时间传递比较简单,因为View(不包括ViewGroup),是一个单独的元素,无法向下传递事件,所以没有onInterceptTouchEvent方法,从上面源码可以看出

  • 首先会判断与没有mOnTouchListener,如果有并且其中的onTouch方法返回true那么onTouchEvent放方法不会调用,可以看出mOnTouchListener的优先级高于onTouchEvent

下面看一下onTouchEvent方法的源码

首先看一下,当View处于不可用状态下,事件的处理过程

   final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;if ((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.return clickable;}
复制代码

可以看出不可用的状态下,View消耗点击事件

再看一下对具体事件的处理

   final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {case MotionEvent.ACTION_UP:...if (mPerformClick == null) {mPerformClick = new PerformClick();}if (!post(mPerformClick)) {performClickInternal();}}}....case MotionEvent.ACTION_DOWN:...case MotionEvent.ACTION_CANCEL:...case MotionEvent.ACTION_MOVE:...break;}复制代码

从上面代码为可以看出

  • 只要View的CLICKABLE和LONG_CLICKABLE一个为true,不管他是不是DISABLED状态都消耗事件,只不过DISABLED不走下面的down,up事件
  • 当Action_Up触发时,会调用PerformClick方法,如果View设置了onClickListener,那么PerformClick将调用他的onClick方法
  • View的LONG_CLICKABLE默认是false,但是CLICKABLE是否为fasle,跟具体View有关,可点击的CLICKABLE为true,不可点击的CLICKABLE为false
  • setClickable和setLongClickable可以改变CLICKABLE,和LONG_CLICKABLE的值
  • setClickLinsterer和setLongClickLinsterer会自动设置CLICKABLE和LONG_CLICKABLE为true

到这里事件分发就处理完了

参考:Android开发艺术探索

allenfeng.com/2017/02/22/…

转载于:https://juejin.im/post/5d4d212d5188253afe4c8ada

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/386238.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零开始系统化学Android,值得收藏!

前言 每年的3、4月份是各大企业为明年拓展业务大量吸纳人才的关键时期&#xff0c;招聘需求集中、空缺岗位多&#xff0c;用人单位也习惯在初秋进行大规模招聘。 金九银十&#xff0c;招聘旺季&#xff0c;也是一个求职旺季。 不打无准备的仗&#xff0c;在这种关键时期&…

[转]清华梦的粉碎——写给清华大学的退学申请

[转]清华梦的粉碎——写给清华大学的退学申请 读了全文&#xff0c;感同身受&#xff0c;全文转载。 By 王垠&#xff08;2005.09.22&#xff09; 作者王垠&#xff0c;非常有思想的一个人&#xff0c;川大计算机系97级本科&#xff0c;2001年毕业后直博保送清华大学计算机系&a…

网易严选Java开发三面面经:java技术编程培训班

前言 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。提供了完善的认证机制和方法级的授权功能。是一款非常优秀的权限管理框架。它的核心是一组过滤器链&#xff0c;不同的功能经由不同的过滤器。这篇文章就是想通过一个小案例将Spring Security整合到…

第九篇 并发(进程和线程)

Python里执行并发有进程和线程两个&#xff0c;分布使用 threading 和multiprocessing 两个库&#xff0c;一般用的是这两个库里的Thread和Process from threading import Thread # 线程 : 同时运行from multiprocessing import Process # 进程 进程和线程的使用场景&#x…

网易严选Java开发三面面经:java读文件内容

一、前言 Redis 提供了5种数据类型&#xff1a;String&#xff08;字符串&#xff09;、Hash&#xff08;哈希&#xff09;、List&#xff08;列表&#xff09;、Set&#xff08;集合&#xff09;、Zset&#xff08;有序集合&#xff09;&#xff0c;理解每种数据类型的特点对于…

网易严选Java开发三面面经:mysql索引面试题

写在前面 最近&#xff0c;很多小伙伴出去面试都被问到了Spring问题&#xff0c;关于Spring&#xff0c;细节点很多&#xff0c;面试官也非常喜欢问一些很细节的技术点。所以&#xff0c;在 Spring 专题中&#xff0c;我们尽量把Spring的每个技术细节说清楚&#xff0c;将透彻…

网易严选Java开发三面面经:南京黑马java培训怎么样

个人背景 如标题所示&#xff0c;我的个人背景非常简单&#xff0c;Java开发经验1年半&#xff0c;学历普通&#xff0c;2本本科毕业&#xff0c;毕业后出来就一直在Crud&#xff0c;在公司每天重复的工作对我的技术提升并没有什么帮助&#xff0c;但小镇出来的我也深知自我努…

网易架构师深入讲解Java开发!BAT等大厂必问技术面试题

前言 MyBatis是目前非常流行的ORM框架&#xff0c;它的功能很强大&#xff0c;然而其实现却比较简单、优雅。本文主要讲述MyBatis的架构设计思路&#xff0c;并且讨论MyBatis的几个核心部件&#xff0c;然后结合一个select查询实例&#xff0c;深入代码&#xff0c;来探究MyBa…

IOS 消息转发

最近在看消息转发的资料&#xff0c;发现大部分都是理论知识&#xff0c;很少有完整的代码。现在以代码的形式形象的解释一下&#xff1a; 用Xcode创建一个工程 1.正常方法调用 创建一个类Person 代码如下 Person.h代码如下&#xff1a; #import <Foundation/Foundation.h&g…

网易资深Java架构师:java数组对象转为list集合

前言 现在刷抖音经常可以看到一些老外街坊&#xff0c;问他们最想把什么带回自己的国家&#xff0c;我听过很多的回答都是&#xff1a;淘宝&#xff0c;支付宝&#xff0c;美食&#xff0c;微信&#xff0c;外卖&#xff0c;高铁等等。 确实如此&#xff0c;随着国家的快速发…

夯实基础——P2084 进制转换

题目链接&#xff1a;https://www.luogu.org/problem/P2084 P2084 进制转换 题目背景 无 题目描述 今天小明学会了进制转换&#xff0c;比如&#xff08;10101&#xff09;2 &#xff0c;那么它的十进制表示的式子就是 : 1*2^40*2^31*2^20*2^11*2^0&#xff0c; 那么请你编程实…

网易资深Java架构师:java方法的定义和使用

前言 今年因为这个疫情&#xff0c;感觉这是从工作以来过的最久的一个年了&#xff0c;在家呆的时间不是一般的久&#xff0c;算一算有好几个月呢&#xff01;我大概是3月底快4月了才出门&#xff0c;投了超多的简历&#xff0c;天天面试面试面试面试面试面试面试…庆幸的是还…

PHP----学生管理系统

闲来无事花费两天时间写了份简易版的学生管理系统 源码地址:https://www.cnblogs.com/post/ReadAuth?blogId509327&PostId11333758&url%2Fbyczyz%2Fprotected%2Fp%2F11333758.html 转载于:https://www.cnblogs.com/byczyz/p/11333760.html

网易资深Java架构师:jdkjrejvm的区别和联系

前言 作为同时具备高性能、高可靠和高可扩展性的典型键值数据库&#xff0c;Redis不仅功能强大&#xff0c;而且稳定&#xff0c;理所当然地成为了大型互联网公司的首选。 众多大厂在招聘的时候&#xff0c;不仅会要求面试者能简单地使用Redis&#xff0c;还要能深入地理解底…

深度学习之开端备注

Adagrad //适合稀疏样本 RMSprop//借鉴Adagrad的思想&#xff0c;改进使得不会出现学习率越来越低的问题 由此可见Adadelta既不需要输入学习率等参数&#xff0c;而且表现得非常好&#xff01;&#xff01;但是我试了几次&#xff0c;这个优化器效果极差&#xff01;&#xff0…

网易资深Java架构师:疫情对java行业的影响分析

前言 在实际开发&#xff0c;Redis使用会频繁&#xff0c;那么在使用过程中我们该如何正确抉择数据类型呢&#xff1f;哪些场景下适用哪些数据类型。而且在面试中也很常会被面试官问到Redis数据结构方面的问题&#xff1a; Redis为什么快呢&#xff1f;为什么查询操作会变慢了…

ListView与.FindControl()方法的简单练习 #2 -- ItemUpdting事件中抓取「修改后」的值

原文出處 http://www.dotblogs.com.tw/mis2000lab/archive/2013/06/24/listview_itemupdating_findcontrol_20130624.aspx ListView与.FindControl()方法的简单练习 #2 -- ItemUpdting事件中抓取「修改后」的值 本文跟上一篇文章有关连&#xff0c;请依照顺序来练习&#xff1…

美团java研发岗二面:java静态方法存储在哪个区

思维导图 前言 在很多时候&#xff0c;我们都可以在各种框架应用中看到ZooKeeper的身影&#xff0c;比如Kafka中间件&#xff0c;Dubbo框架&#xff0c;Hadoop等等。为什么到处都看到ZooKeeper&#xff1f; 一、 前些年&#xff0c;互联网行业里对架构师这个岗位的标准还不是…

[学习之道] 修福不修慧,大象披璎珞; 修慧不修福,罗汉托空钵 (学习写程序,只靠补习上课吗?)...

这是我的备份&#xff0c;原文请看 http://www.dotblogs.com.tw/mis2000lab/archive/2014/09/17/learning-and_do-it_20140917.aspx [学习之道] 修福不修慧&#xff0c;大象披璎珞&#xff1b; 修慧不修福&#xff0c;罗汉托空钵 (学习写程序&#xff0c;只靠补习上课吗&#…

阿里P8亲自教你!mysql列转行

前言 今日博主听闻&#xff0c;现在很多培训出来的应届生薪资都赶上了摸爬滚打两三年的朋友&#xff0c;讲道理&#xff0c;这说不过去啊 作为同行来说&#xff0c;这个行业发展很快&#xff0c;技术更新很快&#xff0c;淘汰也很快&#xff0c;千万不要再找借口了&#xff0…