嵌入式安卓快速入门,谢双元最新笔记,(三天学会安卓底层开发课程)

一个人想学一样技术的最好的方式是有人带着实战,实战的思路是先明确自己要做什么,步聚是什么,这样才学得快,而不是找出一概念在那儿没有目的学

以下这个例子,就是我们先搞了一个灯,然后编写这个灯的驱动,到HAL  层  到,用户层调用的完整示例。






Android HAL 层 LED 灯控制学习文档
,从设备树 → 内核驱动 → HAL 层,逐层打通。按照你的讲课习惯:先讲业务逻辑和目标 → 中文伪代码 → 完整带注释代码 → 关键总结

Android HAL 层完整学习示例 —— LED 灯控制

一、整体业务逻辑(先搞清楚我们要干什么)

plain
 
┌─────────────────────────────────────────────────────────────┐
│  应用层 (App / SystemUI)                                     │
│  Java代码:调用系统服务控制LED                                │
├─────────────────────────────────────────────────────────────┤
│  Framework层 (Java + JNI)                                    │
│  SystemServer中的LED服务,通过JNI调用HAL                      │
├─────────────────────────────────────────────────────────────┤
│  HAL层 (C/C++)  ←── 我们今天重点写这层                       │
│  封装内核sysfs接口,暴露标准硬件模块给Framework                │
├─────────────────────────────────────────────────────────────┤
│  内核驱动层 (C)  ←── 也要写                                   │
│  操作GPIO寄存器,提供 /sys/class/leds/my_led/brightness      │
├─────────────────────────────────────────────────────────────┤
│  设备树 (DTS)  ←── 描述硬件                                   │
│  告诉内核:LED接在哪个GPIO上                                  │
├─────────────────────────────────────────────────────────────┤
│  硬件层                                                      │
│  LED灯 ── 限流电阻 ── GPIO引脚                                │
└─────────────────────────────────────────────────────────────┘
 

每一步的目标

表格
 
 
步骤 目标 通俗解释
Step 1 设备树 告诉内核:LED接在哪个GPIO上 给新来的电工一张电路图,告诉他灯开关在哪
Step 2 内核驱动 把GPIO操作封装成文件(sysfs) 电工装个智能开关,你可以用手机APP控制
Step 3 HAL层 把文件操作封装成Android标准模块 给Android系统做一个统一的"开关面板"
Step 4 构建配置 编译安装,让系统能找到这个模块 把面板装到墙上,贴上标签

二、中文伪代码(理解思路,不用记语法)

Step 1: 设备树伪代码

plain
 
设备树节点 "my_led" {
    兼容驱动 = "mycompany,led-gpio";          // 驱动靠这个名字找到这个设备
    标签 = "status_led";                       // LED的名字
    GPIO引脚 = <&gpio1 15 GPIO_ACTIVE_HIGH>;   // 接在gpio1的第15脚,高电平亮
    默认状态 = "off";                          // 默认关灯
}
 

Step 2: 内核驱动伪代码

plain
 
驱动初始化() {
    从设备树读取GPIO引脚号
    申请GPIO使用权
    设置GPIO方向为输出
    注册LED设备到sysfs(创建 /sys/class/leds/my_led/brightness)
}

LED亮度设置(亮度值) {
    如果 亮度值 > 0 {
        GPIO输出高电平   // 灯亮
    } 否则 {
        GPIO输出低电平   // 灯灭
    }
}
 

Step 3: HAL层伪代码

plain
 
定义LED硬件模块 {
    模块ID = "led"
    模块方法 = { 打开设备: led设备打开 }
}

LED设备打开() {
    分配设备结构体内存
    填充设备接口 = { 开灯: 开灯操作, 关灯: 关灯操作 }
    返回设备指针给Framework
}

开灯操作() {
    打开文件 "/sys/class/leds/my_led/brightness"
    写入 "1"
    关闭文件
}

关灯操作() {
    打开文件 "/sys/class/leds/my_led/brightness"
    写入 "0"
    关闭文件
}
 

三、完整示例代码(带详细注释)

Step 1: 设备树(Device Tree)

文件路径arch/arm64/boot/dts/rockchip/rk3568-myboard.dtsi(根据你的平台调整)
dts
 
