Android 中手指从按钮 A 平移到 B,会发生什么?为什么?

news2024/11/25 23:22:27

作者:TechMerger

前言

Touch 相关问题是 Android 面试中常问的点,不一定要求大家都从 InputFlinger 底层开始回答,但起码需要了解 Touch 抵达 App 之后的完整处理。而即便是这段偏上层的链路,也不要局限在老生常谈的过程复述,需要深刻理解、灵活运用其中的细节和原则。

本文结合一个简单的 Touch 场景的问答,带大家加深一下 Touch 分发的理解。

  1. Button A 和 B 相邻,手指不抬起、从 A 平移到 B,A 会发生什么?为什么?
  2. 此刻,B 又会发生什么?为什么?
  3. 之后,手指再从 B 平移回 A 后,又会发生什么?为什么?
  4. 最后,在 A 上抬起手指,A 会触发点击吗?为什么?

验证

我们自定义两个 Button 分别覆写其 onTouchEvent(),在一个 ConstraintLayout 中上下紧密地放置它们,并为了区分设置为不同的背景色。

按照提问的问题步骤开始尝试一下。

可以看到手指平移到 B 的那一刻,A 的 press 效果没有了,而 B 没有任何反应。即便移动回 A,A 也无法恢复 press 效果,抬起之后也没有触发 click。

解答

解答原理之前,我们先看下 log,再逐一解释。

 // 手指在 A 上按下
 2023-09-12 18:11:25.209 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=109.9668, y[0]=74.92432, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=1823125, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=530500549 }
 ​
 // 手指开始向下移动
 2023-09-12 18:11:25.586 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=109.9668, y[0]=78.92334, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=1823538, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=348888341 }
 2023-09-12 18:11:25.633 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=109.9668, y[0]=82.92236, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=1823591, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=354173977 }
 ...
 2023-09-12 18:11:26.200 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=121.96387, y[0]=155.50244, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=1824161, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=195296965 }
 2023-09-12 18:11:26.216 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=121.96387, y[0]=163.84363, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=1824177, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=273686682 }
 ​
 // Button 高度为 168px,此刻已开始出界到 B
 2023-09-12 18:11:26.233 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=121.96387, y[0]=174.2472, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=1824194, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=758026894 }
 2023-09-12 18:11:26.250 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=121.96387, y[0]=178.18982, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=1, eventTime=1824211, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=498491454 }
 ...
 2023-09-12 18:11:26.801 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=129.96191, y[0]=266.87744, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=1824754, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=936130601 }
 ​
 // 手指开始往上移动
 2023-09-12 18:11:27.484 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=129.96191, y[0]=262.87842, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=1825443, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=17662257 }
 ...
 2023-09-12 18:11:27.585 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=137.95996, y[0]=244.88281, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=1825541, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=507118427 }
 ...
 2023-09-12 18:11:27.966 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=145.16235, y[0]=175.69556, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=1825927, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=876127266 }
 ​
 // Button 高度为 168px,此刻已移动回到 A
 2023-09-12 18:11:27.985 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=145.95801, y[0]=166.91626, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=1825944, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=352798882 }
 2023-09-12 18:11:28.000 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=149.15863, y[0]=162.90283, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=1825961, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=99105321 }
 ...
 2023-09-12 18:11:28.369 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=161.9541, y[0]=86.92139, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=1826312, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=764248821 }
 2023-09-12 18:11:28.722 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=161.9541, y[0]=90.92041, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=1826673, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=197617005 }
 ​
 // 手指从 A 上抬起
 2023-09-12 18:11:28.947 8944-8944/com.ellison.demo D/Touch: ButtonA#onTouchEvent() event:MotionEvent { action=ACTION_UP, actionButton=0, id[0]=0, x[0]=161.9541, y[0]=90.92041, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=1826912, downTime=1823125, deviceId=8, source=0x5002, displayId=0, eventId=250168391 }

1. 平移到 B,A 会发生什么?

A 的 pressed 效果会被重置。

以往大家会直观地以为这是 ViewGroup 发送 ACTION_CANCEL 给 ButtonA 造成了的。

