React事件机制源码解析

React v17里事件机制有了比较大的改动,想来和v16差别还是比较大的。

本文浅析的React版本为17.0.1,使用ReactDOM.render创建应用,不含优先级相关。

原理简述

React中事件分为委托事件(DelegatedEvent)和不需要委托事件(NonDelegatedEvent),委托事件在fiberRoot创建的时候,就会在root节点的DOM元素上绑定几乎所有事件的处理函数,而不需要委托事件只会将处理函数绑定在DOM元素本身。

同时,React将事件分为3种类型——discreteEvent、userBlockingEvent、continuousEvent,它们拥有不同的优先级,在绑定事件处理函数时会使用不同的回调函数。

React事件建立在原生基础上,模拟了一套冒泡和捕获的事件机制,当某一个DOM元素触发事件后,会冒泡到React绑定在root节点的处理函数,通过target获取触发事件的DOM对象和对应的Fiber节点,由该Fiber节点向上层父级遍历,收集一条事件队列,再遍历该队列触发队列中每个Fiber对象对应的事件处理函数,正向遍历模拟冒泡,反向遍历模拟捕获,所以合成事件的触发时机是在原生事件之后的。

Fiber对象对应的事件处理函数依旧是储存在props里的,收集只是从props里取出来,它并没有绑定到任何元素上。

源码浅析

以下源码仅为基础逻辑的浅析,旨在理清事件机制的触发流程,去掉了很多流程无关或复杂的代码。

委托事件绑定

这一步发生在调用了ReactDOM.render过程中,在创建fiberRoot的时候会在root节点的DOM元素上监听所有支持的事件。

function createRootImpl(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
) {
  // ...
  const rootContainerElement =
        container.nodeType === COMMENT_NODE ? container.parentNode : container;
  // 监听所有支持的事件
  listenToAllSupportedEvents(rootContainerElement);
  // ...
}

listenToAllSupportedEvents

在绑定事件时,会通过名为allNativeEvents的Set变量来获取对应的eventName,这个变量会在一个顶层函数进行收集,而nonDelegatedEvents是一个预先定义好的Set。

export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
  allNativeEvents.forEach(domEventName => {
    // 排除不需要委托的事件
    if (!nonDelegatedEvents.has(domEventName)) {
      // 冒泡
      listenToNativeEvent(
        domEventName,
        false,
        ((rootContainerElement: any): Element),
        null,
      );
    }
    // 捕获
    listenToNativeEvent(
      domEventName,
      true,
      ((rootContainerElement: any): Element),
      null,
    );
  });
}

listenToNativeEvent

listenToNativeEvent函数在绑定事件之前会先将事件名在DOM元素中标记,判断为false时才会绑定。

export function listenToNativeEvent(
  domEventName: DOMEventName,
  isCapturePhaseListener: boolean,
  rootContainerElement: EventTarget,
  targetElement: Element | null,
  eventSystemFlags?: EventSystemFlags = 0,
): void {
  let target = rootContainerElement;
	// ...
  // 在DOM元素上储存一个Set用来标识当前元素监听了那些事件
  const listenerSet = getEventListenerSet(target);
  // 事件的标识key,字符串拼接处理了下
  const listenerSetKey = getListenerSetKey(
    domEventName,
    isCapturePhaseListener,
  );

  if (!listenerSet.has(listenerSetKey)) {
    // 标记为捕获
    if (isCapturePhaseListener) {
      eventSystemFlags |= IS_CAPTURE_PHASE;
    }
    // 绑定事件
    addTrappedEventListener(
      target,
      domEventName,
      eventSystemFlags,
      isCapturePhaseListener,
    );
    // 添加到set
    listenerSet.add(listenerSetKey);
  }
}

addTrappedEventListener

addTrappedEventListener函数会通过事件名取得对应优先级的listener函数,在交由下层函数处理事件绑定。

这个listener函数是一个闭包函数,函数内能访问targetContainer、domEventName、eventSystemFlags这三个变量。

