Android 事件分发介绍

文章目录

  • 一、目的
  • 二、环境
  • 三、相关概念
    • 3.1 事件分发
  • 四、详细设计
    • 4.1应用布局
      • 4.1.1 应用布局结构
      • 4.1.2 LayoutInspector
    • 4.2 关键View&方法
      • 4.2.1 相关View
      • 4.2.2 相关方法
      • 4.2.3 View与方法关系
    • 4.3 事件分发概念图
      • 4.3.1 事件分发类图
      • 4.3.2 事件分发模型图
    • 4.4 Activity组件
      • 4.4.1 Activity->dispatchTouchEvent()
      • 4.4.2 Activity->getWindow()
      • 4.4.3 Activity->onTouchEvent()
    • 4.5 ViewGroup组件
      • 4.5.1 ViewGroup->dispatchTouchEvent()
      • 4.5.2 ViewGroup->dispatchTransformedTouchEvent()
    • 4.6 View组件
      • 4.6.1 View->dispatchTouchEvent()
      • 4.6.2 OnTouchListener->onTouch()
      • 4.6.3 View->onTouchEvent()
    • 4.7 例子-点击事件时序图
  • 五、小结&问题点
  • 六、代码仓库地址
  • 七、参考资料

一、目的

        最开始接触Android时,仅仅是知道Android系统存在的点击事件、触摸事件,但是并不清楚这些事件的由来。
        之后,在面试Oppo和美图时,皆有问到Android的事件分发机制,但是都被问得很懵逼,归根到底都是对于其实现逻辑的不理解。
        随后,想去弥补该模块的不足,浏览很多关于Android事件分发的博文,但仍存在一些疑惑,就想着去阅读下源码,整理下笔记,希望对同学们有帮助。

二、环境

  1. 版本:Android 11
  2. 平台:展锐 SPRD8541E

三、相关概念

3.1 事件分发

        Android 中 View 的布局是一个树形结构,各个 ViewGroup 和 View 是按树形结构嵌套布局的,从而会出现用户触摸的位置坐标可能会落在多个 View 的范围内,这样就不知道哪个 View 来响应这个事件,为了解决这一问题,就出现了事件分发机制。

四、详细设计

4.1应用布局

4.1.1 应用布局结构

        如下为一个Activity打开后,其对应视图的层级结构。

4.1.2 LayoutInspector

        Layout Inspector是google提供给我们进行布局分析的一个工具,也是目前google在弃用Hierarchy View后推荐使用的一款布局分析工具。

4.2 关键View&方法

4.2.1 相关View

组件描述
ActivityAndroid事件分发的起始端,其为一个window窗口,内部持有Decorder视图,该视图为当前窗体的根节点,同时,它也是一个ViewGroup容器。
ViewGroupAndroid中ViewGroup是一个布局容器,可以嵌套多个 ViewGroup 和 View,事件传递和拦截都由 ViewGroup 完成。
View事件传递的最末端,要么消费事件,要么不消费把事件传递给父容器

4.2.2 相关方法

方法描述
dispatchTouchEvent分发事件
onInterceptTouchEvent拦截事件
onTouchEvent触摸事件

4.2.3 View与方法关系

组件dispatchTouchEventonInterceptTouchEventonTouchEvent
Activity
ViewGroup
View

4.3 事件分发概念图

4.3.1 事件分发类图

