1.需求描述
以太网目前默认启动是dhcp动态获取,所以就导致了每次dhcp获取到的ip不固定,需求是能够让以太网以固定IP启动。所以本章节按照以下流程进行介绍与功能实现:
(1). Android 原始Ethernet动态启动框架流程
(2). 静态IP需求实现思路以及过程中遇到的问题
(3). Android7.1与Android12.0 实现上差异比对
2. 以太网设置ip框架流程梳理
个人在做非紧急需求开发时,形成的工作习惯是先去看这个需求对应所在的相关系统原生框架流程,之后再考虑如何在现有的框架基础上实现满足对应的功能的需求。所以我们先初步看下对应原生Android Ethernet以太网框架中是如何分别进行静态启动以及动态启动。EthernetManager在以下文章中简称EMS, 类似像EMS这种服务也都是由SystemServer来统一进行初始化与启动, 所以入口位置在于SystemServer.java,我们就先从服务的启动开始看.
文件路径:
frameworks/base/services/java/com/android/server/SystemServer.java
如图1所示:
mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
图1 SystemServer启动EMS
mSystemServiceManager具体类是SystemServiceManager, 转入对应startService方法
文件路径:
frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
如图2所示:
public <T extends SystemService> T startService(Class<T> serviceClass) {
try {
final String name = serviceClass.getName();
Slog.i(TAG, "Starting " + name);
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartService " + name);
if (!SystemService.class.isAssignableFrom(serviceClass)) {
throw new RuntimeException("Failed to create " + name
+ ": service must extend " + SystemService.class.getName());
}
final T service;
try {
Constructor<T> constructor = serviceClass.getConstructor(Context.class);
service = constructor.newInstance(mContext);
} catch (InstantiationException ex) {
//....
//....
}
startService(service);
return service;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
}
图2 startService方法
注意startService进行了重载,本文中EMS传入的就是Class<T>对应模板类中的泛型具体类型,所以直接进入的是这个方法,在这个方法中通过 Constructor进行反射实现对相应服务的构造,并返回对应service, 这个时候进行构造时对应的EthernetService构造方法得到执行,我们先看下。
文件路径:
frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetService.java
如图3所示:
public final class EthernetService extends SystemService {
private static final String TAG = "EthernetService";
final EthernetServiceImpl mImpl;
public EthernetService(Context context) {
super(context);
mImpl = new EthernetServiceImpl(context);
}
@Override
public void onStart() {
Log.i(TAG, "Registering service " + Context.ETHERNET_SERVICE);
publishBinderService(Context.ETHERNET_SERVICE, mImpl);
}
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
mImpl.start();
}
}
}
图3 EthernetService
EthernetService 构造中实例 EthernetServiceImpl, 具体的业务也都是通过 EthernetServiceImpl来进一步实现。
文件路径:
frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetServiceImpl.java
如图4所示:
public class EthernetServiceImpl extends IEthernetManager.Stub {
private static final String TAG = "EthernetServiceImpl";
private final Context mContext;
private final AtomicBoolean mStarted = new AtomicBoolean(false);
private Handler mHandler;
private EthernetTracker mTracker;
public EthernetServiceImpl(Context context) {
mContext = context;
}
//....
//....
}
图4 EthernetServiceImpl
可以看到 EthernetServiceImpl实现了 IethernetManager.Stub,所以对应的EMS客户端其实就是EthernetServiceImpl, 构造方法中就只是赋值了 mContext, 回到 SystemServiceManager中的startService方法, 通过构造实例化对应实际Service后,进一步重载调用startService, 如图5所示:
public void startService(@NonNull final SystemService service) {
// Register it.
mServices.add(service);
// Start it.
long time = SystemClock.elapsedRealtime();
try {
service.onStart();
} catch (RuntimeException ex) {
throw new RuntimeException("Failed to start service " + service.getClass().getName()
+ ": onStart threw an exception", ex);
}
//.....
}
图5 startService方法重载
方法中将服务实例添加进mServices后,调用对应服务中的onStart方法,此时进入EMS中的onStart方法, 具体实现如图上图3, 调用 publishBinderService发布一个服务后就没有其它逻辑了。接下来继续按照SystemServer逻辑往后走,SystemServer的启动也是分阶段进行,通过一个关键变量,简称BOOT_PHASE,变量区间从0-1000取了几个特定的数值用来代表系统启动的阶段,EMS处于PHASE_SYSTEM_SERVICES_READY阶段,所以在SystemServer中继续来追踪,如图6所示:
t.traceBegin("StartBootPhaseSystemServicesReady");
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_SYSTEM_SERVICES_READY);
t.traceEnd();
图6 StartBootPhaseSystemServicesReady阶段
继续回到SystemServiceManager.java文件中的startBootPhase方法中, 具体实现如图7所示:
public void startBootPhase(@NonNull TimingsTraceAndSlog t, int phase) {
//....
//....
try {
t.traceBegin("OnBootPhase_" + phase);
final int serviceLen = mServices.size();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
long time = SystemClock.elapsedRealtime();
t.traceBegin("OnBootPhase_" + phase + "_" + service.getClass().getName());
try {
service.onBootPhase(mCurrentPhase);
} catch (Exception ex) {
throw new RuntimeException("Failed to boot service "
+ service.getClass().getName()
+ ": onBootPhase threw an exception during phase "
+ mCurrentPhase, ex);
}
warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onBootPhase");
t.traceEnd();
}
} finally {
t.traceEnd();
}
//....
//....
}
图7 startBootPhase方法
可以看到遍历mServices列表,分别调用各自服务子类中实现的 onBootPhase方法, 由于我们是EMS服务,所以就直接去看EMS的onBootPhase方法,如上图3所示:调用mImpl.start方法, 具体mImpl.start方法下图8所示:
public void start() {
Log.i(TAG, "Starting Ethernet service");
HandlerThread handlerThread = new HandlerThread("EthernetServiceThread");
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper());
mTracker = new EthernetTracker(mContext, mHandler);
mTracker.start();
mStarted.set(true);
}
图8 start方法
方法中创建名为 EthernetServiceThread子线程并同时使用该线程的loop来实例化出Handler,后续使用该Handler分发的消息处理实体线程就在这个创建出来的EthernetServiceThread子线程中,之后实例化 EthernetTracker并调用start。
文件路径:
frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetTracker.java
start方法如图9所示:
void start() {
mConfigStore.read();
// Default interface is just the first one we want to track.
mIpConfigForDefaultInterface = mConfigStore.getIpConfigurationForDefaultInterface();
final ArrayMap<String, IpConfiguration> configs = mConfigStore.getIpConfigurations();
for (int i = 0; i < configs.size(); i++) {
mIpConfigurations.put(configs.keyAt(i), configs.valueAt(i));
}
try {
mNMService.registerObserver(new InterfaceObserver());
} catch (RemoteException e) {
Log.e(TAG, "Could not register InterfaceObserver " + e);
}
mHandler.post(this::trackAvailableInterfaces);
}
图9 start方法
mConfigStore.read();关键的部分, 这里ConfigStore是一个封装了预设ip, 网关,子网掩码,主dns,备用dns的类,这个类和我们静态启动功能强相关,我们先看下这个类
文件路径:
frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetConfigStore.java
关键代码如图10所示:
public class EthernetConfigStore {
private static final String ipConfigFile = Environment.getDataDirectory() +
"/misc/ethernet/ipconfig.txt";
private IpConfigStore mStore = new IpConfigStore();
private ArrayMap<String, IpConfiguration> mIpConfigurations;
private IpConfiguration mIpConfigurationForDefaultInterface;
private final Object mSync = new Object();
public EthernetConfigStore() {
mIpConfigurations = new ArrayMap<>(0);
}
public void read() {
synchronized (mSync) {
ArrayMap<String, IpConfiguration> configs =
IpConfigStore.readIpConfigurations(ipConfigFile);
// This configuration may exist in old file versions when there was only a single active
// Ethernet interface.
if (configs.containsKey("0")) {
Log.e("EthernetConfigStore", "mIpConfigurationForDefaultInterface set value");
mIpConfigurationForDefaultInterface = configs.remove("0");
}
mIpConfigurations = configs;
}
}
public void write(String iface, IpConfiguration config) {
boolean modified;
synchronized (mSync) {
if (config == null) {
modified = mIpConfigurations.remove(iface) != null;
} else {
IpConfiguration oldConfig = mIpConfigurations.put(iface, config);
modified = !config.equals(oldConfig);
}
if (modified) {
mStore.writeIpConfigurations(ipConfigFile, mIpConfigurations);
}
}
}
public ArrayMap<String, IpConfiguration> getIpConfigurations() {
synchronized (mSync) {
return new ArrayMap<>(mIpConfigurations);
}
}
@Nullable
public IpConfiguration getIpConfigurationForDefaultInterface() {
synchronized (mSync) {
return mIpConfigurationForDefaultInterface == null
? null : new IpConfiguration(mIpConfigurationForDefaultInterface);
}
}
}
图10 EthernetConfigStore类
由于这个类在静态IP启动中尤为重要,所以代码没有简化处理,可以看到read方法中会从 ipConfigFile文件中读取相关信息,然后填充内部成员 mIpConfigurations,之后在相应接口中进行相应返回处理逻辑,ipConfigFile是什么呢, 在本类中被定义为/data/misc/ethernet/ipconfig.txt, 在该文件一开始中有定义, 所以说如果设备实际系统中存在ipconfig.txt,那么read时就会从该文件中读取相关配置更新到mIpConfigurations 中。
继续往后走,如图9所示, 之后继续调用mConfigStore.getIpConfigurationForDefaultInterface 以及 mConfigStore.getIpConfigurations, 并分别返回相应变量, 这时注意 getIpConfigurations中返回的就是从ipconfig.txt文件中读取出来的相关网络参数, 如果确实有相应网络配置参数,则填充mIpConfigurations, 我们这里默认没有这些参数,也就是我们设备目前的默认状态,以太网dhcp默认动态启动。继续往后走看,mNMService.registerObserver(new InterfaceObserver()) 进行监听注册,使用观察者设计模式,目的在于以太网是存在手动插拔的情况,当手动插拔时能够通过这个Observer抛上来状态,让EMS服务同步更新对应的网络状态。之后进一步调用 mHandler.post(this::trackAvailableInterfaces); trackAvailableInterfaces实现如图11所示:
private void trackAvailableInterfaces() {
try {
final String[] ifaces = mNMService.listInterfaces();
for (String iface : ifaces) {
maybeTrackInterface(iface);
}
} catch (RemoteException | IllegalStateException e) {
Log.e(TAG, "Could not get list of interfaces " + e);
}
}
图11 trackAvailableInterfaces实现
进一步调用 maybeTrackInterface方法, 如图12所示:
private void maybeTrackInterface(String iface) {
//……
addInterface(iface);
}
图12 maybeTrackInterface方法
关键逻辑 addInterface, 继续追踪, 如图13:
private void addInterface(String iface) {
//.....
final int mode = getInterfaceMode(iface);
if (mode == INTERFACE_MODE_CLIENT) {
IpConfiguration ipConfiguration = mIpConfigurations.get(iface);
if (ipConfiguration == null) {
ipConfiguration = createDefaultIpConfiguration();
}
Log.d(TAG, "Tracking interface in client mode: " + iface);
mFactory.addInterface(iface, hwAddress, nc, ipConfiguration);
} else {
maybeUpdateServerModeInterfaceState(iface, true);
}
//.....
}
图13 addInterface
动态时,获取到的 ipConfiguration为null, 会走入 createDefaultIpConfiguration,我们看下这个方法,如图14:
private static IpConfiguration createDefaultIpConfiguration() {
final IpConfiguration ret = new IpConfiguration();
ret.setIpAssignment(IpAssignment.DHCP);
ret.setProxySettings(ProxySettings.NONE);
return ret;
}
图14 createDefaultIpConfiguration方法
可以看到这里默认给的 IpAssignment为DHCP, 默认动态就是这里配置的,我们可以再往后走走看, mFactory.addInterface 进一步调用.如图15所示:
文件路径:
frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetNetworkFactory.java
void addInterface(String ifaceName, String hwAddress, NetworkCapabilities capabilities,
IpConfiguration ipConfiguration) {
if (mTrackingInterfaces.containsKey(ifaceName)) {
Log.e(TAG, "Interface with name " + ifaceName + " already exists.");
return;
}
if (DBG) {
Log.d(TAG, "addInterface, iface: " + ifaceName + ", capabilities: " + capabilities);
}
NetworkInterfaceState iface = new NetworkInterfaceState(
ifaceName, hwAddress, mHandler, mContext, capabilities, this);
iface.setIpConfig(ipConfiguration);
mTrackingInterfaces.put(ifaceName, iface);
updateCapabilityFilter();
}
图15 addInterface方法
进一步调用iface.setIpConfig(ipConfiguration); 如图16所示:
void setIpConfig(IpConfiguration ipConfig) {
this.mIpConfig = ipConfig;
restart();
}
图16 setIpConfig
ipconfig赋值后,调用restart, restart里面会先stop后再次start,所以我们直接看start方法, 如图17所示:
private void start() {
//......
provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
}
图17 provisionIpClient
进一步调用provisionIpClient, 具体实现如下图18所示:
private static void provisionIpClient(IIpClient ipClient, IpConfiguration config,
String tcpBufferSizes) {
//.....
//.....
if (config.getIpAssignment() == IpAssignment.STATIC) {
provisioningConfiguration = new ProvisioningConfiguration.Builder()
.withStaticConfiguration(config.getStaticIpConfiguration())
.build();
} else {
provisioningConfiguration = new ProvisioningConfiguration.Builder()
.withProvisioningTimeoutMs(0)
.build();
}
try {
ipClient.startProvisioning(provisioningConfiguration.toStableParcelable());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
图18 provisionIpClient
可以看到根据区分静态模式还是DHCP动态模式,重新基于 IpConfiguration类对象生成 provisioningConfiguration并进行后续跨进呈传输,当然provisioningConfiguration自身会实现序列化相关功能,后续的流程就是走入ipClient中startProvisioning 开始通过状态机以及dhcp相应指令获取合法ip, 这部分流程和实际做静态IP启动关联性很小,有兴趣读者可自行继续深入分析, 至此其实对于我们当前做静态IP启动框架梳理到这里就足够了, 中间静态和动态分流点在于图13中通过mIpConfigurations.get(iface);是否能够获取到有效配置,如果是非有效配置,则调用createDefaultIpConfiguration做动态处理;如果是有效配置则根据对应配置设置以太网相应参数。
那 mIpConfigurations 来源又是来自哪里,可以想上追溯到 mConfigStore.getIpConfigurations,这个来源,这个方法实现又是再图10的 EthernetConfigStore中,return new ArrayMap<>(mIpConfigurations); mIpConfigurations在EthernetConfigStore中是在read方法中进一步赋值的,赋值来源则是/data/misc/ethernet/ipconfig.txt, 所以静态IP的方案就基本出来了,在EthernetTracker构造的同时, 将对应的网络配置参数写入ipconfig.txt就可以了。
3. 静态IP启动功能实现与思路
3.1 方法一
承接上文, 思路就是在EthernetTracker构造时, 将期望的ip参数配置到ipconfig.txt中,那么如何配置呢。是个好问题,别忘了,Android系统Settings中可是有以太网的设置条目, 我们直接去追看下Setting, 搜索关键字可以找到EthernetSetting.java文件。
文件路径:
packages/apps/Settings/src/com/android/settings/ethernet/EthernetSettings.java
关键实现代码如图19所示:
private boolean setStaticIpConfiguration() {
/*
* get ip address, netmask,dns ,gw etc.
*/
Inet4Address inetAddr = getIPv4Address(this.mEthIpAddress);
int prefixLength = maskStr2InetMask(this.mEthNetmask);
InetAddress gatewayAddr = getIPv4Address(this.mEthGateway);
InetAddress dnsAddr = getIPv4Address(this.mEthdns1);
if (null == inetAddr || inetAddr.getAddress().toString().isEmpty()
|| prefixLength == 0
|| gatewayAddr.toString().isEmpty()
|| dnsAddr.toString().isEmpty()) {
log("ip,mask or dnsAddr is wrong");
return false;
}
String dnsStr2 = this.mEthdns2;
ArrayList<InetAddress> dnsAddrs = new ArrayList<InetAddress>();
dnsAddrs.add(dnsAddr);
if (!dnsStr2.isEmpty()) {
dnsAddrs.add(getIPv4Address(dnsStr2));
}
mStaticIpConfiguration = new StaticIpConfiguration.Builder()
.setIpAddress(new LinkAddress(inetAddr, prefixLength))
.setGateway(gatewayAddr)
.setDnsServers(dnsAddrs)
.build();
mIpConfiguration = new IpConfiguration();
mIpConfiguration.setIpAssignment(IpAssignment.STATIC);
mIpConfiguration.setProxySettings(ProxySettings.NONE);
mIpConfiguration.setStaticIpConfiguration(mStaticIpConfiguration);
return true;
}
图19 Setting setStaticIpConfiguration
该方法会在Setting弹出的Dialog中配置好ip 网关等基础5参数后调用进来,所以我们的静态IP方案中的mIpConfigurationg构造就可以参考这个方法,之后调用mConfigStore.write方法将配置的参数进一步写入到ipconfig.txt就可以了。注意这个流程一定要在mConfigStore.read方法之前,因为read方法中会读取ipconfig.txt填充目标mIpConfigurations 。所以放在EthernetTracker构造方法中没有任何问题,因为mConfigStore.read方法是EthernetTracker构造后才调用的,逻辑顺序上要注意,否则达不到静态启动的效果,这里需求方案实现code如下图20所示:
diff --git a/java/com/android/server/ethernet/EthernetTracker.java b/java/com/android/server/ethernet/EthernetTracker.java
index d52742e..028d312 100644
--- a/java/com/android/server/ethernet/EthernetTracker.java
+++ b/java/com/android/server/ethernet/EthernetTracker.java
@@ -46,10 +46,17 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.net.module.util.NetdUtils;
import com.android.server.net.BaseNetworkObserver;
import com.android.server.ethernet.WifiSleepController
import java.io.FileDescriptor;
import java.net.InetAddress;
+import android.net.InetAddresses;
+import java.net.Inet4Address;
+import android.net.LinkAddress;
+import android.net.StaticIpConfiguration;
+import android.os.SystemProperties;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@@ -139,12 +146,67 @@
mConfigStore = new EthernetConfigStore();
+ if (SystemProperties.get("persist.sys.ethernet.enable","1").equals("1")){
+ Log.e(TAG, "Start set eth0 static ipconfig");
+ StaticIpConfiguration mStaticIpConfiguration = new StaticIpConfiguration();
+ String mIpAddress = SystemProperties.get("persist.sys.eth0.ip", "10.10.10.12");
+ String mGateway = SystemProperties.get("persist.sys.eth0.gateway", "10.10.10.1");
+ String mDnsFirst = SystemProperties.get("persist.sys.eth0.dns1", "4.4.4.4");
+ String mDnsSecond = SystemProperties.get("persist.sys.eth0.dns2", "8.8.8.8");
+ String mMask = SystemProperties.get("persist.sys.eth0.netmask", "255.255.255.0");
+ String iface = "eth0";
+
+ Log.e(TAG, "The mMask value is " + mMask + "And int mask value is " + convertNetmaskToCIDR(mMask));
+ int mNetmask = convertNetmaskToCIDR(mMask);
+ Inet4Address inetAddr = getIPv4Address(mIpAddress);
+ int prefixLength = mNetmask;
+ InetAddress gatewayAddr = getIPv4Address(mGateway);
+ InetAddress dnsAddrFirst = getIPv4Address(mDnsFirst);
+ InetAddress dnsAddrSecond = getIPv4Address(mDnsSecond);
+
+ ArrayList<InetAddress> dnsAddrs = new ArrayList<>();
+ dnsAddrs.add(dnsAddrFirst);
+ dnsAddrs.add(dnsAddrSecond);
+ mStaticIpConfiguration = new StaticIpConfiguration.Builder()
+ .setIpAddress(new LinkAddress(inetAddr, prefixLength))
+ .setGateway(gatewayAddr).setDnsServers(dnsAddrs)
+ .build();
+ IpConfiguration ipConfiguration = new IpConfiguration();
+ ipConfiguration.setIpAssignment(IpAssignment.STATIC);
+ ipConfiguration.setProxySettings(ProxySettings.NONE);
+ ipConfiguration.setStaticIpConfiguration(mStaticIpConfiguration);
+ mConfigStore.write(iface, ipConfiguration);
+ }
NetworkCapabilities nc = createNetworkCapabilities(true /* clear default capabilities */);
mFactory = new EthernetNetworkFactory(handler, context, nc);
mFactory.register();
mWifiSleepController = new WifiSleepController(mContext);
}
+ private Inet4Address getIPv4Address(String info){
+ try{
+ return (Inet4Address)InetAddresses.parseNumericAddress(info);
+ }catch (Exception e){
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private int convertNetmaskToCIDR(String netmask) {
+ String[] parts = netmask.split("\\.");
+ int cidr = 0;
+
+ for (String part : parts) {
+ String binaryString = Integer.toBinaryString(Integer.parseInt(part));
+ cidr += binaryString.chars().filter(ch -> ch == '1').count();
+ }
+ return cidr;
+ }
+
void start() {
mConfigStore.read();
图20 静态IP启动需求实现Code
为便于灵活配置,分别使用属性动态维护ip参数,没有对应属性的话则使用默认ip,补充解释Code
(1). getIPv4Address是啥作用?答:ip参数的转换,这里使用Android即定parseNumericAddress统一转换成InetAddress, 因为mStaticIpConfiguration中需要的是这种类型参数,参考同样也是以Setting模块设置IP作为参考处理的
(2). mNetmask掩码是怎么转换的, 点分十进制中255.255.255.0前3位是连续的1,每一位又是8bit, 所以就是3*8=24, CIDR格式就是这么来的,计算每个大块中字符1的个数,这个个数累加后就是对应int类型数,这里通过自封装API通过这个转换规则来实现相应转换算法。思路是split以.分割掩码,分出来4个部分,之后遍历每个子部分, 然后toBinaryString转换成二进制字符串再统计字符1的个数进行返回,这个就是自封装 convertNetmaskToCIDR的实现。
3.2 方法二
相比于方法一,方法二代码修改量很小,由前面部分可知Setting里面有以太网IP设置条目,所以法二就是先去Setting里面设置你期望的ip, 之后会在设备系统目录里直接生成ipconfig.txt,然后把这个ipconfig.txt直接拿出来预置到系统的/system/etc目录里,之后同步修改EthernetConfigStore中读取ipconfig.txt的路径改成system/etc, 这样就直接实现静态IP启动,相比于法一,简单很多,但是不足够灵活,个人本地验证效果一样,读者有兴趣就根据我的描述自行实践下吧。
4. Android7.1实现和Android12.0实现差异部分
个人之前在Android7.1上同样也实现过静态ip启动, 所以当Android12有这个需求时,一开始是准备借鉴Android7.1的静态IP实现,结果发现由于部分差异出现编译问题,故重新又在Setting模块中寻找正确方法,这里补充下差异点。如下图21是Android7.1中的关键差异部分:
Inet4Address inetAddr = getIPv4Address(mIpAddress);
int prefixLength = mNetmask;
InetAddress gatewayAddr =getIPv4Address(mGateway);
InetAddress dnsAddr = getIPv4Address(mDns1);
mStaticIpConfiguration.ipAddress = new LinkAddress(inetAddr, prefixLength);
mStaticIpConfiguration.gateway=gatewayAddr;
mStaticIpConfiguration.dnsServers.add(dnsAddr);
mStaticIpConfiguration.dnsServers.add(getIPv4Address(mDns2));
mIpConfiguration = new IpConfiguration(IpAssignment.STATIC, ProxySettings.NONE,mStaticIpConfiguration,null);
图21 Android7.1关键实现
这种实现在Android12.0上被编译问题拦截掉,
编译问题1:frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetTracker.java:166: error: no suitable constructor found for IpConfiguration(IpAssignment,ProxySettings,StaticIpConfiguration,<null>)
IpConfiguration mIpConfiguration = new IpConfiguration(IpAssignment.STATIC, ProxySettings.NONE,mStaticIpConfiguration,null);
解决方法: IpConfiguration在Android12.0上已经不支持带参构造,需要先无参构造后通过set方法进行对应配置,具体实现已在图20中, 不赘述
编译问题2:frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetTracker.java:165: error: cannot find symbol
mStaticIpConfiguration.ipAddress = new LinkAddress(inetAddr, prefixLength);
symbol: variable ipAddress
location: variable mStaticIpConfiguration of type StaticIpConfiguration
frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetTracker.java:166: error: cannot find symbol
mStaticIpConfiguration.gateway = gatewayAddr;
报错提示StaticIpConfiguration类中找不到相应成员变量,这个问题就需要我们去看下StaticIpConfiguration.java文件中的成员变量定义:
文件路径:
packages/modules/Connectivity/framework/src/android/net/StaticIpConfiguration.java
关键成员变量如图22所示:
public final class StaticIpConfiguration implements Parcelable {
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Nullable
public LinkAddress ipAddress;
//...
//...
}
图22 StaticIpConfiguration关键成员
可以看到 UnsupportedAppUsage标识代表这个不对外公开,所以实例化StaticIpConfiguration时方式也要变更,这时再往下看会发现有个静态内部类Builder,如下图23所示:
public static final class Builder {
private LinkAddress mIpAddress;
private InetAddress mGateway;
private Iterable<InetAddress> mDnsServers;
private String mDomains;
/**
* Set the IP address to be included in the configuration; null by default.
* @return The {@link Builder} for chaining.
*/
public @NonNull Builder setIpAddress(@Nullable LinkAddress ipAddress) {
mIpAddress = ipAddress;
return this;
}
/**
* Set the address of the gateway to be included in the configuration; null by default.
* @return The {@link Builder} for chaining.
*/
public @NonNull Builder setGateway(@Nullable InetAddress gateway) {
mGateway = gateway;
return this;
}
//.....
//.....
}
图23 静态内部类Builder
这里在Android12.0上采用了建造者设计模式,在设计上更为合理,所以构建StaticIpConfiguration对象也和之前不同,解决方式就使用目前设计上期望的建造者模式来实例化StaticIpConfiguration, 具体实现方式如上图20所示
5. 总结
经测试,以太网静态IP启动功能基本正常,但是在后续测试同事手里进行多次重启测试时发现偶现eth0 网卡节点IP缺失,该问题请跳转至Android12.0 问题解决篇之以太网静态IP启动偶现失败问题解决一文观看。
若想要设置其他静态Ip时,则设置对应的系统属性并同时重启即可,因为是做成persist属性,本章节以太网静态启动需求开发结束, 如有不当,欢迎指正。