【源码阅读系列】(五)进程间通信(二)

进程间通信(二)

这一部分主要会介绍Android中特有的几个IPC机制。分别是: Intent、Binder、AIDL、ContentProvider

https://juejin.cn/post/7244018340880007226
https://juejin.cn/post/6844903764986462221

Binder

https://juejin.cn/post/7244018340880007226
https://juejin.cn/post/6897868762410811405
https://juejin.cn/post/7278103488097452092

Binder机制在安卓知识体系中的位置是非常重要的,理解Binder是成为高级安卓开发工程师的第一步。首先需要思考为什么需要Binder?

Android 系统是基于 Linux 内核的,Linux 已经提供了管道、消息队列、共享内存和 Socket 等 IPC 机制。那为什么 Android 还要提供 Binder 来实现 IPC 呢?主要是基于性能、稳定性和安全性几方面的原因。

这里我们可以整体回忆一下之前在进程间通信(一)里面提到的哪几种IPC机制。

  • 管道:管道一次通信需要经历2次数据复制(进程A -> 管道文件,管道文件 -> 进程B)并且是通过内核缓冲区实现的,这个缓冲区是有限的,如果传输的数据大小超过缓冲区上限,或者在阻塞模式下没有安排好数据的读写,会出现阻塞的情况。管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式。管道自带同步机制。
  • 共享内存:共享内存是几种IPC机制中最快的,性能最好的,但是没有同步机制,安全性较差。
  • 消息队列:息队列是存放在内核中的消息链表,每个消息队列由消息队列标识符表示。消息队列允许多个进程同时读写消息,发送方与接收方要约定好,消息体的数据类型与大小。消息队列克服了信号承载信息量少、管道只能承载无格式字节流等缺点,消息队列一次通信同样需要经历2次数据复制(进程A -> 消息队列,消息队列 -> 进程B)
  • Socket:UNIX Domain Socket 是典型的C/S架构,一个Socket会拥有两个缓冲区,一读一写,由于发送/接收消息需要将一个Socket缓冲区中的内容拷贝至另一个Socket缓冲区,所以Socket一次通信也是需要经历2次数据复制

而我们现在提到的 Binder,是Android 系统通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder驱动(Binder Dirver)。

Binder 底层使用了内存映射(mmp)的机制,实现了传输数据只需要一次拷贝!

Binder机制的底层原理

由于Binder机制的知识量过于庞大,我这里先放着,只稍微记录一下他的思想。

Binder基于C/S的结构下,定义了4个角色:Server、Client、ServerManager、Binder驱动,其中前三者是在用户空间的,也就是彼此之间无法直接进行交互,Binder驱动是属于内核空间的,属于整个通信的核心,虽然叫驱动,但是实际上和硬件没有太大关系,只是实现的方式和驱动差不多,驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。

Binder能够实现的最重要的概念,就是内存映射。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。

比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘–>内核空间–>用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。

一次完整的 Binder IPC 通信过程通常是这样:

首先 Binder 驱动在内核空间创建一个数据接收缓存区;
接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

在这里插入图片描述

Binder的优势

Binder 在 Android 系统中具有显著的性能、稳定性和安全性优势,具体表现如下:

  1. 性能优势
  • 高效数据传输:Binder 仅需一次数据拷贝(发送进程到接收进程),性能上接近共享内存,优于传统的 Socket、消息队列和管道(均需两次拷贝)。
  • 低开销:与通用 Socket 接口相比,Binder 适用于本地进程间高效通信,开销更小,传输效率更高。
  1. 稳定性优势
  • 架构清晰:Binder 基于客户端-服务器(C/S)架构,客户端发起请求,服务端响应执行,职责分明且相互独立,保证了系统的高稳定性。
  • 简化控制:相比共享内存,Binder 通信机制简单,不需开发者管理复杂的共享内存控制逻辑,降低了出错几率。
  1. 安全性优势
  • 可靠身份验证:Binder 支持进程的用户 ID(UID)和进程 ID(PID)鉴别,杜绝了传统 IPC 无法确认对方身份的缺陷,确保通信方的可信性。
  • 控制访问权限:支持实名和匿名 Binder,限制未授权的应用程序访问,避免恶意程序通过猜测接入点地址进行未经许可的连接,安全性更高。

