Android11 事件分发流程

news2024/11/21 1:37:02

在Android 11 输入系统之InputDispatcher和应用窗口建立联系一文中介绍到,当InputDispatcher写入数据后,客户端这边就会调用handleEvent方法接收数据

//frameworks\base\core\jni\android_view_InputEventReceiver.cpp
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
   //省略

    if (events & ALOOPER_EVENT_INPUT) {//之前构造数据的时候,events为ALOOPER_EVENT_INPUT
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }
    
  //省略

继续调用consumeEvents处理

//frameworks\base\core\jni\android_view_InputEventReceiver.cpp
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
	//省略
	for (;;) {
        uint32_t seq;
        InputEvent* inputEvent;

        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);//1
		//省略
			case AINPUT_EVENT_TYPE_MOTION: {
        
                MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);//使用inputEvent构造MotionEvent对象
                if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
                    *outConsumedBatch = true;
                }
                inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);//创建java层的MotionEvent对象,并将该对象的mNativePtr指向c++的MotionEvent对象
                break;
            }

	//省略
                env->CallVoidMethod(receiverObj.get(),
                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);//2
       
	//省略
}

注释1处接收InputDispatcher发过来的数据,并将数据封装成InputEvent对象,注释2处通过JNI调用InputEventReceiver的dispatchInputEvent方法
先来看一下如何接收数据的

//frameworks\native\libs\input\InputTransport.cpp
status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
                                nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
	//省略
	while (!*outEvent) {
        if (mMsgDeferred) {
            // mMsg contains a valid input message from the previous call to consume
            // that has not yet been processed.
            mMsgDeferred = false;
        } else {
            // Receive a fresh message.
            status_t result = mChannel->receiveMessage(&mMsg);//1

		//省略
	switch (mMsg.header.type) {
		case InputMessage::Type::MOTION: {
			//省略
			 	MotionEvent* motionEvent = factory->createMotionEvent();
                if (!motionEvent) return NO_MEMORY;

                updateTouchState(mMsg);
                initializeMotionEvent(motionEvent, &mMsg);//2
                *outSeq = mMsg.body.motion.seq;
                *outEvent = motionEvent;
				//省略
                break;
		}

}

注释1处接收数据,接收到的数据是InputMessage对象。注释2处根据读取到的InputMessage,创建motionEvent对象

//frameworks\native\libs\input\InputTransport.cpp
status_t InputChannel::receiveMessage(InputMessage* msg) {
    ssize_t nRead;
    do {
        nRead = ::recv(mFd.get(), msg, sizeof(InputMessage), MSG_DONTWAIT);//读fd
    } while (nRead == -1 && errno == EINTR);

consume方法得到数据并将数据封装成motionEvent对象后,回到consumeEvents方法,继续调用InputEventReceiver的dispatchInputEvent方法

//frameworks\base\core\java\android\view\InputEventReceiver.java
private void dispatchInputEvent(int seq, InputEvent event) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        onInputEvent(event);
}

调用onInputEvent方法,WindowInputEventReceiver继承InputEventReceiver,调用WindowInputEventReceiver的onInputEvent方法

//frameworks\base\core\java\android\view\ViewRootImpl.java
final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }

        @Override
        public void onInputEvent(InputEvent event) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
            //省略
            if (processedEvents != null) {
                //省略
            } else {
                enqueueInputEvent(event, this, 0, true);//注意第二个参数传入的是当前对象
            }
        }

enqueueInputEvent

//frameworks\base\core\java\android\view\ViewRootImpl.java
void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
       	//省略
        if (processImmediately) {//processImmediately传进来的是true
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }
    }

doProcessInputEvents

//frameworks\base\core\java\android\view\ViewRootImpl.java
void doProcessInputEvents() {
        // Deliver all pending input events in the queue.
        while (mPendingInputEventHead != null) {
           
			//省略
            deliverInputEvent(q);
        }

       //省略
    }

deliverInputEvent