function addTrappedEventListener(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  isCapturePhaseListener: boolean,
  isDeferredListenerForLegacyFBSupport?: boolean,
) {
  // 根据优先级取得对应listener
  let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags,
  );

  if (isCapturePhaseListener) {
    addEventCaptureListener(targetContainer, domEventName, listener);
  } else {
    addEventBubbleListener(targetContainer, domEventName, listener);
  }
}

addEventCaptureListener函数和addEventBubbleListener函数内部就是调用原生的target.addEventListener来绑定事件了。

这一步是循环一个存有事件名的Set,将每一个事件对应的处理函数绑定到root节点DOM元素上。

不需要委托事件绑定

不需要委托的事件其中也包括媒体元素的事件。

export const nonDelegatedEvents: Set<DOMEventName> = new Set([
  'cancel',
  'close',
  'invalid',
  'load',
  'scroll',
  'toggle',
  ...mediaEventTypes,
]);
export const mediaEventTypes: Array<DOMEventName> = [
  'abort',
  'canplay',
  'canplaythrough',
  'durationchange',
  'emptied',
  'encrypted',
  'ended',
  'error',
  'loadeddata',
  'loadedmetadata',
  'loadstart',
  'pause',
  'play',
  'playing',
  'progress',
  'ratechange',
  'seeked',
  'seeking',
  'stalled',
  'suspend',
  'timeupdate',
  'volumechange',
  'waiting',
];

setInitialProperties

setInitialProperties方法里会绑定不需要委托的直接到DOM元素本身,也会设置style和一些传入的DOM属性。

export function setInitialProperties(
  domElement: Element,
  tag: string,
  rawProps: Object,
  rootContainerElement: Element | Document,
): void {
  let props: Object;
  switch (tag) {
    // ...
    case 'video':
    case 'audio':
      for (let i = 0; i < mediaEventTypes.length; i++) {
        listenToNonDelegatedEvent(mediaEventTypes[i], domElement);
      }
      props = rawProps;
      break;
    default:
      props = rawProps;
  }
  // 设置DOM属性,如style...
  setInitialDOMProperties(
    tag,
    domElement,
    rootContainerElement,
    props,
    isCustomComponentTag,
  );
}

switch里会根据不同的元素类型,绑定对应的事件,这里只留下了video元素和audio元素的处理,它们会遍历mediaEventTypes来将事件绑定在DOM元素本身上。

listenToNonDelegatedEvent

listenToNonDelegatedEvent方法逻辑和上一节的listenToNativeEvent方法基本一致。

export function listenToNonDelegatedEvent(
  domEventName: DOMEventName,
  targetElement: Element,
): void {
  const isCapturePhaseListener = false;
  const listenerSet = getEventListenerSet(targetElement);
  const listenerSetKey = getListenerSetKey(
    domEventName,
    isCapturePhaseListener,
  );
  if (!listenerSet.has(listenerSetKey)) {
    addTrappedEventListener(
      targetElement,
      domEventName,
      IS_NON_DELEGATED,
      isCapturePhaseListener,
    );
    listenerSet.add(listenerSetKey);
  }
}

值得注意的是,虽然事件处理绑定在DOM元素本身,但是绑定的事件处理函数不是代码中传入的函数,后续触发还是会去收集处理函数执行。

事件处理函数

事件处理函数指的是React中的默认处理函数,并不是代码里传入的函数。

这个函数通过createEventListenerWrapperWithPriority方法创建,对应的步骤在上文的addTrappedEventListener中。

createEventListenerWrapperWithPriority

export function createEventListenerWrapperWithPriority(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
): Function {
  // 从内置的Map中获取事件优先级
  const eventPriority = getEventPriorityForPluginSystem(domEventName);
  let listenerWrapper;
  // 根据优先级不同返回不同的listener
  switch (eventPriority) {
    case DiscreteEvent:
      listenerWrapper = dispatchDiscreteEvent;
      break;
    case UserBlockingEvent:
      listenerWrapper = dispatchUserBlockingUpdate;
      break;
    case ContinuousEvent:
    default:
      listenerWrapper = dispatchEvent;
      break;
  }
  return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer,
  );
}

createEventListenerWrapperWithPriority函数里返回对应事件优先级的listener,这3个函数都接收4个参数。