总结
Android 使用 Binder 作为核心 IPC 机制,满足系统对高性能、稳定性和安全性的需求,实现了高效、可靠的进程间通信。

Intent

熟悉安卓开发的小伙伴应该对Intent非常了解了,不管是启动活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver),还是传递数据和操作,都离不开Intent的参与。可以说,Intent是四大组件之间的纽带。

首先还是贴出官方概念:https://developer.android.com/reference/android/content/Intent

基本概念

Intent,中文可翻译为“意图”,可用于Android同个应用程序中各个组件之间的交互,或者不同应用程序之间的交互。可以用来表明当前组件的思想和意图,比如想执行某个动作,想发送某些数据等等。每个组件都有不同的启动方法:

● Activity:可以调用startActivity() 或 startActivityForResult() 传递 Intent来打开新的Activity;
● Service:可以调用startService()传递 Intent 来启动服务,也可通过 bindService() 传递 Intent 来绑定到该服务;
● Broadcast,可以调用sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast() 等方法传递 Intent 来发起广播;

Intent分为显式Intent和隐式Intent,我们以打开新的Activity为例进行讲解。

分类

● 显式intent:通过在intent中明确设置想要启动组件的类名,来显式地向安卓系统表达本次启动的目的。

Intent intent = new Intent(this, SecondActivity.class);  
startActivity(intent); 

● 隐式intent:在intent中不会显式设置欲启动的组件类名,而是通过设置action、category等信息,安卓系统会自动根据各个组件在AM文件中设置的intent-filter来进行过滤和匹配,匹配成功的组件将会被启动。如果匹配到不止一个组件,将会通过弹窗的方式让用户选择处理该intent的组件

// 清单文件中 MyActivity 提前声明好如下:<activity android:name=".MyActivity"><intent-filter><action android:name="com.example.android.Mytest"/><category android:name="android.intent.category.DEFAULT"/><category android:name="com.example.android.Mycategory"/></intent-filter></activity>// 代码调用Intent intent = new Intent();intent.setAction(com.example.android.Mytest);intent.addCategory(com.example.android.Mycategory);startActivity(intent); //1

执行 [注释1]的代码后,系统会发现MyActivity的所设定的内容,和当前Intent所设定的内容最匹配,系统就会打开MyActivity,但这个过程中,我们并没有显式的指出打开MyActivity,而是通过设置了一些特定条件进行匹配,如“action”,“category”等,从而隐式地打开了MyActivity。

Intent的组成部分

“action”,“category”都是Intent的组成部分。为了更好的理解隐式Intent,需要了解一个Intent由几部分组成:

  • componentName(组件名):目的组件
  • action(动作):用来表现意图的行动
  • uri(统一资源标识符):用于指定资源的具体位置
  • category(类别):用来表现动作的类别
  • data(数据):表示与动作要操纵的数据
  • type(数据类型):对于data范例的描写
  • extras(扩展信息):扩展信息
  • Flags(标志位):期望这个意图的运行模式

安卓系统匹配隐式intent的过程:

  1. 加载安装所有的intent-filter的组件
  2. 剔除action匹配失败的组件
  3. 剔除URI数据匹配失败的组件
  4. 剔除category匹配失败的组件
  5. 如果匹配到的组件数量大于1,按照优先级返回最高优先级的组件。

常见构造

1. 创建Intent
import android.content.Intent;Intent intent = new Intent(); // 空构造
Intent intent = new Intent(String action); // 隐式intent,传入action
Intent intent = new Intent(String action, Uri uri); // 隐式intent
Intent intent = new Intent(Context packageContext, Class<?> cls); // 显式intent,参数1是当前组件的context,参数2是目标组件的类名
Intent intent = new Intent(String action, Uri uri, Context packageContext, Class<?> cls); // 显式intent
2. 设置component目的组件(显式intent)

Component属性明确指定Intent的目标组件的类名称。(属于显示Intent)
如果component这个属性有指定的话,将直接使用它指定的组件。指定了这个属性以后,Intent的其它所有属性都是可选的。

Intent intent = new Intent();
ComponentName component = new ComponentName(MainActivity.this, SecondActivity.class);
intent.setComponent(component);
startActivity(intent);// 简写成
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
3. 设置action动作

action表示intent欲要完成的动作,在Intent类中,定义了一批量的动作,比如ACTION_VIEW,ACTION_PICK等, 基本涵盖了常用动作。
action也可以是一个用户定义的字符串,用于标识一个用户自定义的 Android 应用程序组件。
一个 Intent Filter 可以包含多个 Action,但是一个intent对象只可以设置一个action。
在 AndroidManifest.xml 的Activity 定义时,可以在其intent-filter节点指定一个Action列表用于标识 Activity所能接受的“动作”。

Intent intent = new Intent();
intent.setAction(Intent.ACTION_MAIN);

具体有哪些Action动作可选,可以参考:https://developer.android.com/reference/android/content/Intent

4. 添加category类别

只有action和category中的内容同时能够匹配上Intent中指定的action和category时,这个活动才能响应intent。
用户在AM中声明安卓组件时,如果这个组件没有特别的category,则需要显式地将DEFAULT类型的category添加到category列表中,因为当调用startActivity()方法的时候会自动将这个DEFAULT 类型的category添加到Intent中。
自定义类别: 在Intent添加类别可以添加多个类别,那就要求这些类别能同时被匹配上,才算该intent被组件匹配成功。
操作Activity的时候,如果没有类别,须加上默认类别。

Intent intent = new Intent();
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
startActivity(intent);
5. 设置data数据和type类型

Data属性是Android要访问的数据,和action和category声明方式相同,也是在intent-filter中。
多个组件匹配成功显示优先级高的; 相同显示列表。
Data是用一个uri对象来表示的,uri代表数据的地址,属于一种标识符。通常情况下,我们使用action+data属性的组合来描述一个意图:做什么。
使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android多个应用程序之间的功能共享成为了可能。比如应用程序中需要展示一个网页,没有必要自己去实现一个浏览器(事实上也不太可能),而是只需要调用系统的浏览器来打开这个网页就行了。

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));                
startActivity(intent); 
6. 设置和读取扩展数据extra

通过intent.putExtra(String,XXX)方法以键值对的方式传递数据,其中XXX可代表基本数据类型、数组等。
获取数据通过intent.getXXXExtra(String)方法,其中XXX可代表基本数据类型、数组等。
Intent传递Serializable对象:

Intent intent = new Intent(ActivityA.this, ActivityB.class);
intent.putExtra("key", value)
7. 设置flags标志位

Intent 的 flags 标志位用于指定启动 Activity 的行为,比如启动模式和任务栈处理方式。

//用于启动一个新的任务栈,常用于从非 Activity 的上下文启动 Activity,比如从 BroadcastReceiver 或 Service 中启动 Activity。Intent intent = new Intent(context, TargetActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);//可以组合使用多个 flags,比如同时设置 FLAG_ACTIVITY_CLEAR_TOP 和 FLAG_ACTIVITY_NEW_TASK:
Intent intent = new Intent(context, TargetActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

Intent 数据传输的限制

Intent 传输数据的大小是有限制的,大概是1MB左右(这个数据不是固定的,有可能0.8M也会报错)。这是因为Intent 对象在 Android 系统中是通过 Binder 机制在进程间传递的。Binder 机制设计用于高效地处理进程间通信(IPC),但它并不适合传输大量数据。如果尝试通过 Intent 传递大量数据,那么这将显著增加 IPC 过程的开销,进而影响应用程序的性能。

如果我们调用

intent.putExtra("key", value) // value超过1M

则会出现报错,报错信息如下:

android.os.TransactionTooLargeException: data parcel size xxx bytes

如果非要想传输大一点的数据可以参考:https://juejin.cn/post/7109862936604049415。

Intent的底层原理

这里主要可以参考博客:https://juejin.cn/post/7252951745013727292

大致的意思是,在使用Intent去启动活动等时,PackageManagerService(PMS)就会启动,PMS将分析所有已安装的应用信息,构建相对应的信息表,当用户需要通过Intent跳转到某个组件时,会根据Intent中包含的信息,然后从PMS中查找对应的组件列表,最后跳转到目标组件。

总体有一个:扫描➡️解析➡️绘制信息树➡️信息匹配 的过程。

AIDL

AIDL(Android Interface Definition Language)是一种用于定义和实现跨进程通信接口的语言。它是 Android 系统中用于进程间通信(IPC)的一种机制,允许一个进程向另一个进程发送请求并获取响应。

AIDL 是 Binder 的高级封装

  • AIDL 是为简化使用 Binder 而设计的接口描述语言,开发者可以使用 AIDL 定义服务接口,系统会自动生成用于跨进程通信的接口代码。
  • 通过 AIDL,开发者不需要直接操作底层的 Binder API,只需定义接口方法,AIDL 编译器会生成 Binder 的代理类和服务端接口,从而简化了 IPC 的实现。

Android AIDL 的使用方法和示例

在 Android 中使用 AIDL(Android Interface Definition Language)来实现跨进程通信,主要包括以下步骤:

  1. 创建 AIDL 文件并声明接口方法。
  2. 创建服务(Service),在其中实现 AIDL 接口并返回 Binder 对象。
  3. 在客户端绑定服务,通过 ServiceConnection 获取 AIDL 接口对象并调用其方法。

使用方式可以参考:
https://juejin.cn/post/7126801737561669639
https://juejin.cn/post/7123129439898042376

这里由于篇幅有限,不介绍AIDL 的具体使用方法,后续会开专题介绍。

AIDL 的原理

服务端(Server)

服务端需要实现一个 Service,作为远程服务的提供者,包含具体的业务逻辑。

客户端(Client)

客户端需要绑定服务,获取远程服务的 Binder 对象后,通过该对象调用服务端的方法。

绑定过程

  1. 绑定服务:客户端调用 bindService 方法绑定远程服务。
  2. 接收 Binder:通过 ServiceConnection 获取远程服务的 Binder 对象。
  3. 远程调用:通过 Binder 调用服务端定义的方法。
Stub 类(用于服务端)
  • 作用:Stub 类在服务端实现了接口的所有方法,并将其绑定到 Binder 的通信逻辑中。服务端的所有逻辑处理都会在 Stub 的实现类中完成。
  • 功能
    • 处理客户端的请求。
    • 提供跨进程方法的具体实现。
onTransact 方法
  • 作用onTransact 是 Stub 类的核心方法,用于接收并处理客户端的远程调用请求。
  • 工作流程
    1. 根据方法标识符(code 参数)判断客户端调用的是哪一个接口方法。
    2. Parcel 对象中解析客户端传递的数据。
    3. 调用对应的接口方法。
    4. 将结果写入返回的 Parcel 对象,并发送给客户端。
Proxy 类(用于客户端)
  • 作用Proxy 是客户端的代理类,负责将客户端调用的方法转化为 Binder 的底层通信。
  • 工作流程
    1. 通过 transact 方法将调用请求发送到服务端的 onTransact 方法。
    2. 使用 Parcel 对象传递请求参数和接收响应结果。
    3. 将服务端返回的数据封装为客户端需要的格式。

示例调用链:获取书籍列表

以 getBookList() 为例,具体调用链如下:

  1. 客户端调用 Proxy.getBookList():
  • 客户端通过 Proxy 调用方法。
  • Proxy 封装请求,发送到服务端。
  1. 服务端接收请求:
  • Stub.onTransact() 方法接收并解析请求。
  • 根据方法标识符调用 getBookList() 的具体实现。
  1. 服务端返回结果:
  • 服务端将结果写入 Parcel。
  • 返回数据通过 Binder 传递到客户端。
  1. 客户端接收结果:
  • Proxy 解包数据,将结果返回给客户端。

ContentProvider

ContentProvider的底层实现是采用Android中的Binder机制,它允许不同应用程序之间或者同一个应用程序的不同部分之间共享数据。ContentProvider本质上就是封装了一层接口,用来屏蔽各种数据存储的方式。 不管是数据库、磁盘、还是网络存储,只需要通过contentProvider 提供的方式来获取就行。使用方不用关心具体的实现逻辑。
提供的方法包括: query、insert、update、delete。

contentProvider的特点:

  1. 数据共享:ContentProvider允许应用程序之间共享数据,这些数据可以是结构化的(如数据库中的数据)、非结构化的,甚至是文件中的数据
  2. 统一接口:ContentProvider提供了一个统一的接口,使得其他应用程序可以通过这个接口访问和操作数据,包括增删改查等操作
  3. URI访问:ContentProvider使用URI(统一资源标识符)来标识数据,其他应用程序可以使用URI来定位和访问特定数据
  4. 权限控制:ContentProvider可以定义权限来控制哪些应用程序有权访问数据,从而保护数据安全性
  5. 基于Binder机制:ContentProvider的底层实现是采用Android中的Binder机制,这是一种高效的IPC方式,允许跨进程调用
  6. 通知机制:当ContentProvider中的数据发生变化时,它可以通知所有已注册的侦听器(ContentObserver),这样其他应用程序可以及时响应数据的变化
  7. 跨进程通信的实现:通过在AndroidManifest.xml中声明ContentProvider,并在其中实现抽象方法,如query、insert、update和delete,应用程序可以接收来自其他进程的请求,并处理数据

contentProvider的使用方式

provider提供方

  1. 需要继承ContentProvider抽象类。并且实现相应的query方法

用户需要实现如下抽象方法:

// 创建数据库并获得数据库连接
public abstract boolean onCreate()// 查询数据
public abstract Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)// 插入数据
public abstract Uri insert(Uri uri, ContentValues values)// 删除数据
public abstract int delete(Uri uri, String selection, String[] selectionArgs)// 更新数据
public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)// 获取数据类型(MIME类型,如:"text/html"、"image/png"、"message/rfc882"、"vnd.android-dir/mms-sms")
public abstract String getType(Uri uri)

参数说明:

  • uri:数据表路径
  • projection:需要查询的字段名称
  • selection:查询条件
  • selectionArgs:查询条件中的参数列表
  • sortOrder:排序
  1. URI的定义
    协议:授权标识:路径:
content://authority/data_path/id
Content://com.demo.gradle/android/db
  1. 在AndroidManifest.xml 中注册
<provider
android:authorities="com.demo.gradle"
android:name=".MyProvider"
android:exported="true"
/>

provider调用方
当contentResolve要查询或者插入的时候,就需要解析uri。

val resolver = context.contentResolver
var uri=Uri.parse("content://com.demo.gradle/android/db")resolver.query(uri)

第一步,获取contentResolver对象
第二步,开始执行query

代码范例可参考:https://blog.csdn.net/m0_37602827/article/details/109912206

了解更多

ContentProvider基础概念

使用 ContentObserver 监听数据变化

高级场景 - 数据分页加载

最佳实践和常见问题

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习(ML)在发射机识别与资源管理的应用

电子战&#xff08;EW&#xff09;涉及在受干扰的频谱环境中&#xff0c;通过多个无线电频率传感和发射平台进行非合作交互。操作人员需要管理频谱资源、共享关键情报&#xff0c;并有效干扰威胁发射器。现代RF系统的复杂性和威胁发射器的敏捷性&#xff0c;要求系统能够以机器…

高项 - 信息化发展

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 博文更新参考时间点&#xff1a;2024-11-09 高项 - 章节与知识点汇总&#xff1a;点击跳转 文章目录 高项 - 信息化发展信息与信息化信息信息系统信息化 现代化基础设施新型基础设施建设工业互联网车联网 现代化创…

TaskBuilder内设置任擎服务器

TaskBuilder内设置任擎服务器 在使用TaskBuilder进行软件开发时&#xff0c;必须要先连接到任擎服务器&#xff08;后续文档所说的服务器如果不特别注明&#xff0c;皆指任擎服务器&#xff09;才能继续操作&#xff0c;因为使用TaskBuilder开发所需的数据模型、后台服务和前端…

六、nginx负载均衡

负载均衡&#xff1a;将四层或者七层的请求分配到多台后端的服务器上。 从而分担整个业务的负载。提高系统的稳定性&#xff0c;也可以提高高可用&#xff08;备灾&#xff0c;其中一台后端服务器如果发生故障不影响整体业务&#xff09;. 负载均衡的算法 round robin 轮询 r…

代码随想录训练营第十七天| 654.最大二叉树 617.合并二叉树 700.二叉搜索树中的搜索 98.验证二叉搜索树

654.最大二叉树 题目链接/文章讲解&#xff1a; 代码随想录 视频讲解&#xff1a;又是构造二叉树&#xff0c;又有很多坑&#xff01;| LeetCode&#xff1a;654.最大二叉树_哔哩哔哩_bilibili 创建一个根节点&#xff0c;其值为 nums 中的最大值。递归地在最大值 左边 的 子…

面向对象进阶:多态

黑马程序员Java个人笔记 BV17F411T7Ao p129~132 目录 多态 多态调用成员的特点 调用成员变量 调用成员方法 理解 多态的优势 解耦合 多态的弊端 解决方案&#xff1a;强制类型转换 instanceof jdk14新特性&#xff0c;将判断和强转放一起 总结 多态 多态调…

系统思考沙盘模拟

今天《收获季节》沙盘模拟的第一天课程圆满结束&#xff0c;不仅从管理技巧的角度深入展开&#xff0c;还让大家体验了决策带来的直接影响。明天&#xff0c;我们将带领学员从系统思考和全局视角来重新审视这些问题&#xff0c;找到更深层的因果关系和系统性改进的思路。期待更…

AI 赋能直播新玩法 —— 无人直播,它到底藏着多少未知?

​ 在数字浪潮汹涌澎湃的时代&#xff0c;直播领域正历经一场前所未有的变革&#xff0c;AI 赋能的无人直播宛如一颗神秘新星&#xff0c;闯入大众视野&#xff0c;撩拨着人们的好奇心&#xff0c;可它究竟潜藏着多少待解谜团&#xff0c;尚无人能完全洞悉。 从技术的幽微深处…

【深度学习】热力图绘制

热力图&#xff08;Heatmap&#xff09;是一种数据可视化方法&#xff0c;通过颜色来表示数据矩阵中的数值大小&#xff0c;以便更直观地展示数据的分布和模式。热力图在许多领域中都有应用&#xff0c;尤其在统计分析、机器学习、数据挖掘等领域&#xff0c;能够帮助我们快速识…

ssm-springmvc-学习笔记

简介 简单的来说&#xff0c;就是一个在表述层负责和前端数据进行交互的框架 帮我们简化了许多从前端获取数据的步骤 springmvc基本流程 用户在原本的没有框架的时候请求会直接调用到controller这个类&#xff0c;但是其步骤非常繁琐 所以我们就使用springmvc进行简化 当用…

Axure原型设计:打造科技感可视化大屏元件

在数字化时代&#xff0c;数据可视化大屏已成为企业展示数据、监控业务状态的重要工具。一个设计精良的大屏不仅要有丰富的信息展示&#xff0c;更需具备强烈的科技感&#xff0c;以吸引观众的注意力并提升数据解读的效率。Axure&#xff0c;作为一款功能强大的原型设计工具&am…

supervision - 好用的计算机视觉 AI 工具库

Supervision库是一款出色的Python计算机视觉低代码工具&#xff0c;其设计初衷在于为用户提供一个便捷且高效的接口&#xff0c;用以处理数据集以及直观地展示检测结果。简化了对象检测、分类、标注、跟踪等计算机视觉的开发流程。开发者仅需加载数据集和模型&#xff0c;就能轻…

探索 Cesium 的未来:3D Tiles Next 标准解析

探索 Cesium 的未来&#xff1a;3D Tiles Next 标准解析 随着地理信息系统&#xff08;GIS&#xff09;和 3D 空间数据的快速发展&#xff0c;Cesium 作为领先的开源 3D 地球可视化平台&#xff0c;已成为展示大规模三维数据和进行实时渲染的强大工具。近年来&#xff0c;随着…

Launcher启动流程

Launcher启动流程分2个阶段&#xff1a; AMS systemReady() 会启动一个临时Activity&#xff1a;com.android.settings.FallbackHome&#xff0c;如下流程等到用户解锁成功后&#xff0c;FallbackHome轮询到有可用的RealHome包&#xff0c;会销毁掉自己&#xff0c;AMS发现没有…

链式栈的实现及其应用

目录 一、链式栈结构模型 二、链式栈的实现 2.1创建 2.2压栈 2.3出栈 2.4判断栈是否为空 2.5查看栈顶 2.6释放栈 三、应用 链式栈实际上就是基于链表&#xff0c;压栈和弹栈可分别看作头插和头删&#xff0c;链表尾部就是栈底&#xff0c;头指针就是栈顶指针 一、链式…

视频监控汇聚平台方案设计:Liveweb视频智能监管系统方案技术特点与应用

随着科技的发展&#xff0c;视频监控平台在各个领域的应用越来越广泛。然而&#xff0c;当前的视频监控平台仍存在一些问题&#xff0c;如视频质量不高、监控范围有限、智能化程度不够等。这些问题不仅影响了监控效果&#xff0c;也制约了视频监控平台的发展。 为了解决这些问…

Ubuntu 20.04LTS 系统离线安装5.7.44mysql数据库

Ubuntu 20.04LTS 系统离线安装5.7.44mysql数据库 环境下载 MySQL 5.7.44 包安装标题检查服务是否启动成功遇到的问题登陆&修改密码&远程访问 环境 操作系统&#xff1a;Ubuntu 20.04.4 LTS 数据库&#xff1a;MySQL 5.7.34 内核版本&#xff1a;x86_64&#xff08;amd…

国信华源科技赋能长江蓄滞洪区水闸管护项目验收成果报道

“碧水悠悠绕古城&#xff0c;闸启长江万象新。”近日&#xff0c;由北京国信华源科技有限公司倾力打造的万里长江蓄滞洪区水闸管护项目&#xff0c;圆满通过验收&#xff0c;为这片鱼米之乡的防洪安全注入了新的科技活力。 长江之畔&#xff0c;水闸挺立&#xff0c;犹如干堤上…

Spring Boot教程之二十五: 使用 Tomcat 部署项目

Spring Boot – 使用 Tomcat 部署项目 Spring Boot 是一个基于微服务的框架&#xff0c;在其中创建可用于生产的应用程序只需很少的时间。Spring Boot 建立在 Spring 之上&#xff0c;包含 Spring 的所有功能。如今&#xff0c;它正成为开发人员的最爱&#xff0c;因为它是一个…