4.3.2 事件分发模型图

        Android的ACTION_DOWN事件分发如图,从1-9步骤,描述一个down事件的分发过程,如果大家能懂,就不用看下面文字描述了(写完这个篇幅,感觉文字好多,不好理解!)

  1. ACTION_DOWN事件触发。 当我们手指触摸屏幕,tp驱动会响应中断,通过ims输入系统,将down事件的相关信息发送到当前的窗口,即当前的Activity。
  2. Activity事件分发。 会引用dispatchTouchEvent()方法,对down事件分发。Activity本身会持有一个window对象,window对象的实现类PhoneWindow会持有一个DecorView对象,DecorView是一个ViewGroup对象,即我们可以理解为,Activity最终会将事件分发给下一个节点——ViewGroup。
  3. ViewGroup事件拦截。 ViewGroup接收到事件后,会先引用onInterceptTouchEvent(),查看当前的视图容器是否做事件拦截。
  4. ViewGroup消费事件。 如当前的ViewGroup对事件进行拦截,即会调用onTouchEvent(),对事件消费。
  5. ViewGroup事件不拦截。 则ViewGroup会继续遍历自身的子节点,并且当事件的坐标位于子节点上,则继续下发到下一个节点。ViewGroup的子节点有可能是View,也可能是ViewGroup(当然,ViewGroup最后也是继承于View的,突然感觉有点废话)。
  6. ViewGroup事件分发。 目标视图如果是ViewGroup,会引用其super类的dispatchTouchEvent()方法,即事件下发,不管目标视图是View或者ViewGroup最终引用的是View类的分发方法。
  7. View事件消费。 在View的dispatchTouchEvent()方法中会根据当前View是否可以点击、onTouch()是否消费、onTouchEvent()是否消费等条件,来判断当前是否为目标View。
  8. View事件未消费。 View事件未消费,则其父节点,即ViewGroup会调用onTouchEvent()方法,并根据返回值来决定是否消费事件。
  9. ViewGroup事件未消费。 ViewGroup事件未消费,择其父节点,即Actviity会调用onTouchEvent()方法

PS:
(1) ACTION_MOVEACTION_UP事件,流程与ACTION_DOWN的分发过程基本一致,MOVE和UP事件也是通过Activity开始,借助DOWN事件产生的目标View,逐级分发。
(2) ACTION_CANCEL事件,是在down与up、move事件切换过程中,事件被拦截,两次的touchTarget目标view不一致,而产生的事件。用于对之前的目标View做恢复处理,避免down与up/move事件不对称。

4.4 Activity组件

4.4.1 Activity->dispatchTouchEvent()

        底层上报的事件信息,最终会引用到该方法。Activity会持有一个根视图DecordView,事件最终会往该ViewGroup分发,如所有的View都未消费该事件,则最终由Activity的onTouchEvent()
来兜底处理。

@frameworks\base\core\java\android\app\Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    if (getWindow().superDispatchTouchEvent(ev)) {//Step 1. 查看Window对应的View是否分发该事件
        return true;
    }
    return onTouchEvent(ev);//Step 2. 如果没有组件消费事件,则由Activity兜底处理
}

4.4.2 Activity->getWindow()

        我们每次启动一个Activity的组件,会先打开一个window窗口,而PhoneWindow是Window唯一的实现类。

@frameworks\base\core\java\android\app\Activity.java
public Window getWindow() {
    return mWindow;
}

final void attach(Context context, ActivityThread aThread...) {
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);//PhoneWindow是Window窗口唯一的实现类
    ...
}

        PhoneWindow对象内部持有DecorView对象,而该View正是该窗口对应的视图容器,也是根节点。(此部分不具体分析)

@frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.     Callback {
    ...
    private DecorView mDecor;//
    ...
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);//往View的根节点分发事件
    }
}

4.4.3 Activity->onTouchEvent()

        Activity的onTouchEvent方法,是在没有任何组件消费事件的情况下,触发的方法。

@frameworks\base\core\java\android\app\Activity.java
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}

4.5 ViewGroup组件

        ViewGroup组件在整个事件分发的模型中,既有分发事件的责任,又要具备处理事件的能力,真的典型的当爹又当妈。
        当Activity调用superDispatchTouchEvent,即最终会使用到DecorView的superDispatchTouchEvent方法,而DecorView是继承于ViewGroup,即最终会引用ViewGroup的dispatchTouchEvent方法。

4.5.1 ViewGroup->dispatchTouchEvent()