/ {
    // LED设备节点,放在根节点下或合适的父节点下
    leds {
        compatible = "gpio-leds";            // 使用内核自带的gpio-leds驱动框架
        
        // 我们的LED灯
        my_led: led-status {
            label = "myboard:status";        // LED的名字,会出现在/sys/class/leds/下
            gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_HIGH>;  // GPIO1_A7,高电平亮
            linux,default-trigger = "none";  // 默认不绑定任何触发器
            default-state = "off";           // 默认状态:关闭
            
            // 自定义属性,我们自己的驱动可以用
            compatible = "mycompany,led-gpio";
        };
    };
};
 
注释说明:
  • compatible = "gpio-leds":使用Linux内核自带的GPIO LED驱动框架,省得自己写完整驱动
  • gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_HIGH>:指定GPIO引脚,RK平台用RK_PA7这种宏
  • label:决定sysfs路径名,这里是 /sys/class/leds/myboard:status/brightness
如果你要自己写驱动(不用gpio-leds框架),compatible写你自己的名字,如 "mycompany,led-gpio"

Step 2: 内核驱动(Kernel Driver)

这里演示自己写驱动的方式(不用gpio-leds框架),更能理解原理。
文件路径drivers/leds/led-mygpio.c
c
 
/*
 * 自定义LED GPIO驱动
 * 功能:通过sysfs文件控制LED亮灭
 * 路径:/sys/class/leds/my_led/brightness
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of.h>           // 设备树解析
#include <linux/of_gpio.h>      // GPIO相关
#include <linux/gpio.h>         // GPIO操作
#include <linux/leds.h>         // LED子系统

// 设备私有数据结构
struct myled_data {
    struct led_classdev cdev;   // LED子系统标准结构体
    int gpio;                   // GPIO引脚号
};

// ========== 核心操作:设置LED亮度 ==========
static void myled_set(struct led_classdev *led_cdev,
                      enum led_brightness value)
{
    // 通过led_cdev反向找到我们的私有数据
    struct myled_data *priv = container_of(led_cdev, struct myled_data, cdev);
    
    // value: 0=灭, 1~255=亮(我们简单处理,非0就亮)
    if (value == LED_OFF) {
        gpio_set_value(priv->gpio, 0);   // GPIO输出低电平,灯灭
        pr_info("MYLED: LED OFF\n");
    } else {
        gpio_set_value(priv->gpio, 1);   // GPIO输出高电平,灯亮
        pr_info("MYLED: LED ON, brightness=%d\n", value);
    }
}

// ========== 驱动探测函数(设备树匹配后调用)==========
static int myled_probe(struct platform_device *pdev)
{
    struct myled_data *priv;
    struct device_node *np = pdev->dev.of_node;
    int ret;
    
    pr_info("MYLED: Driver probe start\n");
    
    // 1. 分配私有数据结构体内存
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
    
    // 2. 从设备树获取GPIO("led-gpios"属性或gpios属性)
    priv->gpio = of_get_named_gpio(np, "led-gpios", 0);
    if (priv->gpio < 0) {
        // 尝试"gpios"属性
        priv->gpio = of_get_named_gpio(np, "gpios", 0);
    }
    if (priv->gpio < 0) {
        dev_err(&pdev->dev, "Failed to get GPIO from device tree\n");
        return priv->gpio;
    }
    
    // 3. 申请GPIO使用权
    ret = devm_gpio_request(&pdev->dev, priv->gpio, "my_led");
    if (ret) {
        dev_err(&pdev->dev, "Failed to request GPIO %d\n", priv->gpio);
        return ret;
    }
    
    // 4. 设置GPIO方向为输出,默认低电平(灯灭)
    ret = gpio_direction_output(priv->gpio, 0);
    if (ret) {
        dev_err(&pdev->dev, "Failed to set GPIO as output\n");
        return ret;
    }
    
    // 5. 初始化LED子系统结构体
    priv->cdev.name = "my_led";             // sysfs中显示的名字
    priv->cdev.brightness_set = myled_set;  // 亮度设置回调函数
    priv->cdev.max_brightness = 1;          // 最大亮度值(0或1,开关型LED)
    priv->cdev.brightness = LED_OFF;        // 初始状态:关闭
    
    // 6. 注册到LED子系统(自动创建/sys/class/leds/my_led/brightness)
    ret = led_classdev_register(&pdev->dev, &priv->cdev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register LED classdev\n");
        return ret;
    }
    
    // 7. 把私有数据保存到platform_device,方便后面使用
    platform_set_drvdata(pdev, priv);
    
    pr_info("MYLED: LED driver registered, GPIO=%d\n", priv->gpio);
    return 0;
}

// ========== 驱动移除函数 ==========
static int myled_remove(struct platform_device *pdev)
{
    struct myled_data *priv = platform_get_drvdata(pdev);
    
    // 注销LED设备(自动删除sysfs节点)
    led_classdev_unregister(&priv->cdev);
    
    // GPIO在devm_gpio_request时已标记为自动释放,无需手动释放
    
    pr_info("MYLED: LED driver removed\n");
    return 0;
}

// ========== 设备树匹配表 ==========
static const struct of_device_id myled_of_match[] = {
    { .compatible = "mycompany,led-gpio", },   // 和设备树中的compatible对应
    { },
};
MODULE_DEVICE_TABLE(of, myled_of_match);

// ========== platform驱动结构体 ==========
static struct platform_driver myled_driver = {
    .probe  = myled_probe,      // 设备匹配成功时调用
    .remove = myled_remove,     // 设备移除时调用
    .driver = {
        .name = "my-led-gpio",  // 驱动名字
        .of_match_table = myled_of_match,  // 设备树匹配表
    },
};

// ========== 模块入口 ==========
module_platform_driver(myled_driver);

MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple LED GPIO Driver");
MODULE_LICENSE("GPL");
 
Kconfig 和 Makefile 配置:
drivers/leds/Kconfig 中添加:
kconfig
 
config LEDS_MYGPIO
    tristate "LED support for My GPIO LED"
    depends on LEDS_CLASS
    depends on GPIOLIB
    help
      This option enables support for simple GPIO-connected LEDs
      using custom driver.
 
drivers/leds/Makefile 中添加:
makefile
 
obj-$(CONFIG_LEDS_MYGPIO)   += led-mygpio.o
 
驱动层关键总结:
  • led_classdev_register() 会自动创建 /sys/class/leds/my_led/brightness
  • 用户空间写 1 到 brightness → 调用 myled_set()gpio_set_value(1) → 灯亮
  • 用户空间写 0 到 brightness → 调用 myled_set()gpio_set_value(0) → 灯灭

Step 3: HAL层代码(传统 libhardware 方式)

Android HAL传统方式使用 hw_module_thw_device_t 结构体,编译成 .so 库。

3.1 HAL 头文件(定义接口)

文件路径hardware/libhardware/include/hardware/led_hal.h
c
 
/*
 * LED HAL 接口头文件
 * 定义了Framework和HAL之间的标准接口
 */

