android 8.1 framework层Ethernet多个以太网口切换连接实现流程

之前写过一篇《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<IpConfiguration> 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<IpConfiguration> networks = new SparseArray<IpConfiguration>();
        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/

所以要做判断

希望对你有帮助!

今夕是何夕,晚风过花庭~~~~~

猜你喜欢

转载自blog.csdn.net/qq_33796069/article/details/94578825