此方法为事件分发最核心的代码。其主要处理如下四件事情:
Setp 1. 重置事件。 一次完整触摸的事件:DOWN -> MOVE -> UP,即我们可以理解为DOWN是所有触摸事件的起始事件。当输入事件是ACTION_DOWN时,重置触摸事件状态信息,避免产生干扰。
Step 2. 拦截事件。 拦截事件是ViewGroup特有的方法,用于拦截事件,并将该事件分发给自己消费,防止事件继续下发。
Step 3.查找目标View。 查找目标View主要针对于Down事件。当ViewGroup未拦截事件,且输入事件是ACTION_DOWN时,会遍历该ViewGroup的所有子节点,并根据触摸位置的坐标,来决定当前子节点是否是下一级目标View。当找到目标View节点后,会分发Down事件,并记录该节点信息。
Step 4.下发事件。 如果目标View未找到的话,则会将事件交由自己的onTouchEvent()处理;如果目标View已经找到,则Down事件就此结束(此处暂不考虑多指场景);Move和Up事件将继续下发(默认情况下Move、Up和Down事件是成对出现的,如果目标View已经存在,则Down事件已经下发,即意味着Move和Up事件也需要下发给对应的目标View)。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    if (actionMasked == MotionEvent.ACTION_DOWN) {//Step 1.重置事件信息,避免影响下一次事件
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }

    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);//Step 2.拦截事件
            ev.setAction(action); // restore action in case it was changed
        }
    } 
    ...
    if (!canceled && !intercepted) {//Step 3.查找目标View
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            ...
            if (newTouchTarget == null && childrenCount != 0) {
                ...
                for (int i = childrenCount - 1; i >= 0; i--) {//遍历所有的子节点
                    ...
                    if (!child.canReceivePointerEvents()
                            || !isTransformedTouchPointInView(x, y, child, null)) {// 子节点不可以接收事件,或者触摸位置不在子节点的范围上
                        continue;
                    }
                    ...
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//找到目标View
                        ...
                        break;
                    }
                }
                ...
            }
            ...
        }
    }
    //Step 4.根据找到的目标View情况,继续下发事件
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);//没有找到目标View或者事件被拦截,事件下发给自己
    } else {
        ...
        while (target != null) {//多组数据,一般是指多指场景
            final TouchTarget next = target.next;
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {//此场景一般是down事件
                handled = true
            } else {
                if (dispatchTransformedTouchEvent(ev, cancelChild,
                        target.child, target.pointerIdBits)) {//此场景一般是move、up事件
                    handled = true;
                }
                ...
            }
            predecessor = target;
            target = next;
        }
        ...
    }
    ...
    return handled;
}

4.5.2 ViewGroup->dispatchTransformedTouchEvent()

事件分发关键方法,主要用于向目标View分发事件,具体逻辑如下:
Step 1.Cancel事件分发。 之前我们提过Down和Up事件是成对存在的,如果Down事件已经下发的情况下,Up事件却因为事件拦截等原因,未能下发给目标View,目标View未收到Up事件,此时就可能产生一些按压状态的异常问题,故,在当前场景下,将会分发一个ACTION_CANCEL事件给目标View。
Step 2.事件处理。 如果事件未找到目标View,则child会为null,此时的事件将由自身处理。
Step 3.事件分发。 如果事件还存在目标View,则此时的事件会再分发。

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        ...
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {//Step 1.下发取消事件
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        ...
        if (child == null) {//Step 2.如果事件未找到目标View,则触摸事件会发给自己
            handled = super.dispatchTouchEvent(transformedEvent);
        } 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);//Step 3.找到目标View,事件下发给子节点
        }
        ...
        return handled;
    }

4.6 View组件

        View组件在事件处理模型中,主要是处理事件。我们知道ViewGroup,也是继承于View,所以ViewGroup也是同样具备View的处理事件能力。

4.6.1 View->dispatchTouchEvent()