#ifndef ANDROID_LED_HAL_H
#define ANDROID_LED_HAL_H

#include <hardware/hardware.h>

__BEGIN_DECLS

// ========== 模块ID ==========
// Android通过这个名字找到对应的.so库
// 系统会在 /system/lib/hw/led.default.so 或 /vendor/lib/hw/led.xxx.so 中查找
#define LED_HARDWARE_MODULE_ID  "led"

// ========== 设备结构体 ==========
// 继承自 hw_device_t,添加我们自己的LED操作接口
struct led_device_t {
    struct hw_device_t common;      // 必须放在第一个!HAL标准结构体
    
    // LED操作接口(函数指针)
    int (*set_on)(struct led_device_t* dev);    // 开灯
    int (*set_off)(struct led_device_t* dev);   // 关灯
    int (*get_status)(struct led_device_t* dev); // 获取状态(返回0=关,1=开)
};

// ========== 模块结构体 ==========
// 继承自 hw_module_t
struct led_module_t {
    struct hw_module_t common;      // HAL标准模块结构体
};

__END_DECLS

#endif // ANDROID_LED_HAL_H
 

3.2 HAL 实现文件

文件路径hardware/libhardware/modules/led/led.c
c
 
/*
 * LED HAL 实现
 * 功能:封装内核sysfs接口,提供标准HAL模块
 * 编译后生成:led.default.so
 */