但观察 log 你会发现,即便出界了,ACTION_MOVE 始终发给了 ButtonA。同时,随着手指的不断向下移动,ACTION_MOVE 的 y 相对坐标不断增大,当该 y 数值超过了 mBottom - mTop 的高度差的时候,Button 的父亲 View 的 onTouchEvent() 会基于其离开了 View 边界调用 setPressed(false) 去刷新 View 的 Press 状态,继而促使 ButtonA 的按下状态消失了。

 public class View ... {
     ...
     public boolean onTouchEvent(MotionEvent event) {
         ...
         if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
             switch (action) {
                 ...
                 case MotionEvent.ACTION_MOVE:
                     ...
                     // Be lenient about moving outside of buttons
                     if (!pointInView(x, y, touchSlop)) {
                         ...
                         if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                             setPressed(false);
                         }
                         mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                     }
                     ...
                     break;
             }
 ​
             return true;
         }
 ​
         return false;
     }
 ​
     /*package*/ final boolean pointInView(float localX, float localY) {
         return pointInView(localX, localY, 0);
     }
 ​
     public boolean pointInView(float localX, float localY, float slop) {
         return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
                 localY < ((mBottom - mTop) + slop);
     }
     ...
 }

2. B 会发生什么?为什么?

B 没有任何反应。

其实,解答问题 1 时已经侧面解答了 B 没有反应的直接原因:ButtonB 没有收到任何 TouchEvent。

那为什么即便手指移动到了 B 区域,系统仍不发送事件过去呢?

Button 的父布局 ViewGroup 在分发 ACTION_DOWN 的时候,通过 addTouchTarget() 将处理 DOWN 事件的 child 赋值到 mFirstTouchTarget。后续来了 ACTION_MOVE 的时候,发现 mFirstTouchTarget 已存在,就将后续事件通过 dispatchTransformedTouchEvent() 继续发给该 TouchTarget

源码中的注释也体现了这点:

Dispatch to touch targets, excluding the new touch target if we already dispatched to it.

 public abstract class ViewGroup extends View implements ViewParent, ViewManager {
     ...
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         ...
         boolean handled = false;
         if (onFilterTouchEventForSecurity(ev)) {
             ...
             if (!canceled && !intercepted) {
                 ...
                 if (actionMasked == MotionEvent.ACTION_DOWN ...) {
                     ...
 ​
                     final int childrenCount = mChildrenCount;
                     if (newTouchTarget == null && childrenCount != 0) {
                         ...
                         final View[] children = mChildren;
                         for (int i = childrenCount - 1; i >= 0; i--) {
                             ...
                             resetCancelNextUpFlag(child);
                             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                 // Child wants to receive touch within its bounds.
                                 mLastTouchDownTime = ev.getDownTime();
                                 if (preorderedList != null) {
                                     // childIndex points into presorted list, find original index
                                     for (int j = 0; j < childrenCount; j++) {
                                         if (children[childIndex] == mChildren[j]) {
                                             mLastTouchDownIndex = j;
                                             break;
                                         }
                                     }
                                 } else {
                                     mLastTouchDownIndex = childIndex;
                                 }
                                 mLastTouchDownX = ev.getX();
                                 mLastTouchDownY = ev.getY();
                                 newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                 alreadyDispatchedToNewTouchTarget = true;
                                 break;
                             }
                             ...
                         }
                         ...
                     }
                     ...
                 }
             }
 ​
             if (mFirstTouchTarget == null) {
                 handled = dispatchTransformedTouchEvent(ev, canceled, null,
                         TouchTarget.ALL_POINTER_IDS);
             } else {
                 TouchTarget 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;
                         }
                         ...
                     }
                     predecessor = target;
                     target = next;
                 }
             }
             ...
         }
         ...
         return handled;
     }
     ...
 }

3. B 平移回 A 后,又会发生什么?

A 也不再有任何反应。