function fn(
  domEventName,
  eventSystemFlags,
  container,
  nativeEvent,
) {
  //...
}

返回的时候bind了一下传入了3个参数,这样返回的函数为只接收nativeEvent的处理函数了,但是能访问前3个参数。

dispatchDiscreteEvent方法和dispatchUserBlockingUpdate方法内部其实都调用的dispatchEvent方法。

dispatchEvent

这里删除了很多代码,只看触发事件的代码。

export function dispatchEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: AnyNativeEvent,
): void {
  // ...
  // 触发事件
  attemptToDispatchEvent(
    domEventName,
    eventSystemFlags,
    targetContainer,
    nativeEvent,
  );
  // ...
}

attemptToDispatchEvent方法里依然会处理很多复杂逻辑,同时函数调用栈也有几层,我们就全部跳过,只看关键的触发函数。

dispatchEventsForPlugins

dispatchEventsForPlugins函数里会收集触发事件开始各层级的节点对应的处理函数,也就是我们实际传入JSX中的函数,并且执行它们。

function dispatchEventsForPlugins(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
): void {
  const nativeEventTarget = getEventTarget(nativeEvent);
  const dispatchQueue: DispatchQueue = [];
  // 收集listener模拟冒泡
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
  // 执行队列
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}

extractEvents

extractEvents函数里主要是针对不同类型的事件创建对应的合成事件,并且将各层级节点的listener收集起来,用来模拟冒泡或者捕获。

这里的代码较长,删除了不少无关代码。

function extractEvents(
  dispatchQueue: DispatchQueue,
  domEventName: DOMEventName,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: null | EventTarget,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
): void {
  const reactName = topLevelEventsToReactNames.get(domEventName);
  let SyntheticEventCtor = SyntheticEvent;
  let reactEventType: string = domEventName;
	// 根据不同的事件来创建不同的合成事件
  switch (domEventName) {
    case 'keypress':
    case 'keydown':
    case 'keyup':
      SyntheticEventCtor = SyntheticKeyboardEvent;
      break;
    case 'click':
    // ...
    case 'mouseover':
      SyntheticEventCtor = SyntheticMouseEvent;
      break;
    case 'drag':
    // ...
    case 'drop':
      SyntheticEventCtor = SyntheticDragEvent;
      break;
    // ...
    default:
      break;
  }
  // ...
  // 收集各层级的listener
  const listeners = accumulateSinglePhaseListeners(
    targetInst,
    reactName,
    nativeEvent.type,
    inCapturePhase,
    accumulateTargetOnly,
  );
  if (listeners.length > 0) {
    // 创建合成事件
    const event = new SyntheticEventCtor(
      reactName,
      reactEventType,
      null,
      nativeEvent,
      nativeEventTarget,
    );
    dispatchQueue.push({event, listeners});
  }
}

accumulateSinglePhaseListeners

accumulateSinglePhaseListeners函数里就是在向上层遍历来收集一个列表后面会用来模拟冒泡。

export function accumulateSinglePhaseListeners(
  targetFiber: Fiber | null,
  reactName: string | null,
  nativeEventType: string,
  inCapturePhase: boolean,
  accumulateTargetOnly: boolean,
): Array<DispatchListener> {
  const captureName = reactName !== null ? reactName + 'Capture' : null;
  const reactEventName = inCapturePhase ? captureName : reactName;
  const listeners: Array<DispatchListener> = [];

  let instance = targetFiber;
  let lastHostComponent = null;

  // 通过触发事件的fiber节点向上层遍历收集dom和listener
  while (instance !== null) {
    const {stateNode, tag} = instance;
    // 只有HostComponents有listener (i.e. <div>)
    if (tag === HostComponent && stateNode !== null) {
      lastHostComponent = stateNode;

      if (reactEventName !== null) {
        // 从fiber节点上的props中获取传入的事件listener函数
        const listener = getListener(instance, reactEventName);
        if (listener != null) {
          listeners.push({
            instance,
            listener,
            currentTarget: lastHostComponent,
          });
        }
      }
    }
    if (accumulateTargetOnly) {
      break;
    }
    // 继续向上
    instance = instance.return;
  }
  return listeners;
}