#define LOG_TAG "LedHAL"

#include <hardware/hardware.h>
#include <hardware/led_hal.h>
#include <cutils/log.h>         // Android日志:ALOGI, ALOGE等
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>

// ========== 内核sysfs节点路径 ==========
// 对应驱动创建的 /sys/class/leds/my_led/brightness
#define LED_SYSFS_PATH  "/sys/class/leds/my_led/brightness"

// ========== 内部辅助函数:写sysfs ==========
static int write_sysfs(const char *path, const char *value)
{
    int fd;
    int ret;
    
    // 以只写方式打开sysfs文件
    fd = open(path, O_WRONLY);
    if (fd < 0) {
        ALOGE("LED HAL: Failed to open %s, errno=%d", path, errno);
        return -1;
    }
    
    // 写入数据("0"或"1")
    ret = write(fd, value, strlen(value));
    if (ret < 0) {
        ALOGE("LED HAL: Failed to write %s to %s, errno=%d", value, path, errno);
        close(fd);
        return -1;
    }
    
    close(fd);
    ALOGI("LED HAL: Write %s to %s success", value, path);
    return 0;
}

// ========== 内部辅助函数:读sysfs ==========
static int read_sysfs(const char *path, char *buf, size_t size)
{
    int fd;
    int ret;
    
    fd = open(path, O_RDONLY);
    if (fd < 0) {
        ALOGE("LED HAL: Failed to open %s", path);
        return -1;
    }
    
    ret = read(fd, buf, size - 1);
    if (ret < 0) {
        ALOGE("LED HAL: Failed to read %s", path);
        close(fd);
        return -1;
    }
    
    buf[ret] = '\0';    // 确保字符串结束
    close(fd);
    return 0;
}

// ========== HAL接口实现:开灯 ==========
static int led_set_on(struct led_device_t* dev)
{
    (void)dev;  // 暂时没用到dev参数,消除编译警告
    ALOGI("LED HAL: set_on() called");
    return write_sysfs(LED_SYSFS_PATH, "1");
}

// ========== HAL接口实现:关灯 ==========
static int led_set_off(struct led_device_t* dev)
{
    (void)dev;
    ALOGI("LED HAL: set_off() called");
    return write_sysfs(LED_SYSFS_PATH, "0");
}

// ========== HAL接口实现:获取状态 ==========
static int led_get_status(struct led_device_t* dev)
{
    char buf[4];
    int status = 0;
    
    (void)dev;
    
    if (read_sysfs(LED_SYSFS_PATH, buf, sizeof(buf)) == 0) {
        status = (buf[0] == '1') ? 1 : 0;
    }
    
    ALOGI("LED HAL: get_status() returned %d", status);
    return status;
}

// ========== 设备关闭函数 ==========
static int led_close(struct hw_device_t* device)
{
    struct led_device_t* led_dev = (struct led_device_t*)device;
    
    if (led_dev) {
        free(led_dev);
        ALOGI("LED HAL: device closed");
    }
    return 0;
}

// ========== 模块打开函数(核心!Framework通过它获取设备)==========
static int led_device_open(const struct hw_module_t* module,
                           const char* id,
                           struct hw_device_t** device)
{
    struct led_device_t *dev;
    
    ALOGI("LED HAL: led_device_open() called, id=%s", id);
    
    // 1. 分配设备结构体内存
    dev = (struct led_device_t*)malloc(sizeof(*dev));
    if (!dev) {
        ALOGE("LED HAL: Failed to allocate memory");
        return -ENOMEM;
    }
    memset(dev, 0, sizeof(*dev));
    
    // 2. 填充 hw_device_t 标准字段(必须!)
    dev->common.tag = HARDWARE_DEVICE_TAG;          // 设备标签
    dev->common.version = 0;                         // 版本号
    dev->common.module = (struct hw_module_t*)module; // 指向所属模块
    dev->common.close = led_close;                   // 关闭回调
    
    // 3. 填充我们自己的LED操作接口
    dev->set_on = led_set_on;
    dev->set_off = led_set_off;
    dev->get_status = led_get_status;
    
