Android事件分发机制源码解析

news2024/11/15 23:04:28

在这里插入图片描述

触摸事件传递机制是Android中一块比较重要的知识体系,了解并熟悉整套的传递机制有助于更好的分析各种滑动冲突、滑动失效问题,更好去扩展控件的事件功能和开发自定义控件。

预备知识

MotionEvent

在Android设备中,触摸事件主要包括点按、长按、拖拽、滑动等,点按又包括单击和双击,另外还包括单指操作和多指操作等。一个最简单的用户触摸事件一般经过以下几个流程:

  • 手指按下
  • 手指滑动
  • 手指抬起

Android把这些事件的每一步抽象为MotionEvent这一概念,MotionEvent包含了触摸的坐标位置,点按的数量(手指的数量),时间点等信息,用于描述用户当前的具体动作,常见的MotionEvent有下面几种类型:

  • ACTION_DOWN
  • ACTION_UP
  • ACTION_MOVE
  • ACTION_CANCEL

其中,ACTION_DOWNACTION_MOVEACTION_UP就分别对应于上面的手指按下、手指滑动、手指抬起操作,即一个最简单的用户操作包含了一个ACTION_DOWN事件,若干个ACTION_MOVE事件和一个ACTION_UP事件。

几个方法

事件分发过程中,涉及的主要方法有以下几个:

  • dispatchTouchEvent: 用于事件的分发,所有的事件都要通过此方法进行分发,决定是自己对事件进行消费还是交由子View处理
  • onTouchEvent: 主要用于事件的处理,返回true表示消费当前事件
  • onInterceptTouchEvent: 是ViewGroup中独有的方法,若返回true表示拦截当前事件,交由自己的onTouchEvent()进行处理,返回false表示不拦截

我们的源码分析也主要围绕这几个方法展开。

源码分析

Activity

我们从Activity的dispatchTouchEvent方法作为入口进行分析:

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

这个方法首先会判断当前触摸事件的类型,如果是ACTION_DOWN事件,会触发onUserInteraction方法。根据文档注释,当有任意一个按键、触屏或者轨迹球事件发生时,栈顶Activity的onUserInteraction会被触发。如果我们需要知道用户是不是正在和设备交互,可以在子类中重写这个方法,去获取通知(比如取消屏保这个场景)。

然后是调用Activity内部mWindowsuperDispatchTouchEvent方法,mWindow其实是PhoneWindow的实例,我们看看这个方法做了什么:

public class PhoneWindow extends Window implements MenuBuilder.Callback {

    ...

    @Override

    public boolean superDispatchTouchEvent(MotionEvent event) {

        return mDecor.superDispatchTouchEvent(event);

    }

    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

        ...

        public boolean superDispatchTouchEvent(MotionEvent event) {

            return super.dispatchTouchEvent(event);

        }

        ...

    }

}

原来PhoneWindow内部调用了DecorView的同名方法,而DecorView其实是FrameLayout的子类,FrameLayout并没有重写dispatchTouchEvent方法,所以事件开始交由ViewGroup的dispatchTouchEvent开始分发了,这个方法将在下一节分析。

我们回到Activity的dispatchTouchEvent方法,注意当getWindow().superDispatchTouchEvent(ev)这一语句返回false时,即事件没有被任何子View消费时,最终会执行Activity的onTouchEvent

public boolean onTouchEvent(MotionEvent event) {

    if (mWindow.shouldCloseOnTouch(this, event)) {

        finish();

        return true;

    }

    return false;

}

小结: 事件从Activity的dispatchTouchEvent开始,经由DecorView开始向下传递,交由子View处理,若事件未被任何Activity的子View处理,将由Activity自己处理。

ViewGroup

