Android事件分发机制被我翻烂了

news2024/11/15 20:03:44

作者:积木zz

这次说下Android中的事件分发机制 从开始点击屏幕开始,就会产生从Activity开始到decorview一直到最里层的view一连串事件传递。每一层view或者viewgroup都会首先调用它的dispatchTouchEvent方法,然后判断是否就在当前一层消费掉事件

view的事件分发

首先上一段伪代码,是在书上看到的,也是我觉得总结的最好的

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean isConsume = false;
    if (isViewGroup) {
        if (onInterceptTouchEvent(event)) {
            isConsume = onTouchEvent(event);
        } else {
            isConsume = child.dispatchTouchEvent(event);
        }

    } else {
        //isView
        isConsume = onTouchEvent(event);
    }
    return isConsume;
}

如果当前是viewgroup层级,就会判断 onInterceptTouchEvent 是否为true,如果为true,则代表事件要消费在这一层级,不再往下传递。接着便执行当前 viewgroup 的onTouchEvent方法。如果onInterceptTouchEvent为false,则代表事件继续传递到下一层级的 dispatchTouchEvent方法,接着一样的代码逻辑,一直到最里面一层的view。

ok,还没完哦,到最里面一层就会直接执行onTouchEvent方法,这时候,view有没有权利拒绝消费事件呢? 按道理view作为最底层的,应该是没有发言权才对。但是呢,秉着公平公正原则,view也是可以拒绝的,可以在onTouchEvent方法返回false,表示他不想消费这个事件。那么这个事件又会怎么处理呢?见下面一段伪代码:

public void handleTouchEvent(MotionEvent event) {
    if (!onTouchEvent(event)) {
        getParent.onTouchEvent(event);
    }
}

如果view的onTouchEvent方法返回false,那么它的父容器的onTouchEvent又会被调用,如果父容器的onTouchEvent又返回false,则又交给上一级。一直到最上层,也就是Activity的onTouchEvent被调用。

至此,消费流程完毕 但是,关于onTouch,onTouchEvent和onClick又是怎么样的调用关系呢? 那就再来一段伪代码:

public void consumeEvent(MotionEvent event) {
    if (setOnTouchListener) {
        onTouch();
        if (!onTouch()) {
            onTouchEvent(event);
        }
    } else {
        onTouchEvent(event);
    }

    if (setOnClickListener) {
        onClick();
    }
}

当某一层viewGroup的onInterceptTouchEvent被调用,则代表当前层级要消费事件。如果它的onTouchListener被设置了的话,则onTouch会被调用,如果onTouch的返回值返回true,则onTouchEvent不会被调用。如果返回false或者没有设置onTouchListener,则会继续调用onTouchEvent。而onClick方法则是设置了onClickListener则会被正常调用。

这里用一张流程图总结下:

源码分析

一个触摸事件,首先是传到Activity层级,然后传到根view,通过一层层的viewgroup最终到底最里面一层的view,我们来一层层解析

Activity(dispatchTouchEvent)

直接上代码

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

这里可以看到,onUserInteraction方法是空的,主要是调用了getWindow().superDispatchTouchEvent(ev)方法,返回true,就代表事件消费了。返回false,就代表下层没人处理,那就直接到了activity的onTouchEvent方法,这点跟之前的消费传递也是吻合的。

继续看看superDispatchTouchEvent方法,然后就走到了PhoneWindow的superDispatchTouchEvent方法,以及DecorView的superDispatchTouchEvent,看看代码:

    //PhoneWindow.java
    private DecorView mDecor;
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
    
    //DecorView.java
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

这里可以看到,依次经过了PhoneWindow到达了DecorView,DecorView是activity的根view,也是setcontentView所设置的view的父view,它是继承自FrameLayout。所以这里super.dispatchTouchEvent(event)方法,其实就是走到了viewgroup的dispatchTouchEvent 方法。

ViewGroup(dispatchTouchEvent)

   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (onFilterTouchEventForSecurity(ev)) {
            // Check for interception,表示是否拦截的字段
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
              //FLAG_DISALLOW_INTERCEPT标志是通过requestDisallowInterceptTouchEvent设置
                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;
            }
          
          
          //mFirstTouchTarget赋值
             while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    } else {
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            continue;
                        }
                    }
                }          
        }

这里截取了部分关键的代码,首先是两个条件

  • actionMasked == MotionEvent.ACTION_DOWN

  • mFirstTouchTarget != null