Button 的父亲 View 只在接受到 ACTION_DOWN 的时候能够调用 setPressed() 展示 pressed 效果。所以即便手指回到了 A 区域也不会触发按下 UI 的变化。

 public class View ... {
     ...
     public boolean onTouchEvent(MotionEvent event) {
         ...
         if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
             switch (action) {
                 case MotionEvent.ACTION_UP:
                     mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                     if ((viewFlags & TOOLTIP) == TOOLTIP) {
                         handleTooltipUp();
                     }
                     if (!clickable) {
                         removeTapCallback();
                         removeLongPressCallback();
                         mInContextButtonPress = false;
                         mHasPerformedLongPress = false;
                         mIgnoreNextUpEvent = false;
                         break;
                     }
                     boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                     if ((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);
                         }
 ​
                         if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                             // This is a tap, so remove the longpress check
                             removeLongPressCallback();
 ​
                             // Only perform take click actions if we were in the pressed state
                             if (!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();
                                 }
                                 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 now
                             mUnsetPressedState.run();
                         }
 ​
                         removeTapCallback();
                     }
                     mIgnoreNextUpEvent = false;
                     break;         
                 case MotionEvent.ACTION_DOWN:
                     ...
                     if (isInScrollingContainer) {
                         mPrivateFlags |= PFLAG_PREPRESSED;
                         if (mPendingCheckForTap == null) {
                             mPendingCheckForTap = new CheckForTap();
                         }
                         mPendingCheckForTap.x = event.getX();
                         mPendingCheckForTap.y = event.getY();
                         postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                     } else {
                         // Not inside a scrolling container, so show the feedback right away
                         setPressed(true, x, y);
                         checkForLongClick(
                                 ViewConfiguration.getLongPressTimeout(),
                                 x,
                                 y,
                                 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                     }
                 ...
             }
 ​
             return true;
         }
 ​
         return false;
     }
 ​
     /*package*/ final boolean pointInView(float localX, float localY) {
         return pointInView(localX, localY, 0);
     }
 ​
     public boolean pointInView(float localX, float localY, float slop) {
         return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
                 localY < ((mBottom - mTop) + slop);
     }
     ...
 }

4. A 会触发点击吗?为什么?

无法触发点击。

原因很简单,从 A 移走的那刻将执行 performClickRunnable 删除了,继而没有机会触发 click 或 longClick。

 public class View ... {
     ...
     public boolean onTouchEvent(MotionEvent event) {
         ...
         if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
             switch (action) {
                 ...
                 case MotionEvent.ACTION_MOVE:
                     ...
                     if (!pointInView(x, y, touchSlop)) {
                         // Outside button
                         // Remove any future long press/tap checks
                         removeTapCallback();
                         removeLongPressCallback();
                         ...
                     }
                     ...
                     break;
             }
 ​
             return true;
         }
 ​
         return false;
     }
     ...
 }

结语

回顾下这 4 个问题的答案和原因。

  1. Button A 和 B 相邻,手指不抬起、从 A 平移到 B,A 会发生什么?为什么?

    A 的按下效果会消失。

    即便手指移出界了,但 MOVE 事件仍然发给了 A,View 发现坐标超过 Button 范围之后重置了 pressed 状态。

  2. 此刻,B 又会发生什么?为什么?

    B 没有任何变化。

    Button A 先收到了 DOWN 事件,导致后续的事件都发给了 A,B 没有收到任何事件,故没有反应。

  3. 之后,手指再从 B 平移回 A 后,又会发生什么?为什么?

    A 也不恢复按下效果。

    View 只在接受到 DOWN 时设置 pressed 状态,即便手指回到了 A,因为没有新的 DOWN 产生,所以无法再次呈现按下效果。

  4. 最后,在 A 上抬起手指,A 会触发点击吗?为什么?

    无法触发 A 的点击。

    手指从 A 出界的那刻将执行 click runnable 一并移除了,后面 UP 的时候没有可以执行的 runnable,故不会执行任何点击、长按点击的回调。

毫无疑问,Android 进行这样的处理是没有问题的。那如果我们想要改变这个逻辑:

  1. 让移动到的目标 Button 呈现 pressed 状态,并在手指抬起的时候响应 click 呢,该怎么实现?

思路也不复杂,简单来说复写 ViewGroupdispatchTouchEvent() 作如下处理即可:

  1. 发现 touchTarget 变更了,向原 target 发送 CANCEL 取消 pressed 效果
  2. 手动 obtain 一个 DOWN event 发送给移动到的 target,进而能使得新 target 能展示 pressed 状态和设置 click runnable
  3. 之后再发送物理上的实际 MOVE 事件给新 target,后面当 UP 的时候因为 DOWN 的时候补充了 runnable,确保 up 时可以执行 click