由上节分析可知,事件来到DecorView后,经过层层调用,来到了ViewGroup的dispatchTouchEvent方法中:

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

    ... 

    boolean handled = false;

    if (onFilterTouchEventForSecurity(ev)) {

        final int action = ev.getAction();

        ...

        // 先检验事件是否需要被ViewGroup拦截

        final boolean intercepted;

        if (actionMasked == MotionEvent.ACTION_DOWN

                || mFirstTouchTarget != null) {

            // 校验是否给mGroupFlags设置了FLAG_DISALLOW_INTERCEPT标志位

            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

            if (!disallowIntercept) {

                // 走onInterceptTouchEvent判断是否拦截事件

                intercepted = onInterceptTouchEvent(ev);

            } else {

                intercepted = false;

            }

        } else {

            intercepted = true;

        }

        ...

        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;

        if (!canceled && !intercepted) {

            // 注意ACTION_DOWN等事件才会走遍历所有子View的流程

            if (actionMasked == MotionEvent.ACTION_DOWN

                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)

                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

                ...

                // 开始遍历所有子View开始逐个分发事件

                final int childrenCount = mChildrenCount;

                if (childrenCount != 0) {

                    for (int i = childrenCount - 1; i >= 0; i--) {

                        // 判断触摸点是否在这个View的内部

                        final View child = children[i];

                        if (!canViewReceivePointerEvents(child)

                                || !isTransformedTouchPointInView(x, y, child, null)) {

                            continue;

                        }

                        ...

                        // 事件被子View消费,退出循环,不再继续分发给其他子View

                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

                            ...

                            // addTouchTarget内部将mFirstTouchTarget设置为child,即不为null

                            newTouchTarget = addTouchTarget(child, idBitsToAssign);

                            alreadyDispatchedToNewTouchTarget = true;

                            break;

                        }

                    }

                }

            }

        }

        // 事件未被任何子View消费,自己处理

        if (mFirstTouchTarget == null) {

            // No touch targets so treat this as an ordinary view.

            handled = dispatchTransformedTouchEvent(ev, canceled, null,

                    TouchTarget.ALL_POINTER_IDS);

        } else {

            // 将MotionEvent.ACTION_DOWN后续事件分发给mFirstTouchTarget指向的View

            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;

            }

        }

        // 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);

        }

    }

    return handled;

}

private void resetTouchState() {

    clearTouchTargets();

    resetCancelNextUpFlag(this);

    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;

}

private void clearTouchTargets() {

    TouchTarget target = mFirstTouchTarget;

    if (target != null) {

        do {

            TouchTarget next = target.next;

            target.recycle();

            target = next;

        } while (target != null);

        mFirstTouchTarget = null;

    }

}

private TouchTarget addTouchTarget(View child, int pointerIdBits) {

    TouchTarget target = TouchTarget.obtain(child, pointerIdBits);

    target.next = mFirstTouchTarget;

    mFirstTouchTarget = target;

    return target;

}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,

            View child, int desiredPointerIdBits) {

        final boolean handled;

        ...

        // 注意传参child为null时,调用的是自己的dispatchTouchEvent

        if (child == null) {

            handled = super.dispatchTouchEvent(event);

        } else {

            handled = child.dispatchTouchEvent(transformedEvent);

        }

        return handled;

}

public boolean onInterceptTouchEvent(MotionEvent ev) {

    // 默认不拦截事件

    return false;

}

这个方法比较长,只要把握住主要脉络,修枝剪叶后还是非常清晰的:

(1) 判断事件是够需要被ViewGroup拦截

首先会根据mGroupFlags判断是否可以执行onInterceptTouchEvent方法,它的值可以通过requestDisallowInterceptTouchEvent方法设置:

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {

        // We're already in this state, assume our ancestors are too

        return;

    }

    if (disallowIntercept) {

        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;

    } else {

        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;

    }

    // Pass it up to our parent

    if (mParent != null) {

        // 层层向上传递,告知所有父View不拦截事件

        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);

    }

}

所以我们在处理某些滑动冲突场景时,可以从子View中调用父View的requestDisallowInterceptTouchEvent方法,阻止父View拦截事件。

如果view没有设置FLAG_DISALLOW_INTERCEPT,就可以进入onInterceptTouchEvent方法,判断是否应该被自己拦截, ViewGroup的onInterceptTouchEvent直接返回了false,即默认是不拦截事件的,ViewGroup的子类可以重写这个方法,内部判断拦截逻辑。

**注意:**只有当事件类型是ACTION_DOWN或者mFirstTouchTarget不为空时,才会走是否需要拦截事件这一判断,如果事件是ACTION_DOWN的后续事件(如ACTION_MOVEACTION_UP等),且在传递ACTION_DOWN事件过程中没有找到目标子View时,事件将会直接被拦截,交给ViewGroup自己处理。mFirstTouchTarget的赋值会在下一节提到。

(2) 遍历所有子View,逐个分发事件:

执行遍历分发的条件是:当前事件是ACTION_DOWNACTION_POINTER_DOWN或者ACTION_HOVER_MOVE三种类型中的一个(后两种用的比较少,暂且忽略)。所以,如果事件是ACTION_DOWN的后续事件,如ACTION_UP事件,将不会进入遍历流程!

进入遍历流程后,拿到一个子View,首先会判断触摸点是不是在子View范围内,如果不是直接跳过该子View; 否则通过dispatchTransformedTouchEvent方法,间接调用child.dispatchTouchEvent达到传递的目的;

如果dispatchTransformedTouchEvent返回true,即事件被子View消费,就会把mFirstTouchTarget设置为child,即不为null,并将alreadyDispatchedToNewTouchTarget设置为true,然后跳出循环,事件不再继续传递给其他子View。

可以理解为,这一步的主要作用是,在事件的开始,即传递ACTION_DOWN事件过程中,找到一个需要消费事件的子View,我们可以称之为目标子View,执行第一次事件传递,并把mFirstTouchTarget设置为这个目标子View

(3) 将事件交给ViewGroup自己或者目标子View处理

经过上面一步后,如果mFirstTouchTarget仍然为空,说明没有任何一个子View消费事件,将同样会调用dispatchTransformedTouchEvent,但此时这个方法的View child参数为null,所以调用的其实是super.dispatchTouchEvent(event),即事件交给ViewGroup自己处理。ViewGroup是View的子View,所以事件将会使用View的dispatchTouchEvent(event)方法判断是否消费事件。

反之,如果mFirstTouchTarget不为null,说明上一次事件传递时,找到了需要处理事件的目标子View,此时,ACTION_DOWN的后续事件,如ACTION_UP等事件,都会传递至mFirstTouchTarget中保存的目标子View中。这里面还有一个小细节,如果在上一节遍历过程中已经把本次事件传递给子View,alreadyDispatchedToNewTouchTarget的值会被设置为true,代码会判断alreadyDispatchedToNewTouchTarget的值,避免做重复分发。

小结: dispatchTouchEvent方法首先判断事件是否需要被拦截,如果需要拦截会调用onInterceptTouchEvent,若该方法返回true,事件由ViewGroup自己处理,不在继续传递。 若事件未被拦截,将先遍历找出一个目标子View,后续事件也将交由目标子View处理。 若没有目标子View,事件由ViewGroup自己处理。 此外,如果一个子View没有消费ACTION_DOWN类型的事件,那么事件将会被另一个子View或者ViewGroup自己消费,之后的事件都只会传递给目标子View(mFirstTouchTarget)或者ViewGroup自身。简单来说,就是如果一个View没有消费ACTION_DOWN事件,后续事件也不会传递进来。

View

现在回头看上一节的第2、3步,不管是对子View分发事件,还是将事件分发给ViewGroup自身,最后都殊途同归,调用到了View的dispatchTouchEvent,这就是我们这一节分析的目标。

public boolean dispatchTouchEvent(MotionEvent event) {

        ...

        if (onFilterTouchEventForSecurity(event)) {

            // 判断事件是否先交给ouTouch方法处理

            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&

                    mOnTouchListener.onTouch(this, event)) {

                return true;

            }

            // onTouch未消费事件,传给onTouchEvent

            if (onTouchEvent(event)) {

                return true;

            }

        }

        ...

        return false;

    }

代码量不多,主要做了三件事:

  1. 若View设置了OnTouchListener,且处于enable状态时,会先调用mOnTouchListener的onTouch方法
  2. 若onTouch返回false,事件传递给onTouchEvent方法继续处理
  3. 若最后onTouchEvent也没有消费这个事件,将返回false,告知上层parent将事件给其他兄弟View

这样,我们的分析转到了View的onTouchEvent方法:

public boolean onTouchEvent(MotionEvent event) {

    final int viewFlags = mViewFlags;

    if ((viewFlags & ENABLED_MASK) == DISABLED) {

        if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {

            mPrivateFlags &= ~PRESSED;

            refreshDrawableState();

        }

        // 如果一个View处于DISABLED状态,但是CLICKABLE或者LONG_CLICKABLE的话,这个View仍然能消费事件

        return (((viewFlags & CLICKABLE) == CLICKABLE ||

                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));

    }

    ...

    if (((viewFlags & CLICKABLE) == CLICKABLE ||

            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {

        switch (event.getAction()) {

            case MotionEvent.ACTION_UP:

                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;

                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {

                    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.

                        mPrivateFlags |= PRESSED;

                        refreshDrawableState();

                   }

                    if (!mHasPerformedLongPress) {

                        // 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)) {

                                performClick();

                            }

                        }

                    }

                    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();

                }

                break;

            case MotionEvent.ACTION_DOWN:

                mHasPerformedLongPress = false;

                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.

                if (isInScrollingContainer) {

                    mPrivateFlags |= PREPRESSED;

                    if (mPendingCheckForTap == null) {

                        mPendingCheckForTap = new CheckForTap();

                    }

                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());

                } else {

                    // Not inside a scrolling container, so show the feedback right away

                    mPrivateFlags |= PRESSED;

                    refreshDrawableState();

                    checkForLongClick(0);

                }

                break;

            case MotionEvent.ACTION_CANCEL:

                mPrivateFlags &= ~PRESSED;

                refreshDrawableState();

                removeTapCallback();

                break;

            case MotionEvent.ACTION_MOVE:

                final int x = (int) event.getX();

                final int y = (int) event.getY();

                // Be lenient about moving outside of buttons

                if (!pointInView(x, y, mTouchSlop)) {

                    // Outside button

                    removeTapCallback();

                    if ((mPrivateFlags & PRESSED) != 0) {

                        // Remove any future long press/tap checks

                        removeLongPressCallback();

                        // Need to switch from pressed to not pressed

                        mPrivateFlags &= ~PRESSED;

                        refreshDrawableState();

                    }

                }

                break;

        }

        return true;

    }

    return false;

}

public final boolean isFocusable() {

    return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK);

}

public final boolean isFocusableInTouchMode() {

    return FOCUSABLE_IN_TOUCH_MODE == (mViewFlags & FOCUSABLE_IN_TOUCH_MODE);

}

onTouchEvent方法的主要流程如下:

  1. 如果一个View处于DISABLED状态,但是CLICKABLE或者LONG_CLICKABLE的话,这个View仍然能消费事件,只是不会再走下面的流程;
  2. 如果View是enable的且处于可点击状态,事件将被这个View消费: 在方法返回前,onTouchEvent会根据MotionEvent的不同类型做出不同响应,如调用refreshDrawableState()去设置View的按下效果和抬起效果等。 这里我们主要关注ACTION_UP分支,这个分支内部经过重重判断之后,会调用到performClick方法:
public boolean performClick() {

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    if (mOnClickListener != null) {

        playSoundEffect(SoundEffectConstants.CLICK);

        mOnClickListener.onClick(this);

        return true;

    }

    return false;

}

可以看到,如果设置了OnClickListener,就会回调我们的onClick方法,*最终消费事件*

总结

通过上面的源码解析,我们可以总结出事件分发的整体流程:

img

下面做一个总体概括:

事件由Activity的dispatchTouchEvent()开始,将事件传递给当前Activity的根ViewGroup:mDecorView,事件开始自上而下进行传递,直至被消费。

事件传递至ViewGroup时,调用dispatchTouchEvent()进行分发处理:

1.检查送否应该对事件进行拦截:onInterceptTouchEvent(),若为true,跳过2步骤; 2.将事件依次分发给子View,若事件被某个View消费了,将不再继续分发; 3.如果2中没有子View对事件进行消费或者子View的数量为零,事件将由ViewGroup自己处理,处理流程和View的处理流程一致;

事件传递至ViewdispatchTouchEvent()时, 首先会判断OnTouchListener是否存在,倘若存在,则执行onTouch(),若onTouch()未对事件进行消费,事件将继续交由onTouchEvent处理,根据上面分析可知,View的onClick事件是在onTouchEventACTION_UP中触发的,因此,onTouch事件优先于onClick事件。

若事件在自上而下的传递过程中一直没有被消费,而且最底层的子View也没有对其进行消费,事件会反向向上传递,此时,父ViewGroup可以对事件进行消费,若仍然没有被消费的话,最后会回到Activity的onTouchEvent

如果一个子View没有消费ACTION_DOWN类型的事件,那么事件将会被另一个子View或者ViewGroup自己消费,之后的事件都只会传递给目标子View(mFirstTouchTarget)或者ViewGroup自身。简单来说,就是如果一个View没有消费ACTION_DOWN事件,后续事件也不会传递进来。