Step 1.触发onTouch()方法。 如果当前的View是可点击的,且配置了onTouch事件监听,则触发该View的onTouch()方法。
Step 2.触发onTouchEvent()方法。 如果该事件在上一步的onTouch()函数中未被消费,则触发onTouchEvent()方法。

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    ...
    if (onFilterTouchEventForSecurity(event)) {
        ...
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {//Step 1.触发onTouch事件
            result = true;
        }

        if (!result && onTouchEvent(event)) {//Step 2.如onTouch未消费,触发onTouchEvent事件
            result = true;
        }
    }
    ...
    return result;
}

4.6.2 OnTouchListener->onTouch()

        View可以设置事件监听,用于监听onTouch事件的回调,当然,像我们常见的onClick()、onLongClick()等事件也可监听,其相关源码如下:

@frameworks\base\core\java\android\view\View.java
public void setOnTouchListener(OnTouchListener l) {//设置onTouch监听
    getListenerInfo().mOnTouchListener = l;
}

ListenerInfo getListenerInfo() {
    if (mListenerInfo != null) {
        return mListenerInfo;
    }
    mListenerInfo = new ListenerInfo();
    return mListenerInfo;
}

public interface OnTouchListener {//Touch接口,用于回调onTouch事件
    boolean onTouch(View v, MotionEvent event);
}

4.6.3 View->onTouchEvent()

        事件如未被onTouch消费掉,则会引用到onTouchEvent()方法,该方法会涉及ACTION_UP、ACTION_DOWN、ACTION_CANCEL、ACTION_MOVE事件的处理,View的onClick()、onLongClick()也是由该方法触发。此外,如果当前的View是可点击的话,则直接消费该事件。

public boolean onTouchEvent(MotionEvent event) {
    ...
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
        || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
        || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;//当前View是否可点击
    ...
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP://抬起
                ...
                if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                    if (!focusTaken) {
                        removeLongPressCallback();//若有长按事件未处理,则移除长按事件
                        if (mPerformClick == null) {
                            mPerformClick = new PerformClick();
                        }
                        if (!post(mPerformClick)) {//通过Hanlder将点击事件发送到主线程执行
                            performClickInternal();//如果不成功,则直接引用点击事件
                        }
                    }
                }
                if (mUnsetPressedState == null) {
                    mUnsetPressedState = new UnsetPressedState();//更新按钮的按压事件
                }
                ...
                break;
            case MotionEvent.ACTION_DOWN://按下
                ...
                if (isInScrollingContainer) {//在可滚动的容器内,为了容错,延迟点击
                    ...
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    setPressed(true, x, y);//设置按下的状态
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);//开启一个长按延时事件
                }
                break;

            case MotionEvent.ACTION_CANCEL://取消
                ...
                break;
            case MotionEvent.ACTION_MOVE://移动
                ...
                break;
        }
        return true;//如果是可点击的View,即消费事件
    }
    ...
    return false;
}

4.7 例子-点击事件时序图

        如下是Android的点击事件时序图,如果能够理解单击事件的由来,对于整个事件分发的知识要点已大体掌握。

五、小结&问题点

  1. 事件分发流程?包括ACTION_DWON、ACTION_UP、ACTION_MOVE事件的处理过程;
  2. ACTION_CANCEL事件的使用场景?父控件对move事件拦截场景?
  3. 单击、长按、触摸事件的产生过程?
  4. 点击一个View未抬起,同时move该事件直至离开当前View的范围,处理过程如何?
  5. 如果所有View都未消费事件,流程如何?
  6. ViewPage+ListView,左右滑动和上下滑动冲突的解决问题?即事件拦截过程?
  7. 普通的View是根据什么来决定是否消费事件,例如Button?
    =>答:如无重写onTouchEvent事件,根据当前的View是否可点击,来决定是否消费事件。

        我最开始没有看源码,直接去看博客上的内容,弯弯绕绕,似懂非懂。在面试的过程中,面试官举个场景分析流程,我都懵逼,分析不出来,现场很尴尬。之后看源码,整体流程代码量很少,感叹于Android事件分发流程的设计,很少的代码量,却承载了很重要的功能,而没有见过该模块发生过异常。
        多读书,多看报,少吃零食,多睡觉!