如果满足了其中一个条件才会继续走下去,执行onInterceptTouchEvent方法等,否则就直接intercepted = true,表示拦截。 第一个条件很明显,就是表示当前事件位按下事件(ACTION_DOWN) 第二个条件是个字段,根据下面的代码可以得知,当后面有view消费掉事件的时候,这个mFirstTouchTarget字段就会赋值,否则就为空。

所以什么意思呢,当ACTION_DOWN事件时候,一定会执行到后面代码。当其他事件来的时候,要看当前viewgroup是否消费了事件,如果当前viewgroup已经消费了事件,没传到子view,那么mFirstTouchTarget字段就为空,所以就不会执行到后面的代码,就直接消费掉所有事件了。 这就符合了之前的所说的一种机制:

某个view一旦开始拦截,那么后续事件就全部就给它处理了,也不会执行onInterceptTouchEvent方法了

但是,两个条件满足了一个,就能执行到onInterceptTouchEvent了吗?不一定,这里看到还有一个判断条件:disallowIntercept。这个字段是由requestDisallowInterceptTouchEvent方法设置的,后面我们会讲到,主要用于滑动冲突,意思就是子view告诉你不想让你拦截,那么你就不拦截了,直接返回false。

ok,继续看源码,之前的内容我们了解到,如果viewgroup不拦截事件,应该会传递给子view,那在哪里传的呢?继续看看dispatchTouchEvent的代码:

if (!canceled && !intercepted) {
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        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);

                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            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;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                }
            }

这里可以看到,进行了一个子view的遍历,其中,如果满足两个条件中的一个,就跳出。否则就执行dispatchTransformedTouchEvent方法。先看看这两个条件:

  • !child.canReceivePointerEvents()
  • !isTransformedTouchPointInView(x, y, child, null)

看名字是看不出啥了,直接看代码吧:

    protected boolean canReceivePointerEvents() {
        return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
    }
    
    protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        final float[] point = getTempPoint();
        point[0] = x;
        point[1] = y;
        transformPointToViewLocal(point, child);
        final boolean isInView = child.pointInView(point[0], point[1]);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }

哦,原来是这个意思。canReceivePointerEvents方法就代表view是不是可以接受点击事件,比如是不是在播放动画。而isTransformedTouchPointInView方法代表点击事件的坐标是不是在这个view的区域上面。 ok,如果条件都满足,就执行到dispatchTransformedTouchEvent方法了:

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);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
}

这个方法大家应该都猜到了,其实就是执行了child.dispatchTouchEvent(event)。也就是下一层view的dispatchTouchEvent方法呗,开始事件的层级传递。

View(dispatchTouchEvent)

到view 层级的时候,自然就执行的view的dispatchTouchEvent,上代码

    public boolean dispatchTouchEvent(MotionEvent event) {
        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        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)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

这里可以看到,首先会判断li.mOnTouchListener != null,如果不为空,就会执行onTouch方法。 根据onTouch方法返回的结果,如果为false,result就为false,那么onTouchEvent才会执行。这个逻辑也是符合我们之前说的传递方式。

最后我们再看看view的onTouchEvent都做了什么事:

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:
                    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 (!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();
                                }
                            }
                        }
                    }
                    mIgnoreNextUpEvent = false;
                    break;
            }

            return true;
        }

从代码可以得知,如果设置了CLICKABLE或者LONG_CLICKABLE,那么这个view就会消费事件,并且执行performClickInternal方法,然后执行到performClick方法。这个performClick方法大家应该都很熟悉,就是触发点击的方法,其实内部就是执行了onClick方法。

    private boolean performClickInternal() {
        notifyAutofillManagerOnClick();
        return performClick();
    }
    
    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        return result;
    }

至此,源代码也看的差不多了,内部其实有很多细节,这里也就不一一说明了,大家有空可以去研究下。

事件分发的应用(requestDisallowInterceptTouchEvent)

那既然学会了事件分发机制,我们实际工作中会怎么应用呢?其实最常见的就是解决滑动冲突的问题。一般有两种解决办法:

  • 一种是外部拦截:从父view端处理,根据情况决定事件是否分发到子view
  • 一种是内部拦截:从子view端处理,根据情况决定是否阻止父view进行拦截,其中的关键就是requestDisallowInterceptTouchEvent方法。

第一种方法,其实就是在onInterceptTouchEvnet方法里面进行判断返回true还是返回false。 第二种方法,就是用到了requestDisallowInterceptTouchEvent方法,这个方法的意思就是让父view不要去拦截事件了,在dispatchTouchEvent方法里面就有这个标志位:FLAG_DISALLOW_INTERCEPT,如果disallowIntercept字段为true,就不会去执行onInterceptTouchEvent方法,而是返回false,不拦截事件。