最后

如果你看到了这里,觉得文章写得不错就给个赞呗?

更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》

1.Android车载应用开发系统学习指南(附项目实战)

2.Android Framework学习指南,助力成为系统级开发高手

3.2023最新Android中高级面试题汇总+解析,告别零offer

4.企业级Android音视频开发学习路线+项目实战(附源码)

5.Android Jetpack从入门到精通,构建高质量UI界面

6.Flutter技术解析与实战,跨平台首要之选

7.Kotlin从入门到实战,全方面提升架构基础

8.高级Android插件化与组件化(含实战教程和源码)

9.Android 性能优化实战+360°全方面性能调优

10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔

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

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

相关文章

Steam VR Plugin 2.7.3爬坑指南

因为项目有VR串流的需要&#xff0c;于是就用起了PicoSteam VR串流&#xff0c;真是一把心酸泪&#xff0c;坑还真不少。有些解决了&#xff0c;有些没有&#xff0c;等待后续更新或者有好心人指点一下啊&#xff0c;进入正题。 &#xff08;1&#xff09;导入插件之后&#xf…

Qt5开发及实例V2.0-第十六章-Qt汽车销售管理系统实例

Qt5开发及实例V2.0-第十六章-Qt汽车销售管理系统实例 Qt汽车销售管理系统实例一、 系统概述二、 系统模块三、 界面设计四、 代码实现五、 总结 本章相关例程源码下载 Qt汽车销售管理系统实例 一、 系统概述 汽车销售管理系统是一款基于QT5框架开发的管理系统&#xff0c;主要…

回归预测 | Matlab实现基于MIC-BP最大互信息系数数据特征选择算法结合BP神经网络的数据回归预测

回归预测 | Matlab实现基于MIC-BP最大互信息系数数据特征选择算法结合BP神经网络的数据回归预测 目录 回归预测 | Matlab实现基于MIC-BP最大互信息系数数据特征选择算法结合BP神经网络的数据回归预测效果一览基本介绍研究内容程序设计参考资料 效果一览 基本介绍 Matlab实现基于…

LeetCode【69. x 的平方根】

给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&#xff0c;例如 pow(x, 0.5) 或者 x ** 0.5 。 示例 1&#xff1…

智云谷再获AR HUD新项目定点,打开HUD出口海外新通道

深圳前海智云谷科技有限公司(以下简称“智云谷”)于近日收到国内某新能源车企的《定点通知书》&#xff0c;选择智云谷作为其新车型AR HUD开发与量产供应商。智云谷获得定点的车型为海外出口车型&#xff0c;该车型预计在2024年下半年量产。 中国汽车全产业链出海“圈粉” 随…

矿山定位系统-矿井人员定位系统在矿山自动化安全监控过程中的应用

一&#xff0c;矿井人员定位系统现阶段使用的必要性 1&#xff0c;煤矿开采是一项非常特殊的工作&#xff0c;现场属于非常复杂多变的环境&#xff0c;井下信号极差&#xff0c;数据传输非常不稳定&#xff0c;人员安全难以保证&#xff0c;煤矿企业一直在研究如何使用更合适的…

【MATLAB第77期】基于MATLAB代理模型算法的降维/特征排序/数据处理回归/分类问题MATLAB代码实现【更新中】

【MATLAB第77期】基于MATLAB代理模型算法的降维/特征排序/数据处理回归/分类问题MATLAB代码实现 本文介绍基于libsvm代理模型算法的特征排序方法合集&#xff0c;包括&#xff1a; 1.sing 2.adaboost 3.corr 4.svmrfe_ker 5.svmrfe_ori 1.sing 十折交叉取平均错误率值 累计贡…

微信小程序python+nodejs+php+springboot+vue 学习资料销售平台

本文的组织结构如下&#xff1a; 1、绪论。综述了本文的研究背景&#xff0c;分析了微信小程序学习资料销售平台的结构&#xff1b;更好的从用户的角度出发&#xff0c;发现当今学习资料销售管理中的不足&#xff0c;同时要指出本次系统中的特色。 3、系统的设计与实现。介绍了…

31.链表练习题(2)(王道2023数据结构2.3.7节16-25题)