六、代码仓库地址

Demo地址:  https://gitee.com/linzhiqin/custom-demo

七、参考资料

https://zhuanlan.zhihu.com/p/623664769?utm_id=0
事件分发视频(总结很好,但是得先理解基本概念,才方便学习)
https://www.bilibili.com/video/BV1sy4y1W7az?p=1&vd_source=f222e3bf3083cad8d9f660629bc47c16

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vmlinux, System.map; cmake的find_package(Clang)产生的变量们; geogebra单位切向量(简单例子)

linux4.15.y内核中的函数个数 依赖关系: vmlinux, vmlinux.bin, bzImage cd /bal/linux-stable/ file vmlinux #vmlinux: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]b99bbd9dda1ec2751da246d4a7ae4e6fcf7d789b, not str…

渐进增强与优雅降级:提升用户体验的双重策略

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

【人工智能】智能电网:未来能源的革命

未来能源的革命 智能电网革命的意义在于将电力行业从传统的集中式发电和集中式输配电模式转变为智能化、分布式、互动式的能源网络。 现在我们从以下方面详细认真的了解一下智能电网: 智能变电站,智能配电网,智能电能表,智能交互…

基于arcgis的遥感深度学习数据集制作

由于很多时候,我们在研究过程中往往需要根据实际情况使用自己的影像数据来提取目标物,如果没有合适的公开数据集的话,为了满足实际需要,我们就需要制作符合自己要求的数据集。 今天我们就根据实际情况来详细讲解如何利用arcgis&am…

CMU15-445-Spring-2023-Project #2 - B+Tree

前置知识:参考上一篇博文 CMU15-445-Spring-2023-Project #2 - 前置知识(lec07-010) CHECKPOINT #1 Task #1 - BTree Pages 实现三个page class来存储B树的数据。 BTree Page internal page和leaf page继承的基类,只包含两个…

K8S的部署策略,重建更新和滚动更新

Deployment Strategies 部署战略 When it comes time to change the version of software implementing your service, a Kubernetes deployment supports two different rollout strategies: RecreateRollingUpdate 当需要更改实施服务的软件版本时,Kubernetes …

代码随想录刷题题Day29

刷题的第二十九天,希望自己能够不断坚持下去,迎来蜕变。😀😀😀 刷题语言:C Day29 任务 ● 01背包问题,你该了解这些! ● 01背包问题,你该了解这些! 滚动数组 …

WebGL在实验室方向的应用

WebGL在实验室方向的应用涉及到实验过程的可视化、数据分析、模拟等方面。以下是一些WebGL在实验室领域的应用示例,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 1.分子模型和化学反应模拟&#xff…

element plus el-form双列布局及拓展任意布局

1 场景 一般表单我们直接默认布局&#xff0c;也就是单列布局&#xff0c;突然有个人员信息表单&#xff0c;需要双列布局的需求&#xff0c;简单实现并拓展下 2 思路 直接无脑divflex布局实现 3 代码 <template><el-form ref"formRef" :model"fo…

2024--Django平台开发-Django知识点(五)

day05 django知识点 今日概要&#xff1a; 中间件 【使用】【源码】cookie 【使用】【源码 - Django底层请求本质】session【使用】【源码 - 数据库请求周期中间件】 1.中间件 1.1 使用 编写类&#xff0c;在类型定义&#xff1a;process_request、process_view、process_…

基于JavaWeb+BS架构+SpringBoot+Vue校园一卡通系统的设计和实现