//frameworks\base\core\java\android\view\ViewRootImpl.java
private void deliverInputEvent(QueuedInputEvent q) {
        //省略
        try {
            //省略
            InputStage stage;
            if (q.shouldSendToSynthesizer()) {
                stage = mSyntheticInputStage;
            } else {
                stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;//1
            }

           //省略
            if (stage != null) {
                handleWindowFocusChanged();
                stage.deliver(q);//2
            } else {
                finishInputEvent(q);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

注释1处设置InputStage,对于触摸事件,默认是忽略输入法的,所以stage 为mFirstPostImeInputStage 对象。注释2处 调用mFirstPostImeInputStage 的deliver方法。
系统中有多个InputStage组成的一个链表,在setView方法中设置的

//frameworks\base\core\java\android\view\ViewRootImpl.java
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                        "aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                        "aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                        "aq:native-pre-ime:" + counterSuffix);

deliver方法的原理就是输入事件会经过这些InputStage依次处理(调用onProcess方法),如果事件已经被上一个消费处理了,后面的stage就不会处理了。触摸事件会传递到ViewPostImeInputStage中处理

//frameworks\base\core\java\android\view\ViewRootImpl.java
final class ViewPostImeInputStage extends InputStage {
        public ViewPostImeInputStage(InputStage next) {
            super(next);
        }

        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }

对于触摸事件,调用processPointerEvent继续处理

//frameworks\base\core\java\android\view\ViewRootImpl.java
private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested = false;
            mAttachInfo.mHandlingPointerEvent = true;
            boolean handled = mView.dispatchPointerEvent(event);//1
            //省略
            return handled ? FINISH_HANDLED : FORWARD;
}

主要是调用mView的dispatchPointerEvent方法,这里的mView是DecorView,DecorView中没有实现该方法,在其父类View中实现

//frameworks\base\core\java\android\view\View.java
@UnsupportedAppUsage
    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

又回到DecorView的dispatchTouchEvent方法

//frameworks\base\core\java\com\android\internal\policy\DecorView.java
 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

这里的callback就是Activity对象,调用Activity的dispatchTouchEvent方法。

//frameworks/base/core/java/android/app/Activity.java
  public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {//1
            return true;
        }
        return onTouchEvent(ev);//2
    }

注释1处调用getWindow的superDispatchTouchEvent方法,getWindow返回的是一个PhoneWindow对象。注意返回值,如果返回ture的话,表明消费事件,注释2处Activity的onTouchEvent方法就不会执行。反之返回false的话使用onTouchEvent进行兜底,onTouchEvent如果是返回true,后面的InputStage就不会处理了,返回false则表明继续交给后面的InputStage处理

//frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

又继续调用到DecorView的superDispatchTouchEvent方法

//frameworks/base/core/java/com/android/internal/policy/DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

调用其父类ViewGroup的dispatchTouchEvent方法。在分析这个方法之前,先总结下事件是如何分发到ViewGroup的
在这里插入图片描述
事件是由DecorView分发给Activity,然后分发给window,最后又回到DecorView,再由DecorView分发给ViewGroup的。
ViewGroup接收到事件后,接下来就是将事件分发给具体的view了
ViewGroup事件分发

//frameworks/base/core/java/android/view/ViewGroup.java
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
      //省略
        boolean handled = false;//表明是否消费事件
        if (onFilterTouchEventForSecurity(ev)) {//是否符合安全策略
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Check for interception.
            final boolean intercepted;//是否拦截事件
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//调用requestDisallowInterceptTouchEvent这个方法设置不允许拦截,
                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;//如果第一次的事件不是down的话,直接拦截
            }
		//省略
       
            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;//比较重要的参数
            if (!canceled && !intercepted) {//不拦截也不是取消事件的话进入

            //省略

                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    //省略
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {//遍历子iew
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
							//省略
							
							//如果子view不能接收事件或者触摸点不在该view上的话,忽略这个view
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);//取出view的TouchTarget
                           
							//忽略
                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//开始分发并处理了
                                //省略
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);//进入这里表示子view消费了事件,就会设置view的TouchTarget链表,mFirstTouchTarget就不为空
                                alreadyDispatchedToNewTouchTarget = true;//设为true
                                break;
                            }
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
					//省略
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);//注意第三个参数为null
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {//表明子view消费了事件
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;//判断是否是取消事件
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                       //省略
        return handled;
    }

首先就是看看是不是需要拦截事件,判断是否通过requestDisallowInterceptTouchEvent这个方法,设置了ViewGroup不允许拦截,如果没有设置,再判断onInterceptTouchEvent的返回值,返回flase不表示不拦截。如果没有拦截则会遍历子view,依次使用dispatchTransformedTouchEvent处理,而如果拦截了话,也是通过dispatchTransformedTouchEvent处理,只不过传入的参数中,第3个参数为null
来看一下dispatchTransformedTouchEvent这个方法

//frameworks/base/core/java/android/view/ViewGroup.java
 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();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);//代表取消事件的话,将action设置为ACTION_CANCEL
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);//又设置回来
            return handled;
        }

     //省略

        // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);//如果第三个参数传入的是空,则调用自己父类的dispatchTouchEvent方法处理
        } 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);//继续分发给子view处理
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

dispatchTransformedTouchEvent的含义是如果child是ViewGroup的话,就继续调用ViewGroup的dispatchTouchEvent方法继续向下分发,如果child是view的话,则调用view的dispatchTouchEvent来处理事件。如果ViewGroup拦截了事件或者ViewGroup的孩子没有消费事件的话,也会调用View的dispatchTouchEvent来处理事件。来看一下view的dispatchTouchEvent方法