上代码:

    //外部拦截法:父view.java		
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        //父view拦截条件
        boolean parentCanIntercept;

        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (parentCanIntercept) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
        }
        return intercepted;

    }

外部拦截很简单,就是判断条件,然后决定是否进行拦截。

    //父view.java			
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            return false;
        } else {
            return true;
        }
    }

    //子view.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        //父view拦截条件
        boolean parentCanIntercept;

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (parentCanIntercept) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(event);
    }

感觉内部拦截有点复杂呀,还要重写父view的方法,这里分析下,为什么要去这么写:

  • 父view ACTION_DOWN的时候,不能拦截,因为如果拦截,那么后续事件也就跟子view无关了
  • 父view 其他事件的时候,要返回true,表示拦截。因为onInterceptTouchEvent方法的调用是被FLAG_DISALLOW_INTERCEPT标志位所控制,所以子view需要父view拦截的时候,才会走到这个onInterceptTouchEvent方法中来,那么这时候要保证方法中一定是要拦截的。

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
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/907280.html

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

相关文章

软件设计风格(干货)-架构师之路(九)

一、软件架构风格概念 Architecture架构&#xff0c;体系结构 软件体系结构风格是 描述某一特定应用领域中 系统组织方式 的惯用模式。架构风格定义一个系统家族&#xff0c;即 一个架构的定义、一个词汇表和一组约束。词汇表包含 一些构建和连接类型&#xff0c;而一组约束 …

监控 FTP 服务器

文件传输协议 &#xff08;FTP&#xff09; 用于在 TCP/IP 网络中的服务器和客户端之间传输文件&#xff0c;它是一种标准协议&#xff0c;广泛用于在各个垂直行业的组织之间从集中位置存储和分发数据。FTP协议的其他一些安全版本如下&#xff1a; SSH 文件传输协议 &#xff…

SpringBoot-1-Spring Boot实战:快速搭建你的第一个应用,以及了解原理

SpringBoot-1-Spring Boot实战&#xff1a;快速搭建你的第一个应用&#xff0c;以及了解原理 今日内容 SpringBootWeb入门 前言 我们在之前介绍Spring的时候&#xff0c;已经说过Spring官方(Spring官方)提供很多开源项目&#xff0c;点击projects&#xff0c;看到spring家族…

中国CV军团,危矣?!

大数据产业创新服务媒体 ——聚焦数据 改变商业 近期&#xff0c;云从科技发布了2023年半年报。应该说&#xff0c;云从科技交出的成绩单很不好。事实上&#xff0c;商汤、旷视等CV厂商的情况也好不到哪去。云从科技的处境有一定的代表性&#xff0c;因此&#xff0c;我们将通…

Python“牵手”拼多多商品评论数据采集方法,拼多多API申请步骤说明

拼多多平台API接口是为开发电商类应用程序而设计的一套完整的、跨浏览器、跨平台的接口规范&#xff0c;拼多多API接口是指通过编程的方式&#xff0c;让开发者能够通过HTTP协议直接访问拼多多平台的数据&#xff0c;包括商品信息、店铺信息、物流信息&#xff0c;评论数据等&a…

国内常见的几款可视化Web组态软件

组态软件是一种用于控制和监控各种设备的软件&#xff0c;也是指在自动控制系统监控层一级的软件平台和开发环境。这类软件实际上也是一种通过灵活的组态方式&#xff0c;为用户提供快速构建工业自动控制系统监控功能的、通用层次的软件工具。通常用于工业控制&#xff0c;自动…

机器学习分类,损失函数中为什么要用Log,机器学习的应用

目录 损失函数中为什么要用Log 为什么对数可以将乘法转化为加法&#xff1f; 机器学习&#xff08;Machine Learning&#xff09; 机器学习的分类 监督学习 无监督学习 强化学习 机器学习的应用 应用举例&#xff1a;猫狗分类 1. 现实问题抽象为数学问题 2. 数据准备…

Electron 报gpu_process_host.cc(951)] GPU process launch faile错误

解决方法&#xff0c;在入口js文件中&#xff0c;添加如下代码: app.commandLine.appendSwitch(no-sandbox)

Vulhub之Shiro篇