最后的数据结构如下:

dispatchQueue的数据结构为数组,类型为[{ event,listeners }]。

这个listeners则为一层一层收集到的数据,类型为[{ currentTarget, instance, listener }]

processDispatchQueue

processDispatchQueue函数里会遍历dispatchQueue。

export function processDispatchQueue(
  dispatchQueue: DispatchQueue,
  eventSystemFlags: EventSystemFlags,
): void {
  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
  for (let i = 0; i < dispatchQueue.length; i++) {
    const {event, listeners} = dispatchQueue[i];
    processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
  }
}

dispatchQueue中的每一项在processDispatchQueueItemsInOrder函数里遍历执行。

processDispatchQueueItemsInOrder

function processDispatchQueueItemsInOrder(
  event: ReactSyntheticEvent,
  dispatchListeners: Array<DispatchListener>,
  inCapturePhase: boolean,
): void {
  let previousInstance;
  // 捕获
  if (inCapturePhase) {
    for (let i = dispatchListeners.length - 1; i >= 0; i--) {
      const {instance, currentTarget, listener} = dispatchListeners[i];
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  } else {
  // 冒泡
    for (let i = 0; i < dispatchListeners.length; i++) {
      const {instance, currentTarget, listener} = dispatchListeners[i];
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  }
}

processDispatchQueueItemsInOrder函数里会根据判断来正向、反向的遍历来模拟冒泡和捕获。

executeDispatch

executeDispatch函数里会执行listener。

function executeDispatch(
  event: ReactSyntheticEvent,
  listener: Function,
  currentTarget: EventTarget,
): void {
  const type = event.type || 'unknown-event';
  event.currentTarget = currentTarget;
  listener(event);
  event.currentTarget = null;
}

结语

本文旨在理清事件机制的执行,按照函数执行栈简单的罗列了代码逻辑,如果不对照代码看是很难看明白的,原理在开篇就讲述了。

React的事件机制隐晦而复杂,根据不同情况做了非常多的判断,并且还有优先级相关代码、合成事件,这里都没有一一讲解,原因当然是我还没看~

平时用React也就写写简单的手机页面,以前老板还经常吐槽加载不够快,那也没啥办法,就对我的工作而言,有没有Cocurrent都是无关紧要的,这合成事件更复杂,完全就是不需要的,不过React的作者们脑洞还是牛皮,要是没看源码我肯定是想不到竟然模拟了一套事件机制。

小思考

  • 为什么原生事件的stopPropagation可以阻止合成事件的传递?

这些问题我放以前根本没想过,不过今天看了源码以后才想的。

  • 因为合成事件是在原生事件触发之后才开始收集并触发的,所以当原生事件调用stopPropagation阻止传递后,根本到不到root节点,触发不了React绑定的处理函数,自然合成事件也不会触发,所以原生事件不是阻止了合成事件的传递,而是阻止了React中绑定的事件函数的执行。
<div 原生onClick={(e)=>{e.stopPropagation()}}>
  <div onClick={()=>{console.log("合成事件")}}>合成事件</div>
</div>

比如这个例子,在原生onClick阻止传递后,控制台连“合成事件”这4个字都不会打出来了。

以上就是React事件机制源码解析的详细内容,更多关于React事件机制源码的资料请关注我们其它相关文章!

(0)

相关推荐

  • React中阻止事件冒泡的问题详析

    前言 最近在研究react.redux等,网上找了很久都没有完整的答案,索性自己整理下,这篇文章就来给大家介绍了关于React阻止事件冒泡的相关内容,下面话不多说了,来一起看看详细的介绍吧 在正式开始前,先来看看 JS 中事件的触发与事件处理器的执行. JS 中事件的监听与处理 事件捕获与冒泡 DOM 事件会先后经历 捕获 与 冒泡 两个阶段.捕获即事件沿着 DOM 树由上往下传递,到达触发事件的元素后,开始由下往上冒泡. IE9 及之前的版本只支持冒泡 |  A  --------------

  • React事件节流效果失效的原因及解决

    今天做react项目中,给一个 input onKeyDown 事件做节流,出现了节流效果失效. 问题代码: render() { return ( <div className="search-bar"> <input className="search-input" type="text" placeholder="请输入要搜索的用户名(英文)" onKeyDown={this.throttle(this

  • 详细分析React 表单与事件

    本章节我们将讨论如何在 React 中使用表单. HTML 表单元素与 React 中的其他 DOM 元素有所不同,因为表单元素生来就保留一些内部状态. 在 HTML 当中,像 <input>, <textarea>, 和 <select> 这类表单元素会维持自身状态,并根据用户输入进行更新.但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState() 方法进行更新. 一个简单的实例 在实例中我们设置了输入框 input 值 value =

  • React学习之JSX与react事件实例分析

    本文实例讲述了React学习之JSX与react事件.分享给大家供大家参考,具体如下: 1.JSX 1.1.表达式 在React中使用JSX来描述HTML页面,而且可以与js混合使用,使用JavaScript表达式时要将表达式包含在大括号里 const user = { firstName: 'Harper', lastName: 'Perez' }; const element = ( //将JSX语句保存在变量中 <h1> Hello, {formatName(user)}! {/* {}

  • 详解react关于事件绑定this的四种方式

    在react组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件,而且react还会对这种引用进行缓存,以达到cpu和内存的最大化.在使用了es6 class或者纯函数时,这种自动绑定就不复存在了,我们需要手动实现this的绑定 React事件绑定类似于DOM事件绑定,区别如下: 1.React事件的用驼峰法命名,DOM事件事件命名是小写 2.通过jsx,传递一个函数作为event handler,而不是一个字符串. 3.React事件不能通过返回false来阻止默认事件,

  • React+Antd+Redux实现待办事件的方法

    之前也是写过一篇关于Redux的文章,来简单理解一下Redux,以及该如何使用.今天我就来分享一个也是入门级别的,React+Redux+antd来实现简单的待办事件.同时也讲讲自己对Redux的理解.先来看一张图吧: 我们简单的比喻来让我们更加好的理解Redux,我们这样比喻(图书馆借书): 1.React Component:借书人 2.Action Creators:你要说你要借书这句话,肯定要说话吧,就是一句话:我要借书 3.Store:图书馆管理员 4.Reducer:图书馆管理员肯定

  • React如何解决fetch跨域请求时session失效问题

    前言 fetch在reactjs中等同于 XMLHttpRequest,它提供了许多与XMLHttpRequest相同的功能,但被设计成更具可扩展性和高效性. Fetch 的核心在于对 HTTP 接口的抽象,包括 Request,Response,Headers,Body,以及用于初始化异步请求的 global fetch.得益于 JavaScript 实现的这些抽象好的 HTTP 模块,其他接口能够很方便的使用这些功能:除此之外,Fetch 还利用到了请求的异步特性--它是基于 Promise

  • 通过实例学习React中事件节流防抖

    节流 方法一 import Throttle from 'lodash-decorators/throttle'; export default class Search extends Component { constructor(props) { super(props) this.handleSearch = this.handleSearch.bind(this); } handleSubmit = (e) => { e.preventDefault(); this.handleSea

  • React事件机制源码解析

    React v17里事件机制有了比较大的改动,想来和v16差别还是比较大的. 本文浅析的React版本为17.0.1,使用ReactDOM.render创建应用,不含优先级相关. 原理简述 React中事件分为委托事件(DelegatedEvent)和不需要委托事件(NonDelegatedEvent),委托事件在fiberRoot创建的时候,就会在root节点的DOM元素上绑定几乎所有事件的处理函数,而不需要委托事件只会将处理函数绑定在DOM元素本身. 同时,React将事件分为3种类型--d

  • Spring AOP实现声明式事务机制源码解析

    目录 一.声明式全局事务 二.源码 三.小结: 一.声明式全局事务 在Seata示例工程中,能看到@GlobalTransactional,如下方法示例: @GlobalTransactional public boolean purchase(long accountId, long stockId, long quantity) { String xid = RootContext.getXID(); LOGGER.info("New Transaction Begins: " +

  • 详解Redis 缓存删除机制(源码解析)

    删除的范围 过期的 key 在内存满了的情况下,如果继续执行 set 等命令,且所有 key 都没有过期,那么会按照缓存淘汰策略选中的 key 过期删除 redis 中设置了过期时间的 key 会单独存储一份 typedef struct redisDb { dict *dict; // 所有的键值对 dict *expires; //设置了过期时间的键值对 // ... } redisDb; 设置有效期 Redis 中有 4 个命令可以给 key 设置过期时间,分别是 expire pexpi

  • react diff算法源码解析

    React中Diff算法又称为调和算法,对应函数名为reconcileChildren,它的主要作用是标记更新过程中那些元素发生了变化,这些变化包括新增.移动.删除.过程发生在beginWork阶段,只有非初次渲染才会Diff. 以前看过一些文章将Diff算法表述为两颗Fiber树的比较,这是不正确的,实际的Diff过程是一组现有的Fiber节点和新的由JSX生成的ReactElement的比较,然后生成新的Fiber节点的过程,这个过程中也会尝试复用现有Fiber节点. 节点Diff又分为两种

  • Netty分布式客户端处理接入事件handle源码解析

    目录 处理接入事件创建handle 我们看其RecvByteBufAllocator接口 跟进newHandle()方法中 继续回到read()方法 我们跟进reset中 前文传送门 :客户端接入流程初始化源码分析 上一小节我们剖析完成了与channel绑定的ChannelConfig初始化相关的流程, 这一小节继续剖析客户端连接事件的处理 处理接入事件创建handle 回到上一章NioEventLoop的processSelectedKey ()方法 private void processS

  • Kubernetes controller manager运行机制源码解析

    目录 Run StartControllers ReplicaSet ReplicaSetController syncReplicaSet Summary Run 确立目标 理解 kube-controller-manager 的运行机制 从主函数找到run函数,代码较长,这里精简了一下 func Run(c *config.CompletedConfig, stopCh <-chan struct{}) error { // configz 模块,在kube-scheduler分析中已经了解

  • jquery事件绑定解绑机制源码解析

    引子 为什么Jquery能实现不传回调函数也能解绑事件?如下: $("p").on("click",function(){ alert("The paragraph was clicked."); }); $("#box1").off("click"); 事件绑定解绑机制 调用on函数的时候,将生成一份事件数据,结构如下: { type: type, origType: origType, data: da

  • React实现合成事件的源码分析

    目录 事件绑定 事件触发 结尾 今天尝试学习 React 事件的源码实现. React 版本为 18.2.0 React 中的事件,是对原生事件的封装,叫做合成事件.抽象出一层合成事件,是为了做兼容,抹平不同浏览器之间的差异. 下面会从两个方面进行源码的解读: 事件绑定 事件触发 事件绑定 首先是 React 项目过程启动时,调用 listenToAllSupportedEvents 方法,做合成事件的绑定. // 对应方法 `ReactDOM.createRoot() function cre

  • Android源码解析之截屏事件流程

    今天这篇文章我们主要讲一下Android系统中的截屏事件处理流程.用过android系统手机的同学应该都知道,一般的android手机按下音量减少键和电源按键就会触发截屏事件(国内定制机做个修改的这里就不做考虑了).那么这里的截屏事件是如何触发的呢?触发之后android系统是如何实现截屏操作的呢?带着这两个问题,开始我们的源码阅读流程. 我们知道这里的截屏事件是通过我们的按键操作触发的,所以这里就需要我们从android系统的按键触发模块开始看起,由于我们在不同的App页面,操作音量减少键和电

  • Tomcat的类加载机制流程及源码解析

    目录 前言 1.Tomcat 的类加载器结构图: 2.Tomcat 的类加载流程说明: 3.源码解析: 4.为什么tomcat要实现自己的类加载机制: 前言 在前面 Java虚拟机:对象创建过程与类加载机制.双亲委派模型 文章中,我们介绍了 JVM 的类加载机制以及双亲委派模型,双亲委派模型的类加载过程主要分为以下几个步骤: (1)初始化 ClassLoader 时需要指定自己的 parent 是谁 (2)先检查类是否已经被加载过,如果类已经被加载了,直接返回 (3)若没有加载则调用父加载器 p

随机推荐