【前面使用的所有链表的定义在第29节】 试题16&#xff1a;两个整数序列A&#xff0c;B存在两个单链表中&#xff0c;设计算法判断B是否是A的连续子序列。 bool Pattern(LinkList L1,LinkList L2){ //此函数实现比较L1的子串中是否有L2LNode *p, *q; //工作在L1,p记录L1子串…

3.wifi开发,网络编程

网络协议栈LwIP WiFi UDP Clinet编程 WiFi UDP Server编程 WiFi TCP Client编程 WiFi TCP Server编程 一。LWIP原理介绍&#xff0c;API介绍&#xff0c;文件结构 1.Lwip支持的协议 2.API 3.文件结构 1.api目录&#xff1a;应用程序接口文件。 2.arch目录&#xff1a;与硬件和…

[管理与领导-100]:管理者到底是什么?调度器?路由器?交换机?监控器?

目录 前言&#xff1a; 二层交换机 三层路由器 监视器&#xff08;Monitor&#xff09; 调度器 前言&#xff1a; 人在群体中&#xff0c;有点像设备在网络中&#xff0c;管理者到底承担什么的功能&#xff1f; 二层交换机 交换机是计算机网络中&#xff0c;用于连接多台…

环状分组柱状图 Python

代码&#xff1a; import matplotlib.pyplot as plt import numpy as np# 数据 np.random.seed(123) group1 100 * np.random.rand(5) group2 100 * np.random.rand(5) group3 100 * np.random.rand(5) group4 100 * np.random.rand(5)groups [group1, group2, group3, g…

SpringAOP补充-通知获取类型

JoinPoint 是 ProceedingJoinPoint 的父类。 getArgs()是JoinPoint获取原方法返回值的函数。 preceed()是ProceedingJoinPoint获取原方法返回值的函数。

Qt5开发及实例V2.0-第十四章-Qt多国语言国际化

Qt5开发及实例V2.0-第十四章-Qt多国语言国际化 第14章 Qt 5多国语言国际化14.1 基本概念14.1.1 国际化支持的实现14.1.2 翻译工作&#xff1a;“*.qm”文件的生成 14.2 【实例】14.2.1 简单测试14.2.2 选择语言翻译文字 本章相关例程源码下载1.Qt5开发及实例_CH1401.rar 下载2.…

基于Xml方式Bean的配置-命名空间种类

Spring的标签 Spring的xml标签大体上分为两类&#xff0c;一种是默认标签&#xff0c;一种是自定义标签 默认标签&#xff1a;就是不用额外导入其它命名空间约束的标签&#xff0c;例如<bean>标签 标签作用 <beans> 一般作为xml配置根标签&#xff0c;其他标签都是…

如何利用播放器节省20%点播成本

点播成本节省的点其实涉及诸多部分&#xff0c;例如&#xff1a;CDN、转码、存储等&#xff0c;而利用播放器降本却是很多客户比较陌生的部分。火山引擎基于内部支撑抖音集团相关业务的实践&#xff0c;播放器恰恰是成本优化中最重要和最为依赖的部分。 火山引擎的视频团队做了…

基于复旦微的FMQL45T900全国产化ARM开发开发套件(核心板+底板)

TES745D是我司自主研制的一款基于上海复旦微电子FMQL45T900的全国产化ARM核心板&#xff08;模块&#xff09;。该核心板将复旦微的FMQL45T900&#xff08;与XILINX的XC7Z045-2FFG900I兼容&#xff09;的最小系统集成在了一个87*117mm的核心板上&#xff0c;可以作为一个核心模…

JavaWeb开发-08-MySQL(三)

一.多表查询 -- 多表查询: 数据准备 -- 部门管理 create table tb_dept(id int unsigned primary key auto_increment comment 主键ID,name varchar(10) not null unique comment 部门名称,create_time datetime not null comment 创建时间,update_time datetime not null comm…

数据治理-重要图

语境关系图 车轮图 六边形图

LLMs之InternLM:InternLM-20B的简介、安装、使用方法之详细攻略

LLMs之InternLM&#xff1a;InternLM-20B的简介、安装、使用方法之详细攻略 导读&#xff1a;2023年09月20日&#xff0c;由上海人工智能实验室等团队发布了InternLM-20B的大模型。它在OpenCompass提出的5个能力维度上(语言、知识、理解、推理、学科)全面领先于同规模开源模型&…