有线网络属于以太网的一种实现方式,相对于WiFi而来说,它是通过物理连接方式对设备提供网络支持. 先介绍一下Android中网络需要熟悉哪些模块,如下图:

本片介绍Android网络中有线网络 看完本篇文章 你将了解到如下内容:
- 有线网络服务是如何启动的
- 设备接入网线以后是如何监听的
- 设备IP地址是如何获取到的和 DHCP 协议
- 设备同时拥有有线网无线网 移动数据网 默认数据使用什么网络类型
- 可以为某一个进程指定网络类型
- 网络变化是如何通知上层应用的
- 基本的设备网络路由知识
1、有线网络服务是如何启动的
和Android其他服务启动一样,在zygote启动SystemServer以后,再调用其main方法启动Others服务,启动 EthernetService。相关代码位置:
frameworks\base\services\java\com\android\server\SystemServer.java frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetService.java
ini
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
t.traceBegin("StartEthernet");
// ETHERNET_SERVICE_CLASS = "com.android.server.ethernet.EthernetService";
mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
t.traceEnd();
}
注册有线网络服务管理器: frameworks\base\core\java\android\app\SystemServiceRegistry.java
java
registerService(Context.ETHERNET_SERVICE, EthernetManager.class,
new CachedServiceFetcher<EthernetManager>() {
@Override
public EthernetManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(Context.ETHERNET_SERVICE);
IEthernetManager service = IEthernetManager.Stub.asInterface(b);
return new EthernetManager(ctx.getOuterContext(), service);
}});
这样一来,上层应用就可以通过如下方式:
ini
EthernetManager manager = context.getSystemService(Context.ETHERNET_SERVICE);
拿到有线网络管理器从而可以获取到有线网络一些接口,由于Android系统更加注重手持移动设备,所以原生Android系统对这一部分实现的非常简单。Android原生就是默认不支持DHCPv6方式来获取IPV6地址的,而有些芯片供应商会提供相应patch来实现这一部分,DHCP Options Android也是没有默认实现大多数的。甚至Android原生网络系统存在一些bug,比如IP地址续租会导致设备明明连着网络,IP地址信息都正确,但是设备依然不能够正常发送网络请求。这些都是我们需要解决的问题,这个问题放到稍后DHCP协议内容再介绍。
有线网络服务为我们提供如下接口:

2、设备接入网线以后是如何监听的
使用UEventObserver来做Uevent事件监听,监听到插拔网线会通过发送Up/Down事件,通知到Netd,然后由Netd通知到Android系统有线网络服务,根据事件类型重新DHCP或者断开有线网络,从而做到插拔网线有线网络断开和重连。如果不做这个操作,会影响到下一次DHCP流程,导致设备看上去IP地址正常,但是无法正常发送网络请求。

3、设备IP地址是如何获取到的和DHCP协议
设备要想正常发送网络请求,设备网卡必须有正常IP地址并配置正常路由信息,这样发送报文时才知道源地址和接下来报文需要发送到哪一个节点时报文才能正常被发送出去。那设备上没有正常IP地址,又是如何获取到IP地址的呢?这个就需要一个额外的协议------DHCP协议。看一次正常DHCP过程抓包过程:

其中,DHCP Discover 和 DHCP Request 报文是由设备发送出去的。它们的目的地址为 255.255.255.255,属于广播报文。也就是说,设备在物理连接建立后,会先发送 Discover 报文,以探测当前网络环境中是否存在能够处理该格式报文的服务器。如果设备所在网络环境中存在 DHCP 服务器,服务器便能正常响应这个报文,即回复 DHCP Offer 报文。该报文中会携带一个 IP 地址,表明可以为设备分配该 IP 地址。当设备收到这个 Offer 报文后,会对其进行处理,并发送 DHCP Request 报文,请求分配 Offer 报文中下发的 IP 地址。当设备得到 DHCP ACK 报文后,会从中解析出 IP 地址、网关信息、DNS 信息、续租周期以及一系列 DHCP Options(这些 Options 对于大型企业对员工设备进行企业部署配置非常有用,也可以用于认证等选项)。此处回答前面遗留的一个问题:设备写入 IP 地址时会同步清理路由信息。有时,续租时间较短的 DHCP 服务器会在续租后出现设备路由信息丢失的情况,从而导致网络服务正常,但设备无法将网络请求发送到网关,最终导致网络请求超时失败。

