前言
朋友们,最近又开始搞 Android P了,同样的以太网静态 IP 是少不了的功能,今天我们就开始来整一下。之前弄6.0 和 8.1 的都 ok 了。
没想到 9.0 改动还是略微有点大的。来来来,先看图。
效果图
上代码
app层
Settings 中的代码和之前的一样就不贴了,可以去看之前的这篇中代码 Android8.1 MTK平台 增加以太网静态IP功能
或者下载这篇的源码资源
framework 层
驱动大哥已经把驱动都搞定了,现在直接插上网线,设备就能上网,网卡图标也正常显示。我们需要控制网卡的开关,先来简单看下流程。
frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetService.java
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(); } } }
EthernetService 继承了系统服务,那自然也就是系统服务,如果挂掉会自动重新创建,严重情况会导致系统重启,我就试出来过。看下当 PHASE_SYSTEM_SERVICES_READY 准备完成,调用 EthernetServiceImpl 的 start()
来看下这个 start() 都干了啥
frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetServiceImpl.java
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); }
主要创建了 EthernetTracker,这个类是 9.0 中新增出来的,用于监听以太网的切换、以太网判断当前网络是否可用等一系列操作。之前 8.1 中都集成在 EthernetNetworkFactory 中,继续跟进
frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetTracker.java
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(); Log.e(TAG, "mIpConfigForDefaultInterface== " + mIpConfigForDefaultInterface); Log.i(TAG, "IpConfiguration size== " + configs.size()); 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); }
mConfigStore 对象用来管理保存的 IpConfigStore 信息,EthernetConfigStore 中通过读取 /misc/ethernet/ipconfig.txt 中保存的信息进行维护一个 ArrayMap<String, IpConfiguration>,根据打印的日志看,start() 中每次获取到的 size 都为 0,基本上没起作用。值得一提的是,在 EthernetTracker 的构造方法中通过解析 config_ethernet_interfaces 字符串也可向 map 中添加初始信息。
EthernetTracker(Context context, Handler handler) { mHandler = handler; // The services we use. IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); mNMService = INetworkManagementService.Stub.asInterface(b); // Interface match regex. mIfaceMatch = context.getResources().getString( com.android.internal.R.string.config_ethernet_iface_regex); // Read default Ethernet interface configuration from resources final String[] interfaceConfigs = context.getResources().getStringArray( com.android.internal.R.array.config_ethernet_interfaces); for (String strConfig : interfaceConfigs) { parseEthernetConfig(strConfig); } mConfigStore = new EthernetConfigStore(); NetworkCapabilities nc = createNetworkCapabilities(true /* clear default capabilities */); mFactory = new EthernetNetworkFactory(handler, context, nc); mFactory.register(); } private void parseEthernetConfig(String configString) { String[] tokens = configString.split(";"); String name = tokens[0]; String capabilities = tokens.length > 1 ? tokens[1] : null; NetworkCapabilities nc = createNetworkCapabilities( !TextUtils.isEmpty(capabilities) /* clear default capabilities */, capabilities); mNetworkCapabilities.put(name, nc); if (tokens.length > 2 && !TextUtils.isEmpty(tokens[2])) { IpConfiguration ipConfig = parseStaticIpConfiguration(tokens[2]); mIpConfigurations.put(name, ipConfig); } }
config_ethernet_interfaces 的初始值位置在
frameworks\base\core\res\res\values\config.xml
<!-- Regex of wired ethernet ifaces --> <string translatable="false" name="config_ethernet_iface_regex">eth\\d</string> <!-- Configuration of Ethernet interfaces in the following format: <interface name|mac address>;[Network Capabilities];[IP config] Where [Network Capabilities] Optional. A comma seprated list of network capabilities. Values must be from NetworkCapabilities#NET_CAPABILITIES_* constants. [IP config] Optional. If empty or not specified - DHCP will be used, otherwise use the following format to specify static IP configuration: ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses> domains=<comma-sep-domains> --> <string-array translatable="false" name="config_ethernet_interfaces"> <!-- <item>eth1;12,13,14,15;ip=192.168.0.10/24 gateway=192.168.0.1 dns=4.4.4.4,8.8.8.8</item> <item>eth2;;ip=192.168.0.11/24</item> --> </string-array>
好了继续回到刚刚的 start() 中,接下来应该调用 trackAvailableInterfaces(),来看下完整的流程,maybeTrackInterface 中先进行 iface 判断,这个指代要使用的网口名称,通过命令 ifconfig -a 可以看到你设备下的所有网口名称。
mIfaceMatch 对应刚刚的 config.xml 中配置的 eth\d, 所以只有是 eth 打头并且 mFactory 中不存在的才能设置以太网连接,这很关键
继续调用 addInterface(), 将 iface 对应的 ipConfiguration 通过 mFactory addInterface,再然后 updateInterfaceState 广播通知当前以太网连接状态 CONNECTED/CONNECTED
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); } } private void maybeTrackInterface(String iface) { if (DBG) Log.i(TAG, "maybeTrackInterface " + iface); // If we don't already track this interface, and if this interface matches // our regex, start tracking it. if (!iface.matches(mIfaceMatch) || mFactory.hasInterface(iface)) { Log.d(TAG, iface + " return "); return; } Log.e(TAG, "maybeTrackInterface " + iface); if (mIpConfigForDefaultInterface != null) { updateIpConfiguration(iface, mIpConfigForDefaultInterface); mIpConfigForDefaultInterface = null; } addInterface(iface); } private void addInterface(String iface) { InterfaceConfiguration config = null; // Bring up the interface so we get link status indications. try { mNMService.setInterfaceUp(iface); config = mNMService.getInterfaceConfig(iface); } catch (RemoteException | IllegalStateException e) { // Either the system is crashing or the interface has disappeared. Just ignore the // error; we haven't modified any state because we only do that if our calls succeed. Log.e(TAG, "Error upping interface " + iface, e); } if (config == null) { Log.e(TAG, "Null interface config for " + iface + ". Bailing out."); return; } final String hwAddress = config.getHardwareAddress(); NetworkCapabilities nc = mNetworkCapabilities.get(iface); if (nc == null) { // Try to resolve using mac address nc = mNetworkCapabilities.get(hwAddress); if (nc == null) { nc = createDefaultNetworkCapabilities(); } } IpConfiguration ipConfiguration = mIpConfigurations.get(iface); if (ipConfiguration == null) { ipConfiguration = createDefaultIpConfiguration(); } Log.d(TAG, "Started tracking interface " + iface); mFactory.addInterface(iface, hwAddress, nc, ipConfiguration); // Note: if the interface already has link (e.g., if we crashed and got // restarted while it was running), we need to fake a link up notification so we // start configuring it. if (config.hasFlag("running")) { updateInterfaceState(iface, true); } } private void updateInterfaceState(String iface, boolean up) { Log.e(TAG, "updateInterfaceState up==" + up); boolean modified = mFactory.updateInterfaceLinkState(iface, up); if (modified) { boolean restricted = isRestrictedInterface(iface); int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { try { if (restricted) { ListenerInfo listenerInfo = (ListenerInfo) mListeners.getBroadcastCookie(i); if (!listenerInfo.canUseRestrictedNetworks) { continue; } } mListeners.getBroadcastItem(i).onAvailabilityChanged(iface, up); } catch (RemoteException e) { // Do nothing here. } } mListeners.finishBroadcast(); } }
知道了 EthernetTracker 的 start() 去连接以太网,那么我们在 EthernetServiceImpl 中增加布尔判断就能控制以太网开关状态。
frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetServiceImpl.java
public class EthernetServiceImpl extends IEthernetManager.Stub { private static final String TAG = "EthernetServiceImpl"; public static final String IS_ETHERNET_OPEN = Settings.IS_ETHERNET_OPEN; public static final String ETHERNET_USE_STATIC_IP = Settings.IS_ETHERNET_STATUC_OPEN; private final Context mContext; private final AtomicBoolean mStarted = new AtomicBoolean(false); private IpConfiguration mIpConfiguration; private final EthernetOpenedObserver mOpenObserver = new EthernetOpenedObserver(); private final EthernetStaticObserver mStaticObserver = new EthernetStaticObserver(); private Handler mHandler; private EthernetTracker mTracker; public EthernetServiceImpl(Context context) { mContext = context; Log.i(TAG, "Creating EthernetConfigStore"); mContext.getContentResolver().registerContentObserver( System.getUriFor(IS_ETHERNET_OPEN), false, mOpenObserver); mContext.getContentResolver().registerContentObserver( System.getUriFor(ETHERNET_USE_STATIC_IP), false, mStaticObserver); } .... 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); mIpConfiguration = mTracker.getDefaultIpConfiguration(); if (getState() == 1) { if (isStatic()) { StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration(); staticIpConfiguration.domains = Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_NETMASK); try { staticIpConfiguration.gateway = InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_GATEWAY)); staticIpConfiguration.ipAddress = new LinkAddress(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_IP)), 24); staticIpConfiguration.dnsServers.add(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_DNS1))); staticIpConfiguration.dnsServers.add(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_DNS2))); }catch (Exception e){ e.printStackTrace(); } mIpConfiguration.ipAssignment = IpAssignment.STATIC; mIpConfiguration.proxySettings = ProxySettings.STATIC; mIpConfiguration.staticIpConfiguration = staticIpConfiguration; } mTracker.start(); mStarted.set(true); } } private boolean isStatic() { Log.e(TAG, "EthernetServiceImpl isStatic() " + Settings.System.getInt(mContext.getContentResolver(),ETHERNET_USE_STATIC_IP,0)); return Settings.System.getInt(mContext.getContentResolver(),ETHERNET_USE_STATIC_IP,0) ==1; } private int getState() { int state = Settings.System.getInt(mContext.getContentResolver(), IS_ETHERNET_OPEN,0); Log.e(TAG, "EthernetServiceImpl getState() " + state); return state; } .... @Override public void setConfiguration(String iface, IpConfiguration config) { if (!mStarted.get()) { Log.w(TAG, "System isn't ready enough to change ethernet configuration"); } enforceConnectivityInternalPermission(); if (mTracker.isRestrictedInterface(iface)) { enforceUseRestrictedNetworksPermission(); } Log.e(TAG, "setConfiguration iface="+iface); // TODO: this does not check proxy settings, gateways, etc. // Fix this by making IpConfiguration a complete representation of static configuration. mTracker.updateIpConfiguration(iface, new IpConfiguration(config)); //add mTracker.removeInterface(iface); mTracker.start(); } .... private final class EthernetOpenedObserver extends ContentObserver { public EthernetOpenedObserver() { super(new Handler()); } @Override public void onChange(boolean selfChange, Uri uri, int userId) { super.onChange(selfChange, uri, userId); Log.i(TAG, "EthernetServiceImpl isEthernetOpen onChange...."); if (getState() == 1) { if (isStatic()) { StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration(); staticIpConfiguration.domains = Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_NETMASK); try { staticIpConfiguration.gateway = InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_GATEWAY)); staticIpConfiguration.ipAddress = new LinkAddress(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_IP)), 24); staticIpConfiguration.dnsServers.add(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_DNS1))); staticIpConfiguration.dnsServers.add(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_DNS2))); } catch (Exception e){ e.printStackTrace(); } mIpConfiguration.ipAssignment = IpAssignment.STATIC; mIpConfiguration.proxySettings = ProxySettings.STATIC; mIpConfiguration.staticIpConfiguration = staticIpConfiguration; } mTracker.start(); mStarted.set(true); }else { mTracker.stop(); } } } private final class EthernetStaticObserver extends ContentObserver { public EthernetStaticObserver() { super(new Handler()); } @Override public void onChange(boolean selfChange, Uri uri, int userId) { super.onChange(selfChange, uri, userId); Log.i(TAG, "EthernetServiceImpl isEthernetStaticOpen onChange...."); if (!isStatic()) { Log.e(TAG, " no static stop and start"); mTracker.recoverDHCPIpConfiguration(); mTracker.stop(); mTracker.start(); mStarted.set(true); } } }
根据 settings 中设置的值 IS_ETHERNET_OPEN 和 ETHERNET_USE_STATIC_IP 判断是否加载,在 EthernetTracker 中新增如下几个方法
frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetTracker.java
//关闭网卡,先更新状态为 false, 再从 mFactory 中移除 eth0, 不然关闭后无法再次打开,因为上面提到的判断 public void stop() { Log.d(TAG, "EthernetTracker stop ethernet..."); updateInterfaceState("eth0", false); android.os.SystemClock.sleep(200); removeInterface("eth0"); } //获取默认的 IpConfiguration,如果不存在则新建一个 DHCP 类型的,根据实际情况修改 ipAssignment 和 proxySettings public IpConfiguration getDefaultIpConfiguration(){ IpConfiguration ipConfiguration = mIpConfigurations.get("eth0"); return ipConfiguration != null ? ipConfiguration : new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null); } //从静态 IP 切换 public void recoverDHCPIpConfiguration(){ mIpConfigurations.put("eth0", new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null)); }
测试发现频繁点击 静态IP 开关时,出现了数组角标越界的情况,应该是 add 和 remove iface导致的,直接将打印 try 一下就可以
2019-10-21 17:01:38.675 1075-1285/? E/AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: EthernetServiceThread java.lang.ArrayIndexOutOfBoundsException: length=2; index=-1 at com.android.internal.util.StateMachine$SmHandler.getCurrentState(StateMachine.java:1151) at com.android.internal.util.StateMachine$SmHandler.access$1300(StateMachine.java:681) at com.android.internal.util.StateMachine.toString(StateMachine.java:2088) at java.lang.String.valueOf(String.java:2896) at java.lang.StringBuilder.append(StringBuilder.java:132) at com.android.server.ethernet.EthernetNetworkFactory$NetworkInterfaceState.toString(EthernetNetworkFactory.java:422) at java.lang.String.valueOf(String.java:2896) at java.lang.StringBuilder.append(StringBuilder.java:132) at com.android.server.ethernet.EthernetNetworkFactory.networkForRequest(EthernetNetworkFactory.java:213) at com.android.server.ethernet.EthernetNetworkFactory.acceptRequest(EthernetNetworkFactory.java:78) at android.net.NetworkFactory.evalRequest(NetworkFactory.java:234) at android.net.NetworkFactory.evalRequests(NetworkFactory.java:253) at android.net.NetworkFactory.handleSetFilter(NetworkFactory.java:204) at android.net.NetworkFactory.handleMessage(NetworkFactory.java:149) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:193) at android.os.HandlerThread.run(HandlerThread.java:65)
frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetNetworkFactory.java
@Override public String toString() { try{ return getClass().getSimpleName() + "{ " + "iface: " + name + ", " + "up: " + mLinkUp + ", " + "hwAddress: " + mHwAddress + ", " + "networkInfo: " + mNetworkInfo + ", " + "networkAgent: " + mNetworkAgent + ", " + "ipClient: " + mIpClient + "," + "linkProperties: " + mLinkProperties + "}"; }catch(Exception e){ e.printStackTrace(); return getClass().getSimpleName() + "{ }"; } }
这样就能实现开头的动图效果了