Android系统开发(十四):跨进程通讯的隐形之手AIDL

引言

你是否曾在 Android 开发中为进程间通讯(IPC)头疼不已?如果是,那么 AIDL 就是你的救星!它不仅让跨进程数据传输变得高效,而且还解决了异构环境的兼容性问题。本篇文章将带你深入了解 AIDL,从理论到实战,再到坑点分析,全方位揭秘它的强大之处。别担心,技术干货虽多,配上幽默诙谐的风格,你一定能轻松读完并收获满满!
在这里插入图片描述


一、AIDL

在 Android 系统中,每个应用运行在独立的进程中,而跨进程通信(IPC)一直是开发中的一大挑战。传统方法如 Binder 和 Messenger 的复杂性让许多开发者望而却步,而 AIDL 的出现则为 IPC 提供了更高效、简洁的解决方案。AIDL 是 Android 提供的一个抽象层,通过 .aidl 文件定义接口,生成可供不同进程共享的绑定类,支持多语言和多架构环境运行。


二、概念

**AIDL 的核心在于基于 Binder 的跨进程通信机制。**它通过编写 .aidl 文件定义接口,并利用 AIDL 编译器生成代理类和 Stub 类,分别负责客户端和服务端的通信。关键步骤包括:序列化数据、通过 Binder 驱动传递消息、在目标进程解包并调用实际方法。简而言之,AIDL 就像进程间的一座“翻译桥梁”,让彼此说“不同语言”的进程实现高效沟通。


三、步骤

环境准备
  • 开发工具:Android Studio
  • 语言:Kotlin 或 Java
  • 最低 SDK 版本:API 16
实现步骤
  1. 创建 .aidl 文件
    src/main/aidl 目录下新建接口文件:

    package com.example.myapp;
    interface IMyService {
        String getData();
    }
    
  2. 生成绑定代码
    使用 Gradle 自动生成 IMyService 的代理和 Stub 类。

  3. 实现服务端逻辑
    在服务端实现 Stub 接口:

    class MyService : Service() {
        private val binder = object : IMyService.Stub() {
            override fun getData(): String {
                return "Hello from AIDL Service!"
            }
        }
        override fun onBind(intent: Intent): IBinder = binder
    }
    
  4. 客户端绑定服务
    客户端通过 ServiceConnection 获取 AIDL 服务实例:

    class MainActivity : AppCompatActivity() {
        private var service: IMyService? = null
        private val connection = object : ServiceConnection {
            override fun onServiceConnected(name: ComponentName, binder: IBinder) {
                service = IMyService.Stub.asInterface(binder)
                val data = service?.getData()
                Log.d("MainActivity", "Received: $data")
            }
            override fun onServiceDisconnected(name: ComponentName) {
                service = null
            }
        }
        fun bindService() {
            val intent = Intent(this, MyService::class.java)
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }
    

四、项目案例 1:音乐播放器的跨进程控制

背景描述
在音乐播放器的应用中,播放服务和用户界面通常运行在不同的进程中。服务负责处理音频播放,用户界面则通过 IPC(跨进程通信)控制服务,如播放、暂停、切换歌曲等。通过 AIDL,可以方便地实现这种跨进程控制。


AIDL 文件定义接口

创建 AIDL 文件 IMusicService.aidl 定义服务端接口方法。
路径:src/main/aidl/com/example/musicplayer/IMusicService.aidl
内容如下:

package com.example.musicplayer;

// 音乐服务接口
interface IMusicService {
    void play();            // 播放音乐
    void pause();           // 暂停音乐
    void stop();            // 停止音乐
    void nextTrack();       // 播放下一首
    void previousTrack();   // 播放上一首
    int getCurrentTrack();  // 获取当前播放曲目
}

AIDL 文件编译后,会自动生成 IMusicService 的代理类和 Stub 类。


服务端实现

在服务端 MusicService 中实现 IMusicService 的 Stub 类,定义音乐控制逻辑。

MusicService.kt

package com.example.musicplayer

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log

class MusicService : Service() {
    private var currentTrack = 0 // 当前播放曲目

    // Stub 实现,处理客户端的调用
    private val binder = object : IMusicService.Stub() {
        override fun play() {
            Log.d("MusicService", "Playing track $currentTrack")
            // 播放音乐逻辑
        }

        override fun pause() {
            Log.d("MusicService", "Pausing track $currentTrack")
            // 暂停音乐逻辑
        }

        override fun stop() {
            Log.d("MusicService", "Stopping music playback")
            // 停止音乐逻辑
        }

        override fun nextTrack() {
            currentTrack++
            Log.d("MusicService", "Switching to track $currentTrack")
            // 切换到下一首
        }

        override fun previousTrack() {
            currentTrack = if (currentTrack > 0) currentTrack - 1 else 0
            Log.d("MusicService", "Switching to track $currentTrack")
            // 切换到上一首
        }

        override fun getCurrentTrack(): Int {
            Log.d("MusicService", "Current track is $currentTrack")
            return currentTrack
        }
    }

    override fun onBind(intent: Intent?): IBinder {
        return binder // 返回 AIDL Stub 实现
    }
}

客户端绑定服务

客户端通过 ServiceConnection 与服务端通信,调用 AIDL 接口实现音乐控制。

MainActivity.kt

package com.example.musicplayer

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    private var musicService: IMusicService? = null

    // 定义 ServiceConnection
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            musicService = IMusicService.Stub.asInterface(service)
            Log.d("MainActivity", "Service connected!")
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            musicService = null
            Log.d("MainActivity", "Service disconnected!")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 绑定服务
        val intent = Intent(this, MusicService::class.java)
        bindService(intent, connection, Context.BIND_AUTO_CREATE)

        // 示例:调用服务端方法
        findViewById<Button>(R.id.playButton).setOnClickListener {
            musicService?.play()
        }

        findViewById<Button>(R.id.pauseButton).setOnClickListener {
            musicService?.pause()
        }

        findViewById<Button>(R.id.nextButton).setOnClickListener {
            musicService?.nextTrack()
        }

        findViewById<Button>(R.id.prevButton).setOnClickListener {
            musicService?.previousTrack()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        unbindService(connection) // 解绑服务
    }
}