基于JavaWebBS架构SpringBootVue校园一卡通系统的设计和实现 文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 文末获取源码 Lun文目录 第一章 概述 4 1.1 研究背景 4 1.2研究目的及意义 4 1.3国内外发展现状 4 1…

最新出炉!知乎最牛最全JMeter+Ant+Jenkins接口自动化测试框架(Windows)

一:简介 大致思路&#xff1a;Jmeter可以做接口测试&#xff0c;也能做压力测试&#xff0c;而且是开源软件&#xff1b;Ant是基于Java的构建工具&#xff0c;完成脚本执行并收集结果生成报告&#xff0c;可以跨平台&#xff0c;Jenkins是持续集成工具。将这三者结合起来可以搭…

数据结构及单链表例题(下)

上次我们已经了解了单链表的数据结构定义以及创建单链表的两种方法,这节介绍几道例题. 文章目录 前言 一、已知L为带头结点的单链表,请依照递归思想实现下列运算 二、单链表访问第i个数据节点 三、在第i个元素前插入元素e 四、删除第i个结点 五、查找带头结点单链表倒数第…

C++学习笔记(三十二):c++ 堆内存与栈内存比较

本节对堆和栈内存进行描述。 应用程序启动后&#xff0c;操作系统将整个程序加载到内存&#xff0c;分配相应的物理ram&#xff0c;确保程序可以正常运行。堆和栈是ram中存在的两个区域。栈通常是一个预定义大小的内存区域&#xff0c;一般是2M字节左右。堆也是预定了默认值的…

固乔快递查询助手:批量、快速、全面的快递信息查询软件

在快递行业飞速发展的今天&#xff0c;如何高效、准确地掌握快递信息成为了很多人的需求。而固乔快递查询助手正是解决这一难题的利器。 固乔快递查询助手是一款专注于快递信息查询的软件&#xff0c;支持多家主流快递公司查询。用户只需输入单号&#xff0c;即可快速查询到实时…

RIP复习实验

条件: R1为外网&#xff0c;R8和r9的环回分别是172.16.1.0/24和172.16.2.0/24 中间使用78.1.1.0/24 剩下的路由器2-6使用172.16.0.0/16 要求: R1为运营商 r1远程登录r2实际登录r7 R2访问r7要求走r5去访问 全网可达 实现流程: 首先配置好各接口ip address 然后r2-r7使用rip…

vue2使用文件上传读取本地照片并转化base64格式进行展示

创建个vue2项目,直接把代码放到一个vue2页面内运行就好,下面代码拿来即用 <template><div><div class"replace_menu_mask" click"closeMenu"><img :src"replaceImg" alt"" style"width: 100%;">&l…

PandoraNext—一个让你呼吸顺畅的ChatGPT

博客地址 PandoraNext—一个让你呼吸顺畅的ChatGPT-雪饼 (xue6ing.cn)https://xue6ing.cn/archives/pandora--yi-ge-rang-ni-hu-xi-shun-chang-de-chatgpt 项目 项目地址 pandora-next/deploy 项目介绍 支持多种登录方式&#xff1a; 账号/密码 Access Token Session To…

探索Shadowsocks-Android:保护你的网络隐私

探索Shadowsocks-Android&#xff1a;保护你的网络隐私 I. 引言 在数字时代&#xff0c;网络隐私和安全变得愈发重要。我们越来越依赖互联网&#xff0c;但同时也面临着各种网络限制和监控。在这个背景下&#xff0c;Shadowsocks-Android应用程序应运而生&#xff0c;为用户提…

文心大模型融入荣耀MagicOS!打造大模型“端云协同”创新样板

2024年1月10日&#xff0c;在荣耀MagicOS 8.0发布会及开发者大会上&#xff0c;荣耀终端有限公司CEO赵明宣布了“百模生态计划”&#xff0c;并与百度集团执行副总裁、百度智能云事业群总裁沈抖共同宣布&#xff0c;百度智能云成为荣耀大模型生态战略合作伙伴。 沈抖在现场演讲…