CVE-2010-3863--认证绕过 1、启动靶机 在终端里进入事先进入准备好的vulhub靶场目录下 cd /vulhub-master/shiro/CVE-2010-3863 sudo docker-compose up -d 2、直接请求管理页面/admin&#xff0c;无法访问&#xff0c;将会被重定向到登录页面 3、构造恶意请求/./admin&…

Nvidia Jetson 编解码开发(5)Jetpack 5.x版本Multimedia API 硬件编码开发

1.前言 基于之前的开发程序 Nvidia Jetson 编解码开发(2)Jetpack 4.x版本Multimedia API 硬件编码开发--集成encode模块_free-xx的博客-CSDN博客 开发版本是基于JetPack 4.x版本 现在需要迁移到JetPack 5.x版本(新发布的Orin/Orin NX/Orin Nano都是JetPack 5.x版本) 尝…

Arch Linux 开发人员近日发布了 Archinstall 2.6

导读经过几个月的努力&#xff0c;Arch Linux 开发人员近日发布了 Archinstall 2.6&#xff0c;作为 Arch Linux 官方文本模式安装程序的最新稳定版本&#xff0c;它带来了几个新功能和许多改进。 Archinstall 2.6 支持 Hyperland 自定义动态平铺 Wayland 窗口管理器&#xff0…

Python在文件中对字母、单词频度统计

读入给定的文本文件“hamlet.txt”&#xff0c;编写两个函数分别实现: 1) 统计所有字母的出现频度,依据频度从高到低&#xff0c;显示前5个字母及其频度&#xff0c;同时把结果写入文件“hamlet_字母频度.txt”。 2) 统计所有单词的出现频度,依据频度从高到低&#xff0c;显示…

数据结构作业——哈夫曼树

/*【基本要求】 &#xff08;1&#xff09; 从文件中读出一篇英文文章&#xff0c;包含字母和空格等字符。 &#xff08;2&#xff09; 统计各个字符出现的频度。 &#xff08;3&#xff09; 根据出现的频度&#xff0c;为每个出现的字符建立一个哈夫曼编码&#xff0c;并输出。…

Open-Cascade 编译全过程-以及注意事项

1.从Github上下载 该源码库; https://github.com/Open-Cascade-SAS/OCCT 2.再从下面网站,下载需要的地方库文件: 从这个地址下载需要的文件&#xff1b; 3rd party Components | Open CASCADE Technology 3.使用cmak gui进行构建编译; 关键地方以及坑所在得点: 如果其编…

打破壁垒,实现高效的跨部门协作与沟通

在如今复杂多变的商业环境下&#xff0c;企业间需要跨越多个部门的壁垒进行协作及沟通以完成企业目标。尽管如此&#xff0c;许多企业仍然面临协作过程中出现不必要的误解、重复、延迟和错失机会等问题。为此&#xff0c;现代技术提供了一些解决方案&#xff0c;其中最为成功的…

【Terraform学习】使用 Terraform 创建Amazon VPC(Terraform-AWS最佳实战学习)

使用 Terraform 创建Amazon VPC 实验步骤 前提条件 安装 Terraform&#xff1a; 地址 下载仓库代码模版 本实验代码位于 task_vpc 文件夹中。 变量文件 variables.tf 在上面的代码中&#xff0c;您将声明&#xff0c;aws_access_key&#xff0c;aws_secret_key和 区域变量…

视频云存储/安防监控EasyCVR视频汇聚平台如何通过角色权限自行分配功能模块?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、…

永久设置pip指定国内镜像源(windows内)

1.首先列出国内四个镜像源网站&#xff1a; 一、清华源 https://pypi.tuna.tsinghua.edu.cn/simple/ 二、阿里源 https://mirrors.aliyun.com/pypi/simple 三、中科大源 https://pypi.mirrors.ustc.edu.cn/simple/ 四、豆瓣源 http://pypi.douban.com/simple/ 2.一般下载所需要…

什么是SaaS、PaaS、aPaaS、iPaaS、IaaS,一文讲透

在数字化的带动下&#xff0c;各行业对云服务的需求进入快速增长期。 SaaS、PaaS、aPaaS、iPaaS、IaaS…… 这些词经常出现&#xff0c;那么他们分别是什么意思&#xff1f;又有什么区别&#xff1f;小帆带大家一起来看看~ SaaS SaaS&#xff0c;Software as a Service&…

数字乡镇综合解决方案[59页PPT]

导读&#xff1a;原文《数字乡镇综合解决方案[59页PPT]》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 喜欢文章&#xff0c;您可以关注评论转发本文&#xff0c;了…