运行结果
  1. 点击播放按钮,日志显示:Playing track 0,音乐开始播放。
  2. 点击下一首按钮,日志显示:Switching to track 1,切换到下一曲目。
  3. 点击暂停按钮,日志显示:Pausing track 1,音乐暂停。

项目亮点
  1. 封装性:通过 AIDL 将音乐控制逻辑完全封装在服务端,客户端只需调用接口即可。
  2. 扩展性:可轻松添加新功能(如调整音量、获取播放列表等)。
  3. 解耦性:UI 和播放逻辑独立运行,互不干扰,提高了稳定性和维护性。

案例 2:远程计算服务

背景描述
在某些场景中,需要将复杂的计算逻辑封装在服务端,通过客户端调用。远程计算服务是一种典型应用,例如计算矩阵的加法、乘法等操作,将计算任务分配到后端服务,客户端仅需获取结果。


AIDL 文件定义接口

创建 AIDL 文件 ICalculationService.aidl,定义计算服务的接口。
路径:src/main/aidl/com/example/calcservice/ICalculationService.aidl

内容如下:

package com.example.calcservice;

// 计算服务接口
interface ICalculationService {
    int add(int a, int b);         // 加法
    int multiply(int a, int b);   // 乘法
    String calculateEquation(String equation); // 计算表达式(例如 "3+5*2")
}

服务端实现

实现服务端的 AIDL 接口逻辑,提供远程计算功能。

CalculationService.kt

package com.example.calcservice

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log

class CalculationService : Service() {

