简介面向安卓底层驱动开发者资料系统讲解GPS在Android系统中的实现原理重点剖析JNI与HAL层如何协作以及Java层通过GpsInterface调用底层硬件的完整机制从应用框架到硬件驱动逐层拆解。压缩包共5个文件体积仅9.28MB其中包含2份docx分析文档、1份rar源码包以及Android.mk编译脚本和C语言示例源码分别对应原理说明、驱动代码与构建配置参考方便按文档、源码、构建脚本三个层次对照学习。该资源已有838人学习适合作为GPS驱动开发入门、源码阅读与移植调试的参考也便于在开发中快速定位常见问题。资料从JNI函数定义、HAL接口封装到GpsLocationProvider的状态管理均有分析并结合安卓2.3车载导航HAL移植案例给出可直接研读的关键源码能帮助开发者快速掌握定位数据的获取、解析与上报流程对学习Wi-Fi、蓝牙等其他硬件驱动的底层实现同样有方法论借鉴。1. GPS Android底层驱动为什么拿到底层驱动源码也常常点不亮“GPS Android底层驱动”这个包里最值钱的部分往往不是那一堆内核宏而是 JNI 与 HAL 层那三十几行绑定逻辑。我拆过不少 GPS 方案见过太多同事卡在同一个地方串口明明有 NMEA 语句模块也有星但 Android 的 LocationManager 就是报不出来位置。这种问题十有八九出在底层驱动到上层框架之间的胶水代码上。这份资料把内核驱动、HAL 实现和 JNI 关键源码串在一起适合做定位产品、ROM 适配或者 Android 驱动开发的人。下面按我实际落地的顺序讲从硬件数据一路打到 Java 层每一步都标注了参数和坑。2. 驱动与 NMEA 解析把原始 GNSS 数据变成可用定位结果2.1 先选对框架字符设备驱动还是 serdev在 Android 设备里挂 GPS 模块最常见的物理接口是 UART也有少数走 I2C。Linux 侧选框架时有两条路一条是直接写 miscdevice 字符驱动注册 file_operations应用层用 open/read 去读另一条是用内核的 serdev 框架把设备挂在 tty 子系统下由 serdev_device 处理收发。我自己的习惯是优先用 serdev因为它在 device tree 里能直接绑 compatible和 UART 控制器驱动天然兼容还能拿到流控、波特率这些参数省掉自己维护 ring buffer 的麻烦。要是图省事写裸字符驱动后期引入电源管理时会发现睡眠功耗很难压下去。选择还牵扯到一条链路——上层 JNI 要么直接 open/dev/ttyS*读串口要么对 HAL 里已打开的 fd 操作。如果驱动侧没把设备节点暴露出来后面所有解析都无从谈起。所以不管用哪种框架第一件事是确认设备节点在 shell 下读得到。我一般会在 probe 里加一段 dev_info 日志启动后用adb shell ls -l /dev/tty*确认节点存在再去接下一层的解析逻辑这样能把“有没有设备节点”和“有没有数据”两个变量分开排查。2.2 NMEA 解析GGA 与 RMC 的关键字段GPS 模块输出的是 NMEA 0183 协议一行以$开头以\r\n结束。真正决定定位能不能用的语句是 GGA 和 RMC。GGA 里有经纬度、定位有效性、卫星数和 HDOPRMC 除了经纬度还有 UTC 时间和速度系统时间校准经常靠它。下面的解析代码只做一件事从串口缓冲区里把 GGA 语句切出来转成应用层能用的结构体。这里最容易出的问题反而是格式换算不是协议本身。/* parse_nmea.c - 简化版 NMEA GGA 解析 */ typedef struct { int valid; /* 0/1A有效V无效 */ double latitude; /* 纬度单位度 */ double longitude; /* 经度单位度 */ int satellites; /* 参与定位的卫星数 */ double hdop; /* 水平精度因子 */ } gps_fix_t; int parse_gga(const char *buf, size_t len, gps_fix_t *fix) { char *start strstr(buf, $GPGGA); if (start NULL || (size_t)(start - buf) 8 len) return -1; /* 语句头不完整直接丢弃 */ char *line strndup(start, len - (size_t)(start - buf)); char *saveptr NULL, *tok strtok_r(line, ,, saveptr); char *field[16] {0}; int n 0; while (tok ! NULL n 16) { field[n] tok; tok strtok_r(NULL, ,, saveptr); } if (n 7) goto liberr; /* 字段不足数据不可用 */ double lat_raw atof(field[3]); /* ddmm.mmmm 格式 */ double lon_raw atof(field[5]); fix-valid (field[2][0] A); fix-latitude (int)(lat_raw / 100.0) (lat_raw - (int)(lat_raw / 100.0) * 100.0) / 60.0; fix-longitude (int)(lon_raw / 100.0) (lon_raw - (int)(lon_raw / 100.0) * 100.0) / 60.0; fix-satellites atoi(field[7]); fix-hdop atof(field[8]); free(line); return 0; liberr: free(line); return -1; }这段代码有两个地方容易翻车。第一个是经纬度格式ddmm.mmmm不是十进制小数直接atof除以 100 会把分当成百分位误差最多能到 1 千米左右要按“度 整数部分 / 100分 去掉度后的小数段 / 60”来换算。第二个是$GPGGA与$GLGGA的区别GLONASS 的 GGA 开头是$GLGGA如果模块多星座全开只匹配$GPGGA会丢掉 GLONASS 的定位数据。这块建议把前缀判断做成按语句域GGA匹配不锁死 GNSS 系统 ID。说到 gps 误差很多开发者以为漂移是定位算法问题其实底层解析里埋雷更多。HDOP 读出来是 1.2但经纬度换算错了漂移照样能跑到几十公里外。所以拿到模块返回的 NMEA 后第一件事别急着看坐标先拿一颗已知位置的基准点对一下解析结果。我一般会先用 gpsd 这类工具做端到端验证确认解析层没有引入额外误差后再往上做 HAL 适配。2.3 上电时序与天线走线对定位的影响很多模块手册里都强调“GPS模块的天线走线注意事项”排在调试清单第一位。我实际踩到过的情况是有源天线馈电没送到导致串口有数据但可见卫星数一直是 0。所以驱动 probe 里除了打开串口还要保证 GPIO 供电和使能脚的时序正确。下面这段是典型的 serdev probe 简化写法。/* gps_serdev.c - 使能 GPS 模块并设置串口波特率 */ static int gps_serdev_probe(struct serdev_device *serdev) { struct gps_dev *gps serdev_device_get_drvdata(serdev); int ret; gpiod_set_value(gps-enable_gpio, 1); /* 拉高模块 EN 脚 */ msleep(200); /* 等 TCXO 起振 */ ret serdev_device_open(serdev); if (ret) return -EIO; serdev_device_set_baudrate(serdev, 9600); serdev_device_set_flow_control(serdev, false); return 0; }参数说明enable_gpio对应 devicetree 里的enable-gpios拉高后要留 100ms 以上给模块内的晶振起振拉太短会出现“间歇性出数据”的怪毛病。波特率这里写 9600 是很多国产模块的出厂默认值也有模块默认 38400拿到数据先看是乱码还是可读文本乱码优先查波特率而不是查线序这是最省时间的排查路径。serdev_device_open之前如果系统里有其他进程打开了同一个 tty 节点open 会返回-EBUSY排查时顺手adb shell ps -A | grep gps看一眼有没有残留进程。天线走线这块我犯过一个比较低级的错把陶瓷天线放在屏蔽罩旁边净空区不够导致所有卫星的 SNR 普遍低 5dB 以上。软件怎么调都无解最后只能改结构。所以硬件方案阶段就得把天线位置定好不然软件再勤奋也白搭。3. HAL 层实现gps_device_t 与 GpsInterface 的正确打开方式3.1 gps.h 里你必须搞懂的四张结构Android 的 HAL 规范定义在 hardware/libhardware/include/hardware/gps.h。上层 framework 认识两个关键对象hw_module_t和hw_device_t。GPS 的 HAL 把二者封装成gps_device_tcommon 内嵌hw_device_t还多了一个get_gps_interface函数指针。JNI 层拿到这个指针后才能获得完整的 GpsInterface进而调用 start、stop 这些操作。理解这几个结构的关系是看懂 HAL 源码的前提。结构体主要职责关键字段hw_module_t标识 HAL 模块被 hw_get_module 查找tag、module_api_version、methodsgps_device_t暴露 GPS 设备的 GpsInterfacecommon、get_gps_interfaceGpsInterface上层控制 GPS 启停的接口集合init、start、stop、inject_timeGpsCallbacksHAL 向上层上报数据的回调集合location_cb、status_cb、sv_status_cb注意 GpsCallbacks 里的location_cb是整个数据流的源头。模块每解出一帧有效的 GpsLocationHAL 就通过这个回调把定位结果往上推。很多人以为 HAL 层只是简单转发 NMEA实际上它还要承担 AGPS 数据注入、卫星状态上报、冷热启动控制这些杂活。framework 不直接和串口打交道它对 GPS 的全部认知都来自 GpsInterface 和 GpsCallbacks 这一对接口。3.2 gps_device_t 的实现套路HAL 模块的入口是hw_module_methods_t里的open。和很多人直觉相反这个 open 并不打开串口它只是返回一个gps_device_t指针给上层真正的硬件初始化发生在后续 GpsInterface 的 init 回调里。下面是我常用的实现骨架。/* gps_hw.c - HAL 模块 open 入口 */ static struct hw_module_methods_t gps_module_methods { .open gps_device_open, }; static int gps_device_open(const hw_module_t *module, const char *name, hw_device_t **device) { gps_device_t *dev (gps_device_t *)calloc(1, sizeof(gps_device_t)); dev-common.tag HARDWARE_DEVICE_TAG; dev-common.version 1; dev-common.module (hw_module_t *)module; dev-get_gps_interface get_gps_interface; /* 返回 GpsInterface */ *device (hw_device_t *)dev; return 0; }get_gps_interface的实现通常返回一个静态的GpsInterface实例里面填好 init、start、stop 这些函数指针。common.version要按硬件版本填填错在某些 Android 版本上会导致 framework 拒绝加载。这段代码里最需要注意的是name参数hw_get_module 传入的 name 对应模块文件里gps.default的default后缀如果这里校验 name 返回 -EINVAL上层会直接判定 HAL 不存在。真正打开串口或注册设备节点监听的逻辑放在 GpsInterface 的 init 回调里。init 里还要把 GpsCallbacks 保存成静态变量因为后续所有 location_cb、status_cb 调用都需要它。这个时间点比较关键如果 init 没执行成功后续 start 就算被调用也不会产生定位数据。3.3 编译 HALAndroid.bp 的依赖清单HAL 层的编译配置直接影响产物能不能被系统找到。Android 10 以后 system 分区只读HAL 放在 vendor 分区是惯例。下面这段 Android.bp 是我在项目里用的最小配置。cc_library_shared { name: android.hardware.gps1.0-impl, srcs: [gps_hw.c, nmea_parser.c], shared_libs: [libhardware, libcutils, liblog], cflags: [-Wall], vendor: true, proprietary: true, }vendor: true决定 .so 落在/vendor/lib/hw/注意这里产物实际名会带上模块名比如gps.default.so。如果 HAL_MODULE_ID 定义成gps.defaulthw_get_module 就会按这个字符串去/vendor/lib/hw/下找文件。我自己翻车过一次把 HAL_MODULE_ID 写成gps编译产物成了gps.so但 framework 找的是gps.default.so结果 hw_get_module 返回 -2定位全部失效。这个命名规则看起来是小问题实际是排错时最容易忽略的一环。4. JNI 与框架链路从 HAL 回调到 Java 层 reportLocation4.1 GpsLocationProvider 的 native 入口绑定Android framework 侧的定位逻辑集中在GpsLocationProvider.java它的 native 方法都在android_location_GpsLocationProvider.cpp里实现。JNI 层通过JNINativeMethod表把 Java 声明和 C 函数绑定起来下面是核心的几个入口。/* android_location_GpsLocationProvider.cpp */ static JNINativeMethod gMethods[] { { nativeInit, ()Z, (void *)android_location_GpsLocationProvider_init }, { nativeStart, ()Z, (void *)android_location_GpsLocationProvider_start }, { nativeStop, ()Z, (void *)android_location_GpsLocationProvider_stop }, { nativeCleanup, ()Z, (void *)android_location_GpsLocationProvider_cleanup }, };这段代码的要点在于签名()Z括号里是参数列表Z 表示 boolean 返回值。Java 侧 nativeStart 返回 true 还是 false直接影响 framework 判断 HAL 是否启动成功。nativeInit是整个链路的地基它只执行一次后续所有 start/stop 都依赖这次初始化拿到的 GpsInterface 指针。如果 init 返回 falseGpsLocationProvider 会直接进入不可用状态后面任何操作都白搭。4.2 JNI 如何拿到 HAL 指针nativeInit的核心动作是调用 hw_get_module 找到 HAL 模块再通过 methods-open 拿到设备对象最后取出 GpsInterface。这段代码我建议原样保留一个错误打印否则上层报的错和真实原因会隔得很远。static const GpsInterface *sGpsInterface NULL; static jboolean android_location_GpsLocationProvider_init(JNIEnv *env, jobject obj) { const hw_module_t *module NULL; gps_device_t *dev NULL; if (hw_get_module(GPS_HARDWARE_MODULE_ID, (const hw_module_t **)module) ! 0) return JNI_FALSE; dev (gps_device_t *)module-methods-open(module); if (dev NULL) return JNI_FALSE; sGpsInterface dev-get_gps_interface(dev); if (sGpsInterface NULL) return JNI_FALSE; sGpsInterface-init(sGpsCallbacks); return JNI_TRUE; }注意module-methods-open(module)的返回值不是文件描述符而是gps_device_t指针只要 HAL 实现里 open 正常返回它就一定存在。真正的串口 fd 或内核句柄封装在 GpsInterface 的 init 回调内部。这里的 sGpsCallbacks 是 JNI 层定义的静态回调集合HAL 层拿到它之后一旦解析出定位结果就会回调到 JNI 层。我自己调试时习惯在 init 之后马上打印一次 sGpsInterface 的地址如果地址稳定且不为空说明 HAL 加载这条路已经通了。4.3 回调线程与 Java 层传递HAL 层的 location_cb 回调跑在自己的线程上这个线程不是 Java 线程。要把 GpsLocation 数据发回 Java 层必须先 AttachCurrentThread再调用 Java 方法。下面是处理回调的标准姿势。static void location_callback(const GpsLocation *loc) { JavaVM *vm sJvm; JNIEnv *env NULL; bool attached false; if (vm-GetEnv((void **)env, JNI_VERSION_1_6) ! JNI_OK) { vm-AttachCurrentThread(env, NULL); attached true; } jclass clazz env-GetObjectClass(sCallbackObj); jmethodID report env-GetMethodID(clazz, reportLocation, (IDDDD)V); env-CallVoidMethod(sCallbackObj, report, loc-flags, loc-latitude, loc-longitude, loc-altitude, loc-speed); if (attached) vm-DetachCurrentThread(); }这段代码里有三个必须注意的参数点。第一sCallbackObj必须在 init 里用NewGlobalRef保存否则 HAL 线程回调时对象可能已经释放直接崩溃。第二flags表示 GpsLocation 里哪些字段有效Java 层要根据 flags 判断要不要相信经纬度和高度不能每一条回调都全字段取值。第三Attach/Detach 必须成对出现线程结束前 DetachCurrentThread否则线程泄漏会拖垮整个进程。这个回调链路是黑匣子里最容易出错的一段我调试时故意把sCallbackObj漏过一次 NewGlobalRef结果崩溃日志指向的位置离真实原因十万八千里。5. 避坑GPS 底层调试中反复踩过的五个常见问题5.1 串口乱码但示波器 RX 波形正常现象读取/dev/ttyS*时输出全是乱码十六进制看是 0xFF 混着 0x0D 0x0A但示波器抓 RX 脚波形波特率和帧格式看起来都对。原因电平不匹配最常见是 3.3V 模块接了 1.8V 串口或者是模块默认 8N2 但驱动配成了 8N1。示波器看的是时序周期看不出来电平域差异。解决先量 RX 脚高电平电压确认模块数据脚和主控 IO 的电平域一致不一致就加电平转换。再排除波特率问题把 9600 和 38400 各试一遍乱码和可读文本的区别一眼就能判断。最后检查 UART 控制器是否配置了 pull-upGPS 模块某些型号空闲时要求线拉高。5.2 hw_get_module 返回 -2现象logcat 里能看到 JNI init 调用但hw_get_module返回 -2模块加载失败。原因绝大多数情况是模块文件命名和 HAL_MODULE_ID 对不上。framework 按gps.default找/vendor/lib/hw/gps.default.so但 Android.bp 里产物名写成了gps.so文件根本不在预期位置。解决adb shell ls -l /vendor/lib/hw/ | grep gps看实际产物名确认与代码里 HAL_MODULE_ID 一致。还要检查proprietary: true后产物是否落到了 vendor 分区而不是 system。我遇到过一次编译产物在 system/lib/hw 下Android 10 只读分区的 SELinux 策略不允许 system_server 加载顺手修掉就好了。5.3 JNI 回调到 Java 层偶发崩溃现象定位跑了几分钟进程突然崩溃日志出现JNI DETECTED ERROR IN APPLICATION或use of deleted local reference。原因sCallbackObj 没有用 NewGlobalRef 保存或者 Attach/Detach 不匹配。HAL 线程每次回调时如果重新 Attach 但不 Detach线程资源会被耗尽反过来如果对象引用是局部引用HAL 线程里再次访问就变成悬垂指针。解决init 里用env-NewGlobalRef(obj)保存 callback 对象确保每次回调前 GetEnv 失败才 Attach回调结束立刻 Detach。这属于一次性写对、反复排查很久的典型问题我建议直接把回调函数体里的引用管理做成公共工具函数所有 native 回调共用同一套逻辑。5.4 冷启动 TTFF 很长但热启动正常现象冷启动需要 3 分钟以上才定位热启动 10 秒内就能出位置温度越低冷启动越慢。原因AGPS 数据没注入或者 TCXO 频率偏移导致模块捕获星历变慢。前者是软件问题后者和硬件晶振选型有关。解决检查 GpsInterface 的 inject_time 回调是否把系统时间正确传给模块时间错半小时都可能让冷启动增加几十秒。查看 GPS.conf 里的 SUPL_HOST 和 SUPL_PORT 是否配置了可用的 AGPS 服务器。硬件侧要回到那四个字“天线走线”净空区不足会直接压低 SNR模块捕获时间成倍增长。这块没有后悔药只能按结构要求重新画板。5.5 LocationManager 里看不到 GPS provider现象Settings 里定位已打开但locationManager.getProviders(true)返回列表里没有 GPS。原因framework 没有成功构造 GpsLocationProvider最常见的源头就是 JNI init 返回 false或者 HAL 的 init 回调里没有正确初始化硬件。解决先adb logcat -s GpsLocationProvider:I看 provider 是否加载再adb logcat -s GpsHAL:V看 HAL 内部日志最后 dmesg 过滤 gps 关键词确认驱动 probe 成功。把 framework 当黑匣子是不行的这一层层日志必须打出来定位到是 JNI、HAL 还是内核驱动哪一层断了。6. 进阶三步完成一次 GPS 驱动改动后的回归验证6.1 编译与推送改了 HAL 或 JNI 层代码后最怕的是编译过了但没推到正确分区。我固定用下面的命令序列做一次完整回归每一步都有明确的检查目标。source build/envsetup.sh lunch your_project-userdebug mmm hardware/libhardware/modules/gps adb root adb remount adb push $OUT/vendor/lib/hw/gps.default.so /vendor/lib/hw/ adb rebootmmm只编译目标模块比 make 快得多适合 HAL 层迭代。push 之后必须 reboot因为 system_server 在启动时才会加载 HAL热替换 .so 不生效。reboot 后第一件事是adb shell ls -l /vendor/lib/hw/gps.default.so确认文件真存在。6.2 三层日志验证adb shell dmesg | grep -iE gps|nmea adb logcat -s GpsLocationProvider:I GpsHAL:V adb shell settings put secure location_mode 3 adb shell cmd location list-providersdmesg 看内核驱动有没有报错logcat 看 JNI 和 HAL 的状态流转settings 命令打开定位开关最后list-providers确认 GPS provider 已经出现在系统列表里。这四条命令从内核到 framework 全链路覆盖了。我一般还会加一条adb shell cmd location get-location看近期定位结果确认不是只挂了 provider 但没有数据流。6.3 用固定坐标做冒烟测试没有真实卫星环境时可以用支持 NMEA 注入的串口工具直接向设备发送一组固定坐标的定位语句。先把波特率调到模块配置值然后循环发送$GPGGA和$GPRMC观察上层 LocationManager 是否收到对应坐标。这个方法能验证 JNI 回调链路是否完整但不能验证天线和 RF 部分所以真实环境下的冷热启动测试仍然不可省略。做完这三步再回头看整个 GPS 链路会发现内核驱动只是起点HAL 的模块命名和 JNI 的线程管理才是真正耗时的雷区。从那以后我每次改完 HAL 或 JNI 层代码都强制自己走一遍上面这三步哪怕只是改了一个日志开关也照样跑。别嫌麻烦GPS 底层这东西出错的地方往往不在你改的那一行而在你没重启的那一层。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
buildah source 子命令完全指南:创建、管理并分发 OCI Source Image 云原生 【免费下载链接】buildah A tool that facilitates building OCI images. 项目地址: https://gitcode.com/gh_mirrors/bu/buildah 点击查看 免费下载 buildah source 是 Buildah 提供的实验性子命令组,用于创建、推送、拉取和管理**源镜像&#… · 2026/9/25 4:17:04
STM32调试必查:BOOT0与NRST硬件级失效机理与实测修复 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:17:04
Context Mode 实战:用 SQLite FTS5 与 MCP 构建上下文窗口管理骨架 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:17:04
华为MDE岗位:模型驱动工程在通信研发中的落地实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:57:30
STM32调试失败的5个底层真相:BOOT0、NRST与SWD链路深度解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:57:30
ESP32-S3四路麦克风阵列回声消除实战:从硬件配置到算法调优 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:57:29
SEED-labs网络安全实验环境搭建:Ubuntu 20.04 + VirtualBox + Docker /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:57:29
Android阅读器翻页交互优化:ViewPager2与Glide实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:57:29
Claude Code嵌入式开发实战:权限控制、寄存器配置与Skills技能管理 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:57:22
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37