DHCP 协议是一个相对简单的协议,但其细节仍然较多。Android 从 5.0 之后开始使用 Java 实现 DHCP 客户端,在此之前都是通过 dhcpd 进程来充当 DHCP 客户端,这使得对一些 DHCP Options 的定制比较困难。而在 5.0 之后,这种定制变得非常简单。具体实现中,Android 使用 DhcpClient 作为 DHCP 客户端,它是一个用 Java 实现的状态机。
代码位置:
packages\modules\NetworkStack\src\android\net\dhcp\DhcpClient.java
Android 10之前位置:
frameworks\base\services\java\com\android\net\dhcp\ DhcpClient.java
这个状态机也是Android中动态IP地址建立连接最重要的一个部分整体,它的状态机结构图如下:

有线网络服务从启动到设备获取到IP地址并通知到上层应用网络已经正常的流程如下图:

注意网络变化广播是一个粘性广播,也就是注册之后立即可以收到一个广播,其实在Android 6 之后 Google不再推荐使用这个广播,因为支持的网络类型增多以后有时候高优先级网络连接以后 低优先级网络再建立连接 这个广播不一定会发送出去从而导致一些应用等待一些指定网络类型建立连接以后的一些操作不能够正常运行,Android 6 以后Google提供新的api 方法来让app 通过使用参考如下:
java
// 监听某个网络类型可用和不可用状态变化回调接口
private static ConnectivityManager.NetworkCallback mCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
// 当监听的网络类型可用了回调
super.onAvailable(network);
}
@Override
public void onLost(Network network) {
// 当监听的网络类型不可用了回调
super.onLost(network);
}
@Override
public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
// 当监听的网络类型 ip地址发生变化后
super.onLinkPropertiesChanged(network, linkProperties);
}
};
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(CONNECTIVITY_SERVICE);
// 构建监听网络状态请求
NetworkRequest.Builder builder = new NetworkRequest.Builder();
// 添加WiFi网络类型状态变化
builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
// 添加有线网络状态变化
builder.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
// 注册状态变化监听器
connectivityManager.registerNetworkCallback(builder.build().mCallback);
4、设备同时拥有有线网无线网 移动数据网 默认数据使用什么网络类型
Android 从 5.0 之后开始支持多个网络类型同时存在的情况。在 Android 5.0 之前,虽然表面上做到了两者共存,但实际上当高优先级网络建立连接后,系统会将低优先级网络类型的路由表移除掉。这样一来,表面上两者共存,但低优先级的网络实际上无法正常发送网络请求。在支持多网络类型之后,Android 为每一种网络类型引入了分数机制。目前,Android 设备上常用的网络类型有以下三种,优先级也按照如下分数从高到低排列。其中,WiFi 和移动数据网络的分数会随信号强弱而调整,但最高不会超过图中标注的分数。

可以通过adb shell dumpsys netd 命令查看当前使用的网络类型。
5、可以为某一个进程指定网络类型
对于上层应用来说,一般不需要为自己发出的网络请求指定某一个网络类型。使用 C 语言开发的进程,可以通过基于 socket 的 setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, ifaceName) 方式绑定指定网卡,从而让自身的数据流量走该网卡。对于 Java 实现的应用,则可以使用系统提供的接口 ConnectivityManager.bindProcessToNetwork()。
6、基本的设备网络路由知识
当网络建立连接后,系统会基于网卡名称在底层建立一个路由表。可以在 adb shell 终端下执行如下命令来查看当前设备的路由信息:
在设备终端执行:
ip route show table all:查看设备所有的路由信息ip route show table eth0:查看设备中有线网络的路由表ip route show table wlan0:查看设备中 WiFi 网络的路由表
当查看某一网络类型时,通常会打印出两条路由信息表:同一局域网内的路由走向,以及需要通过网关的路由,也称为默认路由。Android 的路由信息表是参照 Linux 路由表设计来实现的。
总结
由于 Android 是面向移动终端的操作系统,其对有线网络的支持非常简单,基本上处于一种"可以使用"的状态,问题也比较多。很多功能原生并不支持,例如越来越重要的 IPv6 地址动态获取功能;一些有利于企业部署的 DHCP Option 功能也只支持非常简单的几个。这些都需要厂商自己做较多的优化和改进。
从长远来看,Google 对网络模块的权限管控也越来越严格。Android 9.0 上线了隐私 DNS 功能;到了 Android 10 以后,将 DNS 操作 API 模块从 netd 中抽离出来,单独构建为一个服务;还将 DHCP 客户端从 System_server 进程移到了 NetworkStack 模块中,移除了 dnsmasq 等进程。后续的网络定制将变得更加集中化,但同时也变得更加困难。网络方面的一些问题也较难分析。