    // 4. 通过出参返回设备指针给Framework
    *device = &dev->common;
    
    ALOGI("LED HAL: Device opened successfully");
    return 0;
}

// ========== 模块方法结构体 ==========
// 只有一个方法:open,用于打开设备
static struct hw_module_methods_t led_module_methods = {
    .open = led_device_open,
};

// ========== 模块信息结构体(重要!必须叫 HAL_MODULE_INFO_SYM)==========
// 这是HAL模块的"入口",Android通过这个名字找到模块
const struct led_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,         // 模块标签
        .module_api_version = HARDWARE_MODULE_API_VERSION(1, 0),  // API版本
        .hal_api_version = HARDWARE_HAL_API_VERSION,              // HAL版本
        .id = LED_HARDWARE_MODULE_ID,       // 模块ID,必须和头文件一致
        .name = "My LED HAL",               // 模块名字(给人看的)
        .author = "Your Company",           // 作者
        .methods = &led_module_methods,     // 模块方法表
    },
};
 

3.3 HAL 构建配置(Android.bp)

文件路径hardware/libhardware/modules/led/Android.bp
bp
 
// 编译LED HAL模块为动态库
cc_library_shared {
    name: "led.default",         // 编译生成 led.default.so
                                  // Android查找规则:led.<variant>.so
                                  // 默认variant是"default"
    relative_install_path: "hw", // 安装到 /system/lib/hw/ 或 /vendor/lib/hw/
    proprietary: true,           // 标记为厂商专有(放vendor分区)
    
    srcs: ["led.c"],             // 源文件
    
    shared_libs: [
        "liblog",                // Android日志库(ALOGI等)
    ],
    
    // 头文件搜索路径
    include_dirs: [
        "hardware/libhardware/include",
    ],
}
 
注意:如果是AOSP 8.0+,HAL通常放在vendor分区,路径为 vendor/<company>/hardware/interfaces/...,使用HIDL或AIDL。上面的传统方式适用于学习和理解HAL原理。

Step 4: Framework层调用示例(JNI + Java)

为了让整个链路完整,这里给出Framework层如何调用HAL。

4.1 JNI 层(C++)

文件路径frameworks/base/services/core/jni/com_android_server_led_LedService.cpp
cpp
 
/*
 * LED Service JNI
 * 连接Java层和HAL层
 */

#define LOG_TAG "LedServiceJNI"

#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/led_hal.h>

// 全局HAL设备指针
static struct led_device_t* sLedDevice = NULL;

// ========== 初始化:加载HAL模块 ==========
static jint led_init(JNIEnv* env, jobject clazz)
{
    (void)clazz;
    
    const hw_module_t* module;
    int ret;
    
    // 1. 通过hw_get_module加载HAL模块(系统会自动找led.default.so)
    ret = hw_get_module(LED_HARDWARE_MODULE_ID, &module);
    if (ret != 0) {
        ALOGE("LedServiceJNI: hw_get_module failed, ret=%d", ret);
        return -1;
    }
    
    ALOGI("LedServiceJNI: HAL module loaded, name=%s", module->name);
    
    // 2. 调用模块的open方法打开设备
    ret = module->methods->open(module, LED_HARDWARE_MODULE_ID, 
                                (struct hw_device_t**)&sLedDevice);
    if (ret != 0) {
        ALOGE("LedServiceJNI: Failed to open LED device");
        return -1;
    }
    
    ALOGI("LedServiceJNI: LED device opened successfully");
    return 0;
}

// ========== JNI方法:开灯 ==========
static void led_setOn(JNIEnv* env, jobject clazz)
{
    (void)env;
    (void)clazz;
    
    if (sLedDevice && sLedDevice->set_on) {
        sLedDevice->set_on(sLedDevice);
    }
}

// ========== JNI方法:关灯 ==========
static void led_setOff(JNIEnv* env, jobject clazz)
{
    (void)env;
    (void)clazz;
    
    if (sLedDevice && sLedDevice->set_off) {
        sLedDevice->set_off(sLedDevice);
    }
}