到这里也就讲完了,这 5 个问题你都答对了吗? 希望本文能帮你加深 Touch 处理的理解。

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

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

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

相关文章

【Linux旅行记】探究操作系统是如何进行管理的!

文章目录 什么是操作系统&#xff1f;操作系统概念操作系统的目的底层硬件驱动程序操作系统理解系统调用接口 操作系统是如何进行管理的&#xff1f;什么是管理&#xff1f;操作系统是如何管理硬件信息呢&#xff1f; &#x1f340;小结&#x1f340; &#x1f389;博客主页&am…

权威认可!麒麟信安实力入选中国信通院 2023《高质量数字化转型产品及服务全景图》

日前&#xff0c;中国信息通信研究院&#xff08;简称“中国信通院”&#xff09;发布2023《高质量数字化转型产品及服务全景图&#xff08;8月份新增入图产品&#xff09;》&#xff0c;麒麟信安实力入选&#xff0c;成为云服务领域的标杆企业。 麒麟信安入选2023 《高质量数字…

激光焊如何更准更稳?维视智造激光焊视觉解决方案助力精密制造

激光焊接是一种高能密度、非接触的焊接技术&#xff0c;它利用激光束对工件进行加热和熔化&#xff0c;然后使其在熔池的情况下形成连接。与传统的焊接方法相比&#xff0c;激光焊具有高密度、熔深小、变形小、焊缝质量高、适用性广、自动化程度高等特点&#xff0c;可以实现焊…

【C语言】自定义类型:结构体【结构体内存具详细】,枚举,联合

目录 一、结构体 1.结构的声明 2.特殊的声明 3.结构的自引用 4.结构体变量的定义和初始化 5.结构体内存对齐&#xff08;重点来了&#xff09; 6.为什么会存在内存对齐 7.修改默认对齐数 8.结构体传参 二、位段 1.什么是位段 2.位段的内存分配 3.位段的跨平台问题…

各种存储性能瓶颈如何分析与优化?

【摘要】本文结合实践剖析存储系统的架构及运行原理&#xff0c;深入分析各种存储性能瓶颈场景&#xff0c;并提出相应的性能优化手段&#xff0c;希望对同行有一定的借鉴和参考价值。 【作者】陈萍春&#xff0c;现就职于保险行业&#xff0c;拥有多年的系统、存储以及数据备…

vue3+TS前端JS实现 搜索关键词变红

起初在网上搜索获得的处理方式大都是类似这种&#xff1a; 但是实际使用中发现&#xff0c;对于汉字和数字是没有问题的&#xff0c;但是如果有字母就会出现问题。 1.只有汉字和数字的时候&#xff1a;匹配正常。 2.当有字母的时候&#xff1a;异常替换。 原因&#xff1a;第二…

百度SEO优化技巧(选择、网站结构、内容优化、外链建设、数据分析)

百度关键词SEO优化介绍 SEO是搜索引擎优化的缩写&#xff0c;是指通过优化网站结构、内容和外部链接等方式&#xff0c;提高网站在搜索引擎中的排名&#xff0c;从而获取更多的访问量和流量。百度是中国最大的搜索引擎之一&#xff0c;对于企业来说&#xff0c;优化百度关键词…

千呼万唤openGauss资源池化系列培训来了

应openGauss广大用户要求&#xff0c;社区于近期推出openGauss资源池化培训系列。 关于资源池化 资源池化是openGauss 5.0.0 推出的重点特性&#xff0c;是openGauss基于内存池化和共享存储实现的数据库集群。数据在集群的计算节点内存、共享存储中实现共享。应用可以任意节点…

嵌入式学习攻略

嵌入式软件编程的基础 主要是学习编程语言、开发环境和形成自己的编程逻辑&#xff0c;为嵌入式软件开发打下良好的基础&#xff0c;编程语言建议为C和C语言。书籍中的例子都是比较经典的程序实例&#xff0c;尽量去搞懂&#xff0c;不要觉得太长或者太难了而放弃&#xff0c;…

[正确重装docker] Win10 重装 Docker 提示 Exising installation is up to date 的正确姿势