//frameworks/base/core/java/android/view/View.java
public boolean dispatchTouchEvent(MotionEvent event) {
        //省略

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {//设置过OnTouchListener优先调用
                result = true;
            }

            if (!result && onTouchEvent(event)) {//调用onTouchEvent方法
                result = true;
            }
        }
	//省略

        return result;
    }

对于事件的处理主要是判断view是不是设置过OnTouchListener,如果设置过,则调用其onTouch方法。如果OnTouch返回true的话,表示事件在这里被消费,后面的onTouchEvent就不会被调用。如果没有设置过OnTouchListener或者设置过,但是OnTouch返回false,则onTouchEvent会被调用。

上面的几个方法内容比较多,理解起来也比较费劲,用一张图总结下ViewGroup的事件分发流程

在这里插入图片描述
总结

  • 可以通过重写ViewGroup的onInterceptTouchEvent方法来实现对事件的拦截
  • 可以通过调用requestDisallowInterceptTouchEvent来禁止ViewGroup对事件拦截,这个优先级更高
  • 当事件都没有被View或者ViewGroup消费的话,使用Activity的onTouchEvent进行兜底
  • UP和MOVE 事件并不会重新寻找子view,而是直接分发给接收DOWN事件的view

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

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

相关文章

springboot项目war包部署到腾讯云服务器

一、购买服务器 试用 1 个月&#xff08;需要实名和人脸验证&#xff09; 云产品免费体验馆_云产品免费试用_个人云产品试用-腾讯云 重置密码 登录以后 二、云服务器安装MySql 登录后&#xff0c;接下来的一切我们使用linux命令来操作。 1、卸载centos默认安装的mariadb rp…

axios和ts的简单使用

按照官网的使用案例简单记下笔记 1&#xff1a;安装 npm install axios 2&#xff1a;案例 一个简单的config配置信息 // 发起一个post请求 axios({method: post,url: /user/12345,data: {firstName: Fred,lastName: Flintstone} }); case // 在 node.js 用GET请求获取…

有哪些藏文翻译器在线翻译?工具分享

有哪些藏文翻译器在线翻译&#xff1f;随着全球化的推进&#xff0c;语言之间的交流变得越来越重要。藏语作为中华民族的重要语言之一&#xff0c;其翻译需求也日益增加。为了满足这一需求&#xff0c;市场上涌现出了多款藏文翻译器在线翻译工具&#xff0c;它们以其高效、准确…

Qt for android : libusb在android中使用

简介 如何在Qt for Android中使用libusb&#xff0c; 其实libusb的文档里面都写的很清楚&#xff0c; 这里只是稍微做下整理。 libusb libusb github源码 libusb release的版本, 有编译好的静态 步骤 1. 下载libusb libusb v1.0.027 源码包 2. 整理提取libusb android使用源…

怎么使用Stable diffusion中的models

Stable diffusion中的models Stable diffusion model也可以叫做checkpoint model&#xff0c;是预先训练好的Stable diffusion权重&#xff0c;用于生成特定风格的图像。模型生成的图像类型取决于训练图像。 如果训练数据中从未出现过猫的图像&#xff0c;模型就无法生成猫的…

【MySQL数据库】 MySQL主从复制

MySQL主从复制 MySQL主从复制主从复制与读写分离的意义主从数据库实现同步&#xff08;主从复制&#xff09;三台mysql服务器搭建主从复制&#xff0c;要求不可以用root帐号同步&#xff0c;要求第三台服务器在测试过1、2的主从复制之后进行主从复制配置 MySQL主从复制 主从复…

FastAPI - 组织模块2

FastAPI没有强制指定某种格式来组织项目结构&#xff0c;开发者可以根据自己喜好和项目需要来定制自己的项目结构。 https://fastapi.tiangolo.com/zh/tutorial/bigger-applications/ 在项目根目录创建python包routers&#xff0c;然后创建member.py文件 member.py文件内容 …

嘴尚绝卤味:健康美味新选择,开启味蕾新旅程!

在这个美食文化繁荣的时代&#xff0c;卤味作为传统小吃界的一颗璀璨明珠&#xff0c;一直深受大众的喜爱。而今天&#xff0c;我要向大家介绍一款不仅美味可口&#xff0c;更注重健康营养的卤味品牌——嘴尚绝卤味。它以其独特的制作工艺和丰富的口感&#xff0c;成为众多卤味…

滚珠花键在工业自动化领域中有什么优势?

滚珠花键是工业自动化设备中重要的传动系统之一&#xff0c;不仅在工业自动化系统中有着广泛的运用&#xff0c;还在机械制造领域、航空航天领域、工业汽车领域、工业机器人、高速铁路、新能源领域 等都得到广泛应用。由于具有高精度、高承载、耐磨损、传递扭矩大等特点&#x…