// ========== JNI方法:获取状态 ==========
static jint led_getStatus(JNIEnv* env, jobject clazz)
{
    (void)env;
    (void)clazz;
    
    if (sLedDevice && sLedDevice->get_status) {
        return sLedDevice->get_status(sLedDevice);
    }
    return -1;
}

// ========== JNI方法注册表 ==========
static const JNINativeMethod ledMethods[] = {
    {"native_init",      "()I",  (void*)led_init},
    {"native_setOn",     "()V",  (void*)led_setOn},
    {"native_setOff",    "()V",  (void*)led_setOff},
    {"native_getStatus", "()I",  (void*)led_getStatus},
};

// ========== 注册JNI方法 ==========
int register_android_server_led_LedService(JNIEnv* env)
{
    return jniRegisterNativeMethods(env, "com/android/server/led/LedService",
                                    ledMethods, NELEM(ledMethods));
}
 

4.2 Java Service

java
 
package com.android.server.led;

public class LedService {
    static {
        System.loadLibrary("android_servers");  // 加载JNI库
    }
    
    // Native方法声明(对应JNI中的实现)
    private native int native_init();
    private native void native_setOn();
    private native void native_setOff();
    private native int native_getStatus();
    
    public LedService() {
        int ret = native_init();
        if (ret != 0) {
            throw new RuntimeException("Failed to init LED HAL");
        }
    }
    
    public void turnOn() {
        native_setOn();
    }
    
    public void turnOff() {
        native_setOff();
    }
    
    public boolean isOn() {
        return native_getStatus() == 1;
    }
}
 

四、关键代码总结(背这几句就够了)

表格
 
 
层级 关键代码 作用
设备树 compatible = "mycompany,led-gpio" 驱动匹配钥匙
设备树 gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_HIGH> 告诉内核GPIO在哪
驱动 led_classdev_register() 自动创建sysfs节点
驱动 gpio_set_value(priv->gpio, 1) 控制GPIO高低电平
HAL HAL_MODULE_INFO_SYM HAL模块入口,必须这个名字
HAL hw_get_module(LED_HARDWARE_MODULE_ID, &module) Framework加载HAL
HAL module->methods->open(module, ..., &device) 打开HAL设备
HAL write(fd, "1", 1)/sys/class/leds/my_led/brightness 操作内核接口

五、调试方法

1. 检查内核驱动是否加载

bash
 
# 查看内核日志
adb shell dmesg | grep MYLED

# 查看sysfs节点是否存在
adb shell ls /sys/class/leds/my_led/
# 应该看到 brightness, max_brightness 等文件

# 手动测试
adb shell "echo 1 > /sys/class/leds/my_led/brightness"   # 开灯
adb shell "echo 0 > /sys/class/leds/my_led/brightness"   # 关灯
 

2. 检查HAL模块是否编译成功

bash
 
# 查看vendor分区是否有so文件
adb shell ls -l /vendor/lib/hw/led.default.so
# 或 /system/lib/hw/led.default.so

# 查看日志
adb logcat -s LedHAL:D
 

3. 检查Framework调用

bash
 
adb logcat -s LedServiceJNI:D
 

六、补充:新版AIDL HAL方式简介

Android 11+ 推荐使用 AIDL HAL,主要区别:
表格
 
 
对比项 传统HAL (libhardware) AIDL HAL
接口定义 C头文件 .aidl 文件
通信方式 直接函数调用 Binder IPC
代码生成 aidl 工具自动生成
位置 /vendor/lib/hw/ /vendor/bin/hw/(服务进程)
启动方式 动态加载.so init.rc启动服务
适用版本 Android 8.0之前 Android 11+
AIDL HAL核心流程:
  1. ISslight.aidl 定义接口
  2. aidl 工具生成 C++/Java 桩代码
  3. 实现 BnSslight(服务端)
  4. .rc 文件让init启动服务
  5. 配置 vintf 清单
如果你是做Android 11+的新项目,建议学习AIDL HAL;如果是学习原理,传统HAL更容易理解,如果觉得看文档看累,可以加入元歌fgxwan  抖音,注定我定期分享实操
posted @ 2026-07-28 17:09  鬼门元歌  阅读(3)  评论(0)    收藏  举报