Win10 重装 Docker 报错 Exising installation is up to date 的一种情况是原来的 docker 没有卸载干净&#xff0c;或者说&#xff0c;没有正确卸载。 巧了&#xff0c;我就是直接删除了&#xff0c;因为一些原因重装了好几次&#xff0c;血泪史留给各位嘲笑。 一条正确的卸…

编程(48)----------网页打开的过程

一个网页的打开大致分为以下几步: 1.DNS查询 在进行网络访问过程中, 实际上所访问的是IP地址. 但输入的却仅仅是域名. 因为IP地址过于复杂不利于记忆. 因此, 需要将IP转换成更具有辨识度的域名. 通过输入域名, 以DNS进行转换为IP, 再发起请求. DNS在得到域名后会进行查询, …

P7075 [CSP-S2020] 儒略日(内附封面)

[CSP-S2020] 儒略日 题目描述 为了简便计算&#xff0c;天文学家们使用儒略日&#xff08;Julian day&#xff09;来表达时间。所谓儒略日&#xff0c;其定义为从公元前 4713 年 1 月 1 日正午 12 点到此后某一时刻间所经过的天数&#xff0c;不满一天者用小数表达。若利用这…

得帆信息副总裁——陈明:低代码企业内部推广场景建议

在之前的文章中有介绍过#企业内部如何做低代码的运营推广&#xff08;☜点击回顾精彩&#xff09;&#xff0c;感兴趣的可以结合之前的文章进行了解。 结合本人这几年的低代码落地推广经验&#xff0c;不论是由得帆主导的低代码平台运营推广&#xff0c;还是由客户自身主导进行…

广通优云完成2亿元C轮融资,加速平台级运维产品的技术创新

广通优云于近期完成2亿元C轮融资&#xff0c;松禾资本领投&#xff0c;朗玛峰创投、奇安投资、舜宇投资、天雅资本等机构跟投&#xff0c;皓石资本担任本轮独家财务顾问。资金将主要应用于公司产品创新研发、团队人才建设及市场生态开拓等方面。 在当前资本市场遇冷的背景下&a…

RSS阅读器

focus&#xff1a;免费、无广告、已停止维护[2020]、开源 也许后面我会维护更新。 Archive: https://www.ihewro.com/archives/948/ 开源代码&#xff1a;https://github.com/ihewro/Focus &#x1f370;借助RSShub开源项目&#xff0c;自带丰富订阅市场&#xff0c;尽可能简…

【大数据之Kafka】十四、Kafka-Eagle监控

Kafka-Eagle 框架可以监控Kafka 集群的整体运行情况&#xff0c;在生产环境中经常使用。 1 MySQL环境准备 https://blog.csdn.net/qq_18625571/article/details/130613704?spm1001.2014.3001.5501 2 Kafka环境准备 &#xff08;1&#xff09;关闭Kafka集群。 kf.sh stop&…

反向动力学Ik学习

参考文章&#xff1a;&#xff08;非本人原创&#xff09; 英文原文&#xff1a;Inverse Kinematics Techniques in Computer Graphics: A Survey (andreasaristidou.com) 知乎翻译文章&#xff1a; 【游戏开发】逆向运动学&#xff08;IK&#xff09;详解 - 知乎 (zhihu.co…

django创建web服务器

安装 pip install django 创建项目 django-admin startproject report django-admin startapp data //project下可创建多个app 执行使用 python manage.py migrate //orm代码到数据库 python manage.py runserver 0.0.0.0:80 权限管理 python manage.py createsuperuser 创建…

住宅区电力系统管理,这一点很重要!

随着现代社会对电力供应的不断增长需求&#xff0c;住宅小区的电力分配系统变得越来越复杂。电力是我们日常生活中不可或缺的一部分&#xff0c;因此确保小区的电力供应安全和稳定至关重要。 配电柜监控是确保电力系统安全和稳定运行的重要工具。它可以提高电力供应的可靠性&am…

掌动智能浅谈UI自动化测试工具的重要性

在现代软件开发中&#xff0c;用户界面(UI)的质量和可靠性对于一个应用的成功至关重要。为了确保应用在各种环境和设备上都能正常运行&#xff0c;开发团队需要进行全面的UI测试。为了提高测试效率和减少人为错误&#xff0c;UI自动化测试工具成为不可或缺的工具。本文将探讨UI…