EE trade:如何理解做空黄金

理解做空黄金&#xff0c;其实就是理解卖空操作在黄金市场中的应用。卖空&#xff0c;或称为做空&#xff0c;是指投资者预测某资产(在这个例子中是黄金)的价格会下跌&#xff0c;因此采取的一种投资策略。 下面简要说明做空黄金的过程和相关概念&#xff1a; 借入黄金: 首先…

饲料粉碎混合机组:打造精细化养殖

饲料粉碎混合机组是畜牧业和养殖业中不可或缺的设备。它集饲料粉碎和混合于一体&#xff0c;可以高效地处理各种饲料原料&#xff0c;提高饲料的均匀度和营养价值。 具体来说&#xff0c;饲料粉碎混合机组的主要功能包括将饲料原料进行粉碎&#xff0c;增加其表面积和调质粒度…

计算机毕业设计python+spark天气预测 天气可视化 天气大数据 空气质量检测 空气质量分析 气象大数据 气象分析 大数据毕业设计 大数据毕设

摘 要 近些年大数据人工智能等技术发展迅速&#xff0c;我国工业正努力从“制造”迈向“智造”实现新跨越。神经网络(NeuronNetwork)是一种计算模型&#xff0c;通过大量数据的学习&#xff0c;来发现数据之间的模式和规律&#xff0c;模仿人脑神经元的工作方式。随着算力的提…

SEC突发:以太坊ETF大概率获批

美国证监会大概率批准以太坊现货ETF。 5月20日&#xff0c;据外媒CoinDesk报道&#xff0c;知情人士透露&#xff0c;美国SEC周一要求证券交易所更新以太坊现货ETF的19b-4备案文件。19b-4备案文件是一种表格&#xff0c;用于向SEC通报允许基金在交易所交易的规则变更。 三位消息…

SOLIDWORKS教育版代理商应该如何选择?

SOLIDWORKS作为目前流行的三维设计软件在工程设计&#xff0c;制造和建筑中有着广泛的应用前景。教育版SOLIDWORKS软件是学生及教育机构学习教学的理想平台。 下面介绍几个挑选SOLIDWORKS教育版代理的关键要素: 1、专业知识与经验&#xff1a;代理商应掌握SOLIDWORKS等软件的丰…

【前端笔记】记录一个能优化Echarts Geo JSON大小的网站

前端在使用Echarts等可视化图表库会不可避免遇到的问题&#xff0c;渲染地图的数据太大。 而有那么一个网站能给予这个问题一个解决方案&#xff1a;链接在此 使用方法很简单&#xff0c;首先先进入网站&#xff0c;如果进入了会是这个页面&#xff1a; 接着&#xff0c;选择一…

香橙派 AIPro开发板上手测评

前言 最近拿到了一个新玩具&#xff1a;香橙派 AIPro。一个只比银行卡大一点点的开发板能带给我们多少惊喜呢&#xff1f;接下来就跟我一起来体验下这块开发板的魅力。 一、硬件配置 CPU&#xff1a;配备了4核64位ARM处理器&#xff0c;其中默认预留1个给AI处理器使用 NPU&am…

Discourse 编辑没有办法显示更多的 JS 错误

Priority/Severity: High Platform: 3.3.0.beta3-dev UI bugs Description: 昨天升级的时到最新版本的时候就发现有这个错误&#xff0c;是 JS 的错误。 发了一个帖子到官方的网站上&#xff0c;官方说可能是插件的问题。 但是我们实在是没有安装什么插件呀&#xff1f; 官方…

大数据——Spark

1.Spark MLlib概述 MLlib是Spark的机器学习&#xff08;Machine Learning&#xff09;库&#xff0c;旨在简化机器学习的工程实践工作&#xff0c;并方便扩展到更大规模。 MLlib由一些通用的学习算法和工具组成&#xff0c;包括分类、回归、聚类、协同过滤、降维等&#xff0…

智能奶柜:重塑牛奶零售新篇章

智能奶柜&#xff1a;重塑牛奶零售新篇章 回忆往昔&#xff0c;孩童时代对送奶员每日拜访的期待&#xff0c;那熟悉的一幕——新鲜牛奶被细心放置于家门口的奶箱中&#xff0c;成为了许多人温馨的童年记忆。如今&#xff0c;尽管直接投递袋装牛奶的情景已不多见&#xff0c;但…

Android NDK系列(一)手动搭建Native Project

使用NDK编写的本地代码具有高性能等特性&#xff0c;在游戏、图形处理等领域有广泛应用&#xff0c;下面介绍如何手动搭建一个纯C版的Android项目&#xff0c;通过该项目可以理解Android的项目结构。 一、创建settings.gradle Android项目是基于Gradle构建的&#xff0c;首先得…