很多刚接触 AOSP 的人第一步都栽在同一个地方以为“源码”就是代码拿到压缩包就急着翻文件、看类、找实现。结果打开目录之后看到满屏的 build、frameworks、system、packages、vendor人先懵了。再想找“网络服务”在哪更是无从下手——因为 AOSP 里的网络服务根本不是一个文件、一个进程而是散落在好几个大目录里、通过 Binder 和 socket 互相咬合的一整套机制。这篇博客就是想把这些散落的坐标重新拼起来带你看清楚一个核心问题AOSP 的目录架构是怎么决定网络服务的组织方式以及当我们想改一个网络行为时到底该去哪一层动手。我会按源码树里真实存在的目录路径去讲把 SystemServer、ConnectivityService、netd、NetworkStack 这些老面孔放到它们真正所属的位置上。如果你正准备开始读 AOSP 的源码或者在为某个设备改网络相关功能却总找不到入口这篇内容应该能帮你省下大量瞎翻目录的时间。1. 源码树的第一层先分清“构建系统”和“业务代码”AOSP 的根目录乍看之下很乱但它的组织方式其实有一条明确主线按“构建工具、底层系统、应用框架、外部依赖、设备适配”这五个主题切分。大多数人第一次打开源码树会直接点进 frameworks 和 packages觉得这才是“代码”其实这样容易漏掉最实用的信息。1.1 根目录五大板块谁管构建谁管网络我习惯把根目录先画成一张职责表如下目录主要职责和网络服务的关系build/构建系统、编译脚本、产品配置模板不影响网络逻辑但决定网络模块编不编进镜像frameworks/系统框架与核心服务比如 SystemServer、AMS、PMS、ConnectivityService网络服务的主战场之一绝大部分网络框架代码都在这system/底层系统组件包含 netd、vold、linker 等netd 是网络守护进程的所在地直接操作内核网络栈packages/应用、系统界面、可独立升级的模块现代 AOSP 里 NetworkStack、Wifi 等模块已经挪到这里vendor/ 与 device/厂商定制、硬件抽象、设备树配置决定底层网络硬件接口和私有网络服务加载这张表拿到手之后你起码不会再去 frameworks 里死磕 netd 这种底层东西也不会跑到 build/ 目录下面找网络配置。网络代码的搜索范围被迅速缩小到三块frameworks 里的框架服务、system 里的 netd、packages 里的可升级模块。1.2 为什么说 out/ 目录才是调试网络的第一现场还有一层容易被忽略根目录下经过一次完整编译之后会生成 out/ 目录。很多新手进来就找代码却不知道网络服务的实际运行状态往往要看 out/target/product/xxx/ 下面的产物。编译出来的 system.img 里打包了 framework.jar、services.jar、netd 可执行文件这些才是真机或模拟器里真正跑起来的东西。我在追网络问题时经常先做一件事不急着改代码而是先解包现有镜像里的 services.jar确认当前设备上的 ConnectivityService 是从哪个版本编出来的。这个习惯是从一次“改了代码但结果没变化”的教训里学来的——当时我以为自己改的是框架源码编完刷机却发现行为毫无变化查了半天才意识到用的产物早就被 out/ 里的旧包覆盖了。所以在 AOSP 里调网络服务先分清“源码目录”和“编译产物目录”是基本盘不然你会被自己改的假代码骗一整个下午。2. 网络服务在地图上的精确坐标搞清楚根目录分工后下一步要解决的问题是网络服务究竟在哪些具体路径下。AOSP 的不同分支路径会有差异版本迭代时还会出现目录迁移但大的框架是稳定的。我按当前常见的 AOSP 主干结构整理了一套“导航坐标”足够你在绝大多数分支里快速定位。2.1 从 SystemServer 开始找到 ConnectivityService 的出生地AOSP 里所有系统服务都有一个共同的启动入口SystemServer。只要你想在框架层找一个服务第一步永远去这个入口找。它的代码位置通常在 frameworks/base/services/java/com/android/server/SystemServer.java。打开这个文件你会看到一堆服务注册代码。网络相关的启动链路大致是startOtherServices() 里会调用 startConnectivityServiceLocked()然后才把 ConnectivityService 实例注册到 ServiceManager 上。注册完成之后其他应用才能通过Context.getSystemService(ConnectivityManager.class)拿到服务接口。ConnectivityService 本身的位置是 frameworks/base/services/core/java/com/android/server/ConnectivityService.java。它是整个 AOSP 网络框架的“中枢神经”WiFi、以太网、移动数据、虚拟网络所有网络的注册和路由策略都由它统一调度。这就是为什么你搜网络问题时最后总会绕回这个文件——它不是网络实现者而是网络世界的管理者。2.2 netd 和 NetworkStack懂网线的和懂策略的各管一段ConnectivityService 名字里带个 Service看起来很高层但它其实不会自己去操作网络接口。真正和内核打交道的是 netd也就是 network daemon位于 system/netd/ 目录下。netd 的角色很朴素接收上层下发的指令通过 netlink 和 ioctl 去配置内核的网络接口、路由表、防火墙规则、带宽统计。你可以把它理解成一个“外包执行方”——上层告诉它“把 eth0 连上分配这个网段”它就去操作内核。因此如果你要调底层路由、iptables 规则、DNS 代理真正该看的是 system/netd/server/ 下面的代码。另一边网络协议栈的很多具体实现被放进了可升级模块。以前这些代码混在 frameworks/opt/net/ 或 packages 里现在更容易在 packages/modules/NetworkStack/ 这类目录下找到。它负责 DHCP 客户端、IP 分配、网络探测等更靠近“网线经验”的活。简单记ConnectivityService 管策略netd 管内核NetworkStack 管链路层上的具体协议。组件典型目录职责边界ConnectivityServiceframeworks/base/services/core/java/com/android/server/ConnectivityService.java网络注册、排序、评分、路由策略、应用查询netdsystem/netd/内核接口、路由、iptables、防火墙、流量统计NetworkStackpackages/modules/NetworkStack/DHCP、IP 分配、网络探测、部分协议实现Wifi 服务packages/modules/Wifi/WiFi 状态机、扫描、连接管理EthernetServiceframeworks/base/services/core/java/com/android/server/ethernet/以太网控制、接口监听、IP 分配协调2.3 最容易被忽略的两个目录hardware 与 vendor大多数改网络服务的人都在框架层摸爬滚打但真正设备上的网络经常卡在两个“目录盲区”上一个是 hardware/一个是 vendor/ 下的私有实现。hardware/ 里存放硬件抽象层接口比如 WiFi HAL、以太网 HAL。网络服务再牛最后也要通过 HAL 去控制真实的 WiFi 芯片或网卡。如果设备网卡驱动异常你在上层改再多策略也救不回来。vendor/ 目录则存放厂商自己的网络扩展——比如某些电视盒子会用私有服务做多网卡策略某些车载方案会叠加自己的网络链路监测。这两个目录往往是你在 AOSP 源码里找不到答案时必须转向的地方。我接过一个设备改版项目现象是“以太网断连后不能自动恢复”。当时我在 ConnectivityService 和 EthernetService 里反复加日志、改回调始终没解决。后来一位有经验的前辈提醒我先去 vendor/ 里搜一下是否有厂商自己注册的 EthernetTracker 扩展。结果还真发现厂商在开机脚本里强制重置了网口状态。这件事让我记住了AOSP 目录是地图但设备的实际行为可能是“地图外的标绘”。3. 读网络服务源码的实操路径目录再熟如果不知道用怎样的顺序去读代码还是会一头扎进细节里出不来。我总结了一条基于“现象到文件”的检索路径适合所有刚接触网络服务源码的人。3.1 从一个真实案例反查 ConnectivityService 的入口有一次我需要定位一个很奇怪的问题设备插着网线但第三方应用获取不到网络系统设置界面却显示“已连接”。我没急着搜网上的答案而是先通过 adb 拿运行时状态adb shell dumpsys connectivity adb shell dumpsys ethernet adb shell dumpsys netstats第一份 dump 就能看到当前活动网络、网络分分数、传输类型以及有多少应用被限制在这个网络上。拿到状态后我再去 ConnectivityService.java 里搜特色字符串比如NetworkAgentInfo、mNetworkAgentInfos、onNetworkStateChanged快速定位到状态维护的代码段落。这种“先运行时后静态代码”的路线比对着文件乱看高效得多。它把你的注意力从“目录里的全貌”收敛到“具体状态变量和回调链”上不会让你在几百个方法里迷失。3.2 真要改一个网络行为通常要动哪些文件以一个常见需求为例想修改系统对“低评分网络”的切换策略。你在 AOSP 里的动作一般涉及这几个目录frameworks/base/services/core/java/com/android/server/ConnectivityService.java修改网络评分排序逻辑调整切换阈值。frameworks/base/core/java/android/net/NetworkCapabilities.java修改网络能力定义影响上层如何判断某个网络的可用性。frameworks/base/services/core/java/com/android/server/net/NetworkAgentInfo.java管理和维护网络代理的状态。packages/modules/NetworkStack/如果需要同步调整链路探测方式这里会牵涉到。改动时必须注意一条潜规则上层框架改动后系统内很多模块是通过 Binder 回调拿到网络状态的光改 ConnectivityService 还不够可能还要同步更新系统级回调的位数和权限校验逻辑。我见过很多新手改了 ConnectivityService 里的判定方法编译刷机后现象没变原因就是相同逻辑在 NetworkPolicyManagerService 或 BatteryStatsService 里还有一份近似判断。所以修改网络行为前最好全局搜一遍你要改的字段名别只盯着一个文件。3.3 编译与验证环节最影响效率的细节AOSP 编译是另一个大头尤其是只改框架层服务时每次都全量编译很消耗时间。我的做法是尽量用增量编译先确认改动模块的编译边界source build/envsetup.sh lunch product-name-userdebug m services只编 services.jar 通常比整包快很多。编好之后如果是模拟器直接把产物推进去重启对应服务如果是真机就要重新打包 system.img 再刷机或者通过 adb push 到特定目录并重启系统服务进程。这里最需要注意的是签名问题userdebug 镜像允许你 adb root 后替换文件但正式 release 镜像不能这样玩。验证环节里dumpsys 是我最依赖的工具。改完网络切换逻辑后我会先跑adb shell dumpsys connectivity看网络状态是否按预期变化再跑adb shell cmd connectivity看一下命令行接口提供的状态。如果现象没复现不要急着再改代码先在关键位置加日志打印特别是 ConnectivityService 里的回调线程和 netd 命令下发处你很快能定位到是网络没有上报还是策略没有生效。4. 这些目录架构在不同设备上的差异AOSP 目录架构是通用的但不同硬件形态会对网络服务做明显裁剪。你在一台手机上找到的网络路径换到电视盒子或车机上可能就不完整了。4.1 手机、平板、电视盒子对网络服务的裁剪差异手机和平板基本会启用完整的网络框架移动数据、WiFi、蓝牙共享、热点、以太网通过转接线都会被编译进去。它们的网络目录最常见改的风险也最低。电视盒子则不一样。很多盒子没有移动数据模块也没有电话服务代码树里与 telephony、mobile data 相关的部分会被裁剪掉。你如果在盒子上按手机思路找“移动数据网络”会发现连 NetworkCapabilities 里的传输类型都少了几种。同时盒子更依赖以太网所以 EthernetService、EthernetTracker 会占更重的地位甚至还需要在 overlay 配置里设置默认网卡。平板介于两者之间硬件形态接近手机但可能多出扩展坞、有线网卡等需求。目录架构上依然是同一份源码但 product 配置里会打开或关闭不同模块。这就需要你通过 device/ 和 vendor/ 下的产品配置去推断实际打包出的网络服务范围。4.2 车载形态的特殊网络目录需求车载是最值得单独说的场景因为它经常同时存在“乘客网络”和“车辆控制网络”也就是多个网段并存。AOSP 原生的 ConnectivityService 支持多网络并发但车载方案往往会在 vendor/ 或 device/ 下再加一层网络策略服务用来管理哪些应用可以走乘客网络哪些数据必须走控制平面。这种情况下原生目录架构里的 work profile、app restrictions、network policy 会被厂商改造得很重。你会发现 packages/modules/Connectivity 虽然存在但实际生效的网络策略可能是硬件厂商自己在 vendor/ 里注册的另一个服务。做车载网络定制时一定要先读产品配置里的 overlay 文件看它是否替换了原生 ConnectivityConfig 或 NetworkStack 的相关行为别想当然以为原生代码一定生效。5. 按目录查阅时我沉淀下来的几个检索习惯这部分是我个人在长时间读 AOSP 源码过程中总结出的技巧不成体系但很实用。5.1 用路径联想代替全文检索AOSP 源码太大全文检索常常出来几百个结果反而干扰判断。我的习惯是先预测模块路径再用路径限定检索范围。想找以太网就直奔 frameworks/base/services/core/java/com/android/server/ethernet/想找网络策略就集中在 NetworkPolicyManagerService 所在路径附近搜想找路由设置就到 system/netd/ 下搜 netlink 相关的文件。这种“先定位目录、再搜索文件”的方式能保证你最大概率命中核心实现而不是被一堆没有上下文的同名方法淹没。5.2 善用代码跳转和调用链追踪无论你用的是 Android Studio 还是 VSCode 加插件建立索引之后的代码跳转功能都非常关键。看到一个类方法时我第一步是看它的调用来源第二步是看它调用了谁。两步下来目录架构在你眼里会自动生成一张依赖网。比如看到ConnectivityService.getNetworkCapabilities()跳回调用处你会发现是ConnectivityManager内部的 Binder 代理在调用它而整个调用都经过 ServiceManager 的代理机制。5.3 从配置项反向追溯目录AOSP 里有很多配置文件比如 frameworks/base/core/res/res/values/config.xml、frameworks/base/packages/Connectivity 中的默认值。遇到“某个网络参数在生效但找不到代码”的情况不要光搜 Java 代码还要留意 config.xml、product overlay、init.rc 这些“配置即代码”的地方。很多时候网络服务的启动参数或某个默认开关不是写在 Java 里的而是通过系统属性读取的。我处理过一个奇葩问题板卡上以太网默认关闭无论怎么在 EthernetService 里改初始状态都没用。后来才发现是 init.rc 里提前把sys.eth.auto_connect设成了 false。这就是 AOSP 目录架构里最容易被忽略的一层frameworks/ 里的 Java 服务再努力也拗不过 init 进程预设的系统属性。结尾说点实际的掏心话AOSP 的目录架构看起来像一颗庞大的知识树但它最值得学的不是把每个目录背下来而是建立起一条“需求到文件”的路径感。你知道网络服务存在框架层、守护进程层、协议栈可升级模块层知道从 SystemServer 这个入口去翻服务注册知道用 dumpsys 验证改动的效果就已经超过了绝大多数刚上手的人。按我的经验第一次读这类大型源码树时最忌“从第一个文件读到最后一行”。正确做法是拿到一个真实问题比如“以太网连不上”“网络切换太慢”“某个应用没有网”然后沿着目录架构一路顺藤摸瓜。等你能不假思索地说出 ConnectivityService 在哪个目录、netd 又在哪个目录你已经从“看代码的人”变成了“在代码里找路的人”。最后给一个小技巧本地常备不同版本分支的源码并做好目录 diff。AOSP 的目录迁移频率不低稍微新一点的分支和旧分支之间某些网络组件会被移到 packages/modules 里。当你搜不到某个服务时先看一下它是不是换了个新家——这种“找不到”往往不是源码缺失而是你的地图过期了。