之前写过一篇《android 8.1 framework层修改以太网静态ip功能》的文章,这篇是在该基础上实现的。之前的功能,只在设备仅支持单个网口的情况下。当大佬拿来一个设备说:这个还可以外接底座,多网口,目前无法上网。我内心是崩溃的。于是我又开启了慢慢的framework源码阅读之旅。因为百度目前没有找到过相关资料,希望我这篇能补缺——我会很开心!
我这边直接从设置以太网开始讲解,之前该怎么做的,请看我上面链接的文章!
全篇涉及到的文件有:
frameworks\base\services\core\java\com\android\server\NetworkManagementService.java
frameworks\opt\net\ethernet\java\com\android\server\ethernet(底下全部文件)
frameworks\base\services\core\java\com\android\server\net\IpConfigStore
先从以太网大致流程说起,当设备开机时,启动服务
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();
}
}
}
启动服务同时也实现了EthernetServiceImpl类,在该类的构造方法中初始化了EthernetConfigStore,去读取以太网配置文件,获取参数。主要代码:
public EthernetServiceImpl(Context context) {
mContext = context;
Log.i(TAG, "Creating EthernetConfigStore");
mEthernetConfigStore = new EthernetConfigStore();
mIpConfiguration = mEthernetConfigStore.readIpAndProxyConfigurations();
Log.i(TAG, "Read stored IP configuration: " + mIpConfiguration);
mTracker = new EthernetNetworkFactory(mListeners);//这个注意,下面会用到
}
再看EthernetConfigStore
public class EthernetConfigStore extends IpConfigStore {
private static final String TAG = "EthernetConfigStore";
private static final String ipConfigFile = Environment.getDataDirectory() +
"/misc/ethernet/ipconfig.txt";
public EthernetConfigStore() {
}
public IpConfiguration readIpAndProxyConfigurations() {
SparseArray networks = readIpAndProxyConfigurations(ipConfigFile);
if (networks.size() == 0) {
Log.w(TAG, "No Ethernet configuration found. Using default.");
return new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
}
if (networks.size() > 1) {
// Currently we only support a single Ethernet interface.
Log.w(TAG, "Multiple Ethernet configurations detected. Only reading first one.");
}
return networks.valueAt(0);
}
public void writeIpAndProxyConfigurations(IpConfiguration config) {
SparseArray networks = new SparseArray();
networks.put(0, config);
writeIpAndProxyConfigurations(ipConfigFile, networks);
}
}
我们看到是他读取地址为Environment.getDataDirectory() + "/misc/ethernet/ipconfig.txt"的文件,我们也可以用adb pull出来看(用处不大)。
我们发现继承的是frameworks\base\services\core\java\com\android\server\net\IpConfigStore.java类,具体怎么读取的是在这里面实现的,可以看看,我就不贴代码了。
然后读取配置文件,调用mImpl.start();方法,我们再分析这个方法的流程走向。
public void start() {
Log.i(TAG, "Starting Ethernet service");
HandlerThread handlerThread = new HandlerThread("EthernetServiceThread");
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper());
mTracker.start(mContext, mHandler);
mStarted.set(true);
}
我们看到mTracker = new EthernetNetworkFactory(mListeners);已经在上面EthernetServiceImpl类里面初始化了,所以,这个时候我们就要跳到EthernetNetworkFactory类里面查看是怎么实现的了。
/**
* Begin monitoring connectivity
*/
public void start(Context context, Handler handler) {
mHandler = handler;
// The services we use.
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
mNMService = INetworkManagementService.Stub.asInterface(b);
mEthernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
// Interface match regex.
mIfaceMatch = context.getResources().getString(
com.android.internal.R.string.config_ethernet_iface_regex);
// Create and register our NetworkFactory.
mFactory = new LocalNetworkFactory(NETWORK_TYPE, context, mHandler.getLooper());
mFactory.setCapabilityFilter(mNetworkCapabilities);
mFactory.setScoreFilter(NETWORK_SCORE);
mFactory.register();
mContext = context;
// Start tracking interface change events.
mInterfaceObserver = new InterfaceObserver();
try {
mNMService.registerObserver(mInterfaceObserver);
} catch (RemoteException e) {
Log.e(TAG, "Could not register InterfaceObserver " + e);
}
// If an Ethernet interface is already connected, start tracking that.
// Otherwise, the first Ethernet interface to appear will be tracked.
mHandler.post(() -> trackFirstAvailableInterface());
}
前面都是初始化获取manager对象,最后handler调用trackFirstAvailableInterface();
public void trackFirstAvailableInterface() {
try {
//读取设备接口列表,包含各种接口
final String[] ifaces = mNMService.listInterfaces();
for (String iface : ifaces) {
//这里排除其他不是eth\d的接口
if (maybeTrackInterface(iface)) {
// We have our interface. Track it.
// 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 (mNMService.getInterfaceConfig(iface).hasFlag("running")) {
updateInterfaceState(iface, true);//只有都匹配了才能真正的上网了。
}
break;
}
}
} catch (RemoteException|IllegalStateException e) {
Log.e(TAG, "Could not get list of interfaces " + e);
}
}
我们还有特别注意上面那个筛选的判断方法
private boolean maybeTrackInterface(String iface) {
// If we don't already have an interface, and if this interface matches
// our regex, start tracking it.
if (!iface.matches(mIfaceMatch) || isTrackingInterface())
return false;
Log.d(TAG, "Started tracking interface " + iface);
setInterfaceUp(iface);
return true;
}
这个是用来过滤只有eth\d的接口才需要走的方法。其他的直接return就行。如果只有一个以太网口,那么就只有eth0,如果是多个网口,那么就是eth0和eth1;
其中还有一个判断的方法
public boolean isTrackingInterface() {
return !TextUtils.isEmpty(mIface);
}
当时我就很疑惑,为什么要判断mIface是否为空,而不是传来的iface。原来最终传给NetworkManagementService的是mIface这个字段,那么他在什么时候赋值的?我们往下面看。
接下来的一个更重要的方法:
private void setInterfaceUp(String iface) {
// Bring up the interface so we get link status indications.
try {
mNMService.setInterfaceUp(iface);
String hwAddr = null;
InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
if (config == null) {
Log.e(TAG, "Null interface config for " + iface + ". Bailing out.");
return;
}
if (!isTrackingInterface()) {
setInterfaceInfo(iface, config.getHardwareAddress());
mNetworkInfo.setIsAvailable(true);
mNetworkInfo.setExtraInfo(mHwAddr);
} else {
Log.e(TAG, "Interface unexpectedly changed from " + iface + " to " + mIface);
mNMService.setInterfaceDown(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 " + mIface + ": " + e);
}
}
其中有一个mNMService.setInterfaceUp(iface);,这里面就涉及到了
frameworks\base\services\core\java\com\android\server\NetworkManagementService.java
这里就是我们最后能跟到的地方了,真正的设置参数连接上网实现,再细看下去我已经无能为力了。这个逻辑google已经写好了,所以我们看看就好。
我们就看看一个普通的方法:
@Override
public void setInterfaceConfig(String iface, InterfaceConfiguration cfg) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
Slog.d(TAG, "Enter setInterfaceConfig, iface=" + iface);
LinkAddress linkAddr = cfg.getLinkAddress();
if (linkAddr == null || linkAddr.getAddress() == null) {
throw new IllegalStateException("Null LinkAddress given");
}
final Command cmd = new Command("interface", "setcfg", iface,
linkAddr.getAddress().getHostAddress(),
linkAddr.getPrefixLength());
for (String flag : cfg.getFlags()) {
cmd.appendArg(flag);
}
try {
mConnector.execute(cmd);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
看了跟没看一样,这里面想看可以自己去深入看看。
我们继续看setInterfaceUp方法后往哪里走。
/**
* Set interface information and notify listeners if availability is changed.
*/
private void setInterfaceInfo(String iface, String hwAddr) {
boolean oldAvailable = isTrackingInterface();
mIface = iface;//这里面终于赋值了。。。。。。
mHwAddr = hwAddr;
boolean available = isTrackingInterface();
mNetworkInfo.setExtraInfo(mHwAddr);
mNetworkInfo.setIsAvailable(available);
if (oldAvailable != available) {
int n = mListeners.beginBroadcast();
for (int i = 0; i < n; i++) {
try {
mListeners.getBroadcastItem(i).onAvailabilityChanged(available);
} catch (RemoteException e) {
// Do nothing here.
}
}
mListeners.finishBroadcast();
}
}
我们看到mIface 被赋值了。最后设置网络信息值,发送广播这个整个判断流程就走完了,然后我们就回到了trackFirstAvailableInterface方法了,就是我们要记住的那个需要修改的方法,红字标记。
当所有条件都符合最后调用的就是
public void startIpManager() {
if (DBG) {
Log.d(TAG, String.format("starting IpManager(%s): mNetworkInfo=%s", mIface,
mNetworkInfo));
}
LinkProperties linkProperties;
IpConfiguration config = mEthernetManager.getConfiguration();
if (config.getIpAssignment() == IpAssignment.STATIC) {
if (!setStaticIpAddress(config.getStaticIpConfiguration())) {
// We've already logged an error.
return;
}
linkProperties = config.getStaticIpConfiguration().toLinkProperties(mIface);
/* } else {
mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr);
IpManager.Callback ipmCallback = new IpManager.Callback() {
@Override
public void onProvisioningSuccess(LinkProperties newLp) {
mHandler.post(() -> onIpLayerStarted(newLp));
}
@Override
public void onProvisioningFailure(LinkProperties newLp) {
mHandler.post(() -> onIpLayerStopped(newLp));
}
@Override
public void onLinkPropertiesChange(LinkProperties newLp) {
mHandler.post(() -> updateLinkProperties(newLp));
}
};
stopIpManager();
mIpManager = new IpManager(mContext, mIface, ipmCallback); */
if (config.getProxySettings() == ProxySettings.STATIC ||
config.getProxySettings() == ProxySettings.PAC) {
// mIpManager.setHttpProxy(config.getHttpProxy());
linkProperties.setHttpProxy(config.getHttpProxy());
}
/* final String tcpBufferSizes = mContext.getResources().getString(
com.android.internal.R.string.config_ethernet_tcp_buffers); */
String tcpBufferSizes = mContext.getResources().getString(
com.android.internal.R.string.config_ethernet_tcp_buffers);
/* if (!TextUtils.isEmpty(tcpBufferSizes)) {
mIpManager.setTcpBufferSizes(tcpBufferSizes);
} */
if (TextUtils.isEmpty(tcpBufferSizes) == false) {
linkProperties.setTcpBufferSizes(tcpBufferSizes);
}
} else {
mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr);
}
/* final ProvisioningConfiguration provisioningConfiguration =
mIpManager.buildProvisioningConfiguration()
.withProvisioningTimeoutMs(0)
.build();
mIpManager.startProvisioning(provisioningConfiguration); */
// }
IpManager.Callback ipmCallback = new IpManager.Callback() {
@Override
public void onProvisioningSuccess(LinkProperties newLp) {
mHandler.post(() -> onIpLayerStarted(newLp));
}
@Override
public void onProvisioningFailure(LinkProperties newLp) {
mHandler.post(() -> onIpLayerStopped(newLp));
}
@Override
public void onLinkPropertiesChange(LinkProperties newLp) {
mHandler.post(() -> updateLinkProperties(newLp));
}
};
stopIpManager();
mIpManager = new IpManager(mContext, mIface, ipmCallback);//此处mIface,最后配置参数
if (config.getProxySettings() == ProxySettings.STATIC ||
config.getProxySettings() == ProxySettings.PAC) {
mIpManager.setHttpProxy(config.getHttpProxy());
}
final String tcpBufferSizes = mContext.getResources().getString(
com.android.internal.R.string.config_ethernet_tcp_buffers);
if (!TextUtils.isEmpty(tcpBufferSizes)) {
mIpManager.setTcpBufferSizes(tcpBufferSizes);
}
if (config.getIpAssignment() == IpAssignment.STATIC) {
mIpManager.startProvisioning(config.getStaticIpConfiguration());
} else {
final ProvisioningConfiguration provisioningConfiguration =
mIpManager.buildProvisioningConfiguration()
.withProvisioningTimeoutMs(0)
.build();
mIpManager.startProvisioning(provisioningConfiguration);
}
}
。整个流程就走完了。好了,我好像还没有讲多个网口。。。。。最重要的来了
我们来看看那个标红的代码:
public void trackFirstAvailableInterface() {
try {
//读取设备接口列表,包含各种接口
final String[] ifaces = mNMService.listInterfaces();
for (String iface : ifaces) {
//这里排除其他不是eth\d的接口
if (maybeTrackInterface(iface)) {
// We have our interface. Track it.
// 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 (mNMService.getInterfaceConfig(iface).hasFlag("running")) {
updateInterfaceState(iface, true);//只有都匹配了才能真正的上网了。
}
break;
}
}
} catch (RemoteException|IllegalStateException e) {
Log.e(TAG, "Could not get list of interfaces " + e);
}
}
我们获取到所有接口时,有多个eth,当eth0走进判断,该方法已经被break了,循环结束,所以,后面你就是有100个网口他也不会再去配置上网的信息了。我上面提到最后传给NetworkManagementService的是mIface,而mIface是在maybeTrackInterface方法逻辑赋值的,我通过log发现,当第一次mIface被赋值以后,一直是eth0,当你eth0没有连接网线时,那么
public boolean isTrackingInterface() {
return !TextUtils.isEmpty(mIface);
}
这个判断方法就一直是true了。那么也不能走完设置上网参数的方法,所以我们最后的修改是在trackFirstAvailableInterface方法
public void trackFirstAvailableInterface() {
try {
final String[] ifaces = mNMService.listInterfaces();
for (String iface : ifaces) {
if (maybeTrackInterface(iface)) {
// We have our interface. Track it.
// 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 (mNMService.getInterfaceConfig(iface).hasFlag("running")) {
updateInterfaceState(iface, true);
}else{
mIface = "";
}
// break;
}
}
} catch (RemoteException|IllegalStateException e) {
Log.e(TAG, "Could not get list of interfaces " + e);
}
}
这样我们就能让其他的网口也设置连接参数。并且当你有两个网口时,第一个网口不是running状态,将mIface置空,当eth1进来时重新赋值,这样就完美实现了eth1的上网了。
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
我看了几天源码,就修改了两行代码。。。。。。。刚接触framework层,比较生疏。
加油!
2019年9月4日18:21:48
今天修改一下上面改的一些导致的bug:
1:两个网口拔插切换还是有问题
2:修改后导致wifi连接后,以太网也会自动连接;
解决方案如果
@@ -172,7 +172,9 @@ class EthernetNetworkFactory {
if (up) {
maybeStartIpManager();
} else {
- mIface = "";
+ if(mIface.matches(mIfaceMatch)){
+ mIface = "";
+ }
stopIpManager();
}
}
@@ -184,7 +186,7 @@ class EthernetNetworkFactory {
mHandler.post(() -> {
Log.d(TAG, "这里也进来了吗: " + iface+";什么情况啊=="+up);
- if(up){
+ if(up && iface != null && iface.matches(mIfaceMatch)){
mIface = iface;
}
updateInterfaceState(iface, up);
解释一下,因为wifi也会走这个类,他的事wlan/,而以太网是eth/
所以要做判断
希望对你有帮助!
今夕是何夕,晚风过花庭~~~~~