    private val binder = object : ICalculationService.Stub() {
        override fun add(a: Int, b: Int): Int {
            val result = a + b
            Log.d("CalculationService", "Adding $a + $b = $result")
            return result
        }

        override fun multiply(a: Int, b: Int): Int {
            val result = a * b
            Log.d("CalculationService", "Multiplying $a * $b = $result")
            return result
        }

        override fun calculateEquation(equation: String): String {
            return try {
                val result = evaluateEquation(equation)
                Log.d("CalculationService", "Equation '$equation' = $result")
                result.toString()
            } catch (e: Exception) {
                Log.e("CalculationService", "Error evaluating equation: $equation", e)
                "Error: Invalid equation"
            }
        }

        private fun evaluateEquation(equation: String): Double {
            // 这里可以使用第三方库或自定义解析器计算表达式
            return ScriptEngineManager().getEngineByName("JavaScript").eval(equation) as Double
        }
    }

    override fun onBind(intent: Intent?): IBinder {
        return binder
    }
}

客户端调用

客户端绑定服务,并调用远程计算接口。

MainActivity.kt

package com.example.calcservice

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    private var calcService: ICalculationService? = null

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            calcService = ICalculationService.Stub.asInterface(service)
            Log.d("MainActivity", "Service connected!")
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            calcService = null
            Log.d("MainActivity", "Service disconnected!")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 绑定服务
        val intent = Intent(this, CalculationService::class.java)
        bindService(intent, connection, Context.BIND_AUTO_CREATE)

        // 调用服务
        findViewById<Button>(R.id.calculateButton).setOnClickListener {
            val result = calcService?.add(5, 10)
            Toast.makeText(this, "Result: $result", Toast.LENGTH_SHORT).show()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        unbindService(connection)
    }
}

运行效果
  1. 用户在客户端输入计算任务,例如 5 + 103 * 7
  2. 服务端计算后返回结果,客户端显示 Result: 15Result: 21
  3. 服务端日志显示计算过程,便于调试。

案例 3:智能家居设备管理系统

背景描述
在智能家居场景中,需要通过客户端管理多台设备(如智能灯、空调、摄像头等),设备的管理逻辑由后端服务统一处理,通过 AIDL 提供接口,客户端仅需调用接口实现设备控制。


AIDL 文件定义接口

创建 AIDL 文件 IHomeDeviceService.aidl,定义设备管理服务接口。
路径:src/main/aidl/com/example/smarthome/IHomeDeviceService.aidl

内容如下:

package com.example.smarthome;

// 智能家居设备服务接口
interface IHomeDeviceService {
    void turnOnDevice(String deviceId);    // 打开设备
    void turnOffDevice(String deviceId);   // 关闭设备
    String getDeviceStatus(String deviceId); // 获取设备状态
    List<String> getConnectedDevices();     // 获取已连接设备列表
}

服务端实现

HomeDeviceService.kt

package com.example.smarthome

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log

class HomeDeviceService : Service() {

    private val devices = mutableMapOf(
        "device_1" to "off",
        "device_2" to "on",
        "device_3" to "off"
    )

    private val binder = object : IHomeDeviceService.Stub() {
        override fun turnOnDevice(deviceId: String) {
            devices[deviceId] = "on"
            Log.d("HomeDeviceService", "Device $deviceId turned on.")
        }

        override fun turnOffDevice(deviceId: String) {
            devices[deviceId] = "off"
            Log.d("HomeDeviceService", "Device $deviceId turned off.")
        }

        override fun getDeviceStatus(deviceId: String): String {
            return devices[deviceId] ?: "unknown"
        }

        override fun getConnectedDevices(): List<String> {
            return devices.keys.toList()
        }
    }

    override fun onBind(intent: Intent?): IBinder {
        return binder
    }
}

客户端调用

客户端通过 ServiceConnection 调用设备服务。

MainActivity.kt

package com.example.smarthome

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    private var homeDeviceService: IHomeDeviceService? = null

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            homeDeviceService = IHomeDeviceService.Stub.asInterface(service)
            Log.d("MainActivity", "HomeDeviceService connected!")
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            homeDeviceService = null
            Log.d("MainActivity", "HomeDeviceService disconnected!")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 绑定服务
        val intent = Intent(this, HomeDeviceService::class.java)
        bindService(intent, connection, Context.BIND_AUTO_CREATE)

        // 示例:调用服务端方法
        val connectedDevices = homeDeviceService?.getConnectedDevices()
        Log.d("MainActivity", "Connected devices: $connectedDevices")
    }

    override fun onDestroy() {
        super.onDestroy()
        unbindService(connection)
    }
}

运行效果
  1. 客户端可以控制设备开关,显示设备状态。
  2. 日志输出设备操作记录。
  3. 客户端动态获取已连接设备列表,实时更新状态。

这三个案例展示了 AIDL 在不同场景下的应用,实现了从播放控制、计算服务到设备管理的跨进程通信功能。

五、问题解决:容易踩的坑

  1. 数据序列化问题:AIDL 支持基础数据类型和 Parcelable,自定义类需实现 Parcelable 接口。
  2. 线程问题:服务端运行在 Binder 线程池,避免耗时操作阻塞。
  3. 权限问题:确保服务端在 manifest 文件中正确声明权限。

六、特点

优点
  • 简化 IPC 开发,支持多种数据类型。
  • 高效的进程间通信机制。
缺点
  • 学习曲线稍高,代码生成不够直观。

七、性能

AIDL 基于 Binder,性能优越,适用于低延迟高频通信,但对复杂对象的序列化会增加 CPU 和内存开销。


八、Jetpack

随着 Android 系统的迭代,AIDL 有望与 Jetpack 组件更好地结合,甚至支持更多高级功能如动态权限和自动序列化。


九、参考资料

以下是撰写本文时参考的详细资料,涵盖了官方文档、书籍以及网络资源,确保内容的技术性和权威性:

官方文档

  1. Android Developers 文档

    • AIDL 官方指南
      官方文档对 AIDL 的定义、使用方法以及最佳实践进行了详尽的说明,提供了跨进程通信的标准实现方案。
    • IPC 机制简介
      该文档重点介绍了 Android 平台中进程间通信的多种方式,包括 Messenger 和 AIDL 的对比。
  2. Java 官方文档

    • Java RMI
      对比了解 Java RMI(远程方法调用)与 Android AIDL 的差异,为实现跨进程通信提供了更广泛的背景知识。

书籍

  1. 《Android开发艺术探索》
    作者:任玉刚

    • 第 5 章详细解析了 Android 的 IPC 机制,其中对 AIDL 的使用和代码案例有全面解读,是学习 AIDL 的优质资源。
  2. 《深入理解 Android 卷 I》
    作者:邓凡平

    • 该书从 Android 底层原理出发,剖析了 AIDL 的实现机制,尤其是 Binder 和 ServiceConnection 的内部逻辑。
  3. 《Effective Java》
    作者:Joshua Bloch

    • 尽管是 Java 的经典书籍,但其中的设计原则和编程技巧对编写高质量的 AIDL 接口具有指导意义。

技术博客与社区资源

  1. CSDN 博客

    • 文章标题:《Android AIDL 从入门到精通》
      链接:CSDN Android AIDL 教程
      作者分享了 AIDL 的实际使用场景和问题解决方法。
  2. GitHub 项目案例

    • 项目名称:Android IPC Demo
      仓库地址:GitHub - IPC Examples
      包含了 AIDL 的完整项目实现代码,包括服务端与客户端的交互逻辑。
  3. Stack Overflow 问答

    • 主题:How to implement AIDL in Android?
      链接:Stack Overflow
      社区开发者分享了具体的 AIDL 实现细节以及常见问题的解决办法。

国内外学术论文

  1. 《Binder IPC Mechanism in Android》
    发表期刊:IEEE Xplore

    • 论文重点分析了 Android 的 Binder 通信机制,这是理解 AIDL 工作原理的核心背景知识。
  2. 《Mobile OS Security: IPC Mechanisms in Android》
    作者:Andreas Peter等

    • 探讨了 Android IPC 的安全性,尤其是 AIDL 的权限控制与潜在风险,为实际开发中的安全设计提供了参考。

工具和框架

  1. Android Studio

    • 开发环境使用最新的 Android Studio,结合 AIDL 文件自动生成代码功能,提高了项目开发效率。
    • 官方下载地址:Android Studio
  2. 第三方库与工具

    • Gson: 用于序列化复杂对象,便于通过 AIDL 传递自定义数据类型。
      官网:Gson GitHub

视频教程

  1. YouTube: Android Developers Channel
    视频标题:Understanding AIDL and IPC
    链接:YouTube AIDL

    • 官方视频教程通过实际案例演示了 AIDL 的创建、绑定和使用过程。
  2. 慕课网课程
    课程标题:Android 高级开发技巧

    • 课程中包含完整的 AIDL 实战模块,适合进阶开发者学习。

欢迎关注 GongZhongHao:码农的乌托邦,程序员的精神家园!

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

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

相关文章

string类的常用接口及模拟实现

目录 基础知识 常用接口 1>常见构造 2>容量操作 3>访问及遍历操作 1.迭代器 2.反向迭代器 3.范围for 4.auto 4>修改操作 5>非成员函数 其它接口 模拟实现 string.h string.cpp swap() 基础知识 string是一个管理字符的类&#xff0c;定义在std命…

Linux——多线程的控制

Linux——线程的慨念及控制-CSDN博客 文章目录 目录 文章目录 前言 一、线程函数的认识 1、基本函数的回顾 1、线程的创建pthread_create 2、线程阻塞pthread_join 3、线程退出pthread_exit 2、线程的分离pthread_detach 3、互斥锁初始化函数&#xff1a;pthread_mutex_init 4、…

计算机网络 (49)网络安全问题概述

前言 计算机网络安全问题是一个复杂且多维的领域&#xff0c;它涉及到网络系统的硬件、软件以及数据的安全保护&#xff0c;确保这些元素不因偶然的或恶意的原因而遭到破坏、更改或泄露。 一、计算机网络安全的定义 计算机网络安全是指利用网络管理控制和技术措施&#xff0c;保…

TCP状态转移图详解

状态 描述 LISTEN represents waiting for a connection request from any remote TCP and port. SYN-SENT represents waiting for a matching connection request after having sent a connection request. SYN-RECEIVED represents waiting for a confirming connect…

VUE学习笔记(入门)5__vue指令v-html

v-html是用来解析字符串标签 示例 <!doctype html> <html lang"en"> <head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Document<…

【论文投稿】探秘计算机视觉算法:开启智能视觉新时代

目录 引言 一、计算机视觉算法基石&#xff1a;图像基础与预处理 二、特征提取&#xff1a;视觉信息的精华萃取 三、目标检测&#xff1a;从图像中精准定位目标 四、图像分类&#xff1a;识别图像所属类别 五、语义分割&#xff1a;理解图像的像素级语义 六、计算机视觉…

【Java数据结构】Java对象的比较

元素的比较 基本类型比较 在Java中基本类型比较可以直接比较大小 &#xff0c;返回一个布尔类型&#xff08;true或者false&#xff09;。 int a 10; int b 20; System.out.println(a>b); System.out.println(ab); System.out.println(a<b);对象比较的问题 对象的比…

《自动驾驶与机器人中的SLAM技术》ch8:基于预积分和图优化的紧耦合 LIO 系统

和组合导航一样&#xff0c;也可以通过预积分 IMU 因子加上雷达残差来实现基于预积分和图优化的紧耦合 LIO 系统。一些现代的 Lidar SLAM 系统也采用了这种方式。相比滤波器方法来说&#xff0c;预积分因子可以更方便地整合到现有的优化框架中&#xff0c;从开发到实现都更为便…

Ubuntu 24.04 LTS 更改软件源

Ubuntu 24.04 LTS 修改软件源

【2024年度技术总结】Unity 游戏开发的深度探索与实践

文章目录 前言一、Unity 游戏开发的技术深度总结1、C# 编程基础2、Unity 基础入门3、Unity 实战技巧4、Unity 小技巧分享 二、技术工具与平台的年度使用心得1、学习资源的选择2、开发环境配置3、测试与调试工具 三、技术项目实战经验与成果展示1、【制作100个Unity游戏】专栏2、…

ingress-nginx代理tcp使其能外部访问mysql

一、helm部署mysql主从复制 helm repo add bitnami https://charts.bitnami.com/bitnami helm repo updatehelm pull bitnami/mysql 解压后编辑values.yaml文件&#xff0c;修改如下&#xff08;storageclass已设置默认类&#xff09; 117 ## param architecture MySQL archit…

Top期刊算法!RIME-CNN-BiLSTM-Attention系列四模型多变量时序预测

Top期刊算法&#xff01;RIME-CNN-BiLSTM-Attention系列四模型多变量时序预测 目录 Top期刊算法&#xff01;RIME-CNN-BiLSTM-Attention系列四模型多变量时序预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 基于RIME-CNN-BiLSTM-Attention、CNN-BiLSTM-Attention、R…

游戏引擎学习第84天

仓库:https://gitee.com/mrxiao_com/2d_game_2 我们正在试图弄清楚如何完成我们的世界构建 上周做了一些偏离计划的工作&#xff0c;开发了一个小型的背景位图合成工具&#xff0c;这个工具做得还不错&#xff0c;虽然是临时拼凑的&#xff0c;但验证了背景构建的思路。这个过…

搭建一个基于Spring Boot的数码分享网站

搭建一个基于Spring Boot的数码分享网站可以涵盖多个功能模块&#xff0c;例如用户管理、数码产品分享、评论、点赞、收藏、搜索等。以下是一个简化的步骤指南&#xff0c;帮助你快速搭建一个基础的数码分享平台。 — 1. 项目初始化 使用 Spring Initializr 生成一个Spring …

31、【OS】【Nuttx】OSTest分析(1):stdio测试(一)

背景 接上篇wiki 30、【OS】【Nuttx】构建脚本优化&#xff0c;引入待构建项目参数 最小系统分析完后&#xff0c;下一个能够更全面了解Nuttx的Demo&#xff0c;当然选择OSTest&#xff0c;里面有大量关于OS的测试用例&#xff0c;方便对Nuttx的整体功能有个把握。 stdio_tes…

Spring WebFlux

文章目录 一、概述1、Spring体系定位2、Spring MVC和WebFlux差异 二、入门1、依赖2、ReactorHttpHandlerAdapter&#xff08;main启动&#xff09;3、DispatcherHandler&#xff08;SpringWebFlux启动&#xff09;4、WebFilter 三、DispatcherHandler理解1、handle 前置知识&am…

基于SSM的自助购药小程序设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

Oracle graph 图数据库体验-安装篇

服务端安装 环境准备 安装数据库 DOCKER 安装23AI FREE &#xff0c;参考&#xff1a; https://container-registry.oracle.com/ords/f?p113:4:111381387896144:::4:P4_REPOSITORY,AI_REPOSITORY,AI_REPOSITORY_NAME,P4_REPOSITORY_NAME,P4_EULA_ID,P4_BUSINESS_AREA_ID:1…

CSS 的基础知识及应用

前言 CSS&#xff08;层叠样式表&#xff09;是网页设计和开发中不可或缺的一部分。它用于描述网页的视觉表现&#xff0c;使页面不仅实现功能&#xff0c;还能提供吸引人的用户体验。本文将介绍 CSS 的基本概念、语法、选择器及其在提升网页美观性方面的重要性。 什么是 CSS&…

C语言之装甲车库车辆动态监控辅助记录系统

&#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 C语言之装甲车库车辆动态监控辅助记录系统 目录 一、前言 1.1 &#xff08;一&#xff09;…