Android网络框架情景分析之NetworkManagementService和Netd交互深入分析二

Android以太网框架情景分析之NetworkManagementService和netd交互深入分析二



前言

  通过前面篇章Android以太网框架情景分析之NetworkManagementService和netd交互深入分析一我想小伙伴已经对Netd有了一个比较深层次的认知了,Netd一方面接收处理内核上报的网络状态信息然后将相关指令发送给上层,另外一方面接收上层传递下来的指令执行对应的命令。尼玛!这里多次提到的上层究竟是个啥玩意,其实就是我们本章节要讲到的NetworkManagementService服务(该服务的内容非常多,这里我们只重点关注其怎么和Netd交互的)。好吗,不多说了直接开干!

本篇章演示的源码是在Android 7.1 msm8953平台上,其中涉及的源码路径如下所示:

system/netd/server/
	---CommandListener.cpp
	---CommandListener.h
	---main.cpp
	---NetlinkHandler.cpp
	---netd.rc
	---NetlinkManager.cpp
	---DnsProxyListener.cpp
	---MDnsSdListener.cpp
	---FwmarkServer.cpp
	...NetdNativeService.cpp
	...

system/core/libsysutils/src/
	---FrameworkListener.cpp  
	---NetlinkListener.cpp
	---FrameworkCommand.cpp  
	---NetlinkEvent.cpp
	---SocketListener.cpp
	...

frameworks/base/services/java/com/android/server/SystemServer.java
frameworks/base/services/core/java/com/android/server/NetworkManagementService.java
frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetNetworkFactory.java
frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetService.java
frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetServiceImpl.java
frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetConfigStore.java
frameworks/base/services//core/java/com/android/server/ConnectivityService.java
frameworks/base/core/java/android/net/INetworkManagementEventObserver.aidl


一. NetworkManagementService和Netd交互深入分析

  在正式开始分析前,还是把祖传的Netd和NetworkManagementService交互的架构图奉上,镇场子!同时也为了后续的描述更加简洁,后续统一将NetworkManagementService简称为NMService。

Android网络框架情景分析之NetworkManagementService和Netd交互深入分析二_第1张图片


1.1 NetworkManagementService的启动

  地球人都知道NMService服务属于system_server进程,而通过博客Android 系统启动之SystemServer大揭秘下知道,system_server进程会在其启动的startOtherServices阶段启动一系列相关的服务,而这些服务则包括我们今天的主角NMService。其关键代码如下所示:

//SystemServer.java
NetworkManagementService networkManagement = null;
private void startOtherServices() {
          traceBeginAndSlog("StartNetworkManagementService");
          try {
          	
              networkManagement = NetworkManagementService.create(context);//创建NetworkManagementService对象
              ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);//将NetworkManagementService添加到ServiceManager中,供第三方开发者使用
          } catch (Throwable e) {
              reportWtf("starting NetworkManagement Service", e);
          }
          Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);

  接着继续分析NMService的create方法:

	//NetworkManagementService.java
    private NetworkManagementService(Context context, String socket) {
        mContext = context;

        mFgHandler = new Handler(FgThread.get().getLooper());
        PowerManager.WakeLock wl = null; //pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, NETD_TAG);
		/*
		NativeDaemonConnector是Java Framework中一个特别的类,它用于连接指定的socket,并发送和接收
		socket数据。
		此处,"netd"参数代表目标socket。NetdCallbackReceiver为具体的socket连接及消息处理对象。
		1.当Netd连接成功后,NetdCallbackReceiver的onDaemonConnected函数将被调用。
		2.当收到来自Netd的数据后,NetdCallbackReceiver的onEvent函数将被调用。
		NativeDaemonConnector代码比较简单,感兴趣的读者不妨自行阅读。
		*/
        mConnector = new NativeDaemonConnector(
                new NetdCallbackReceiver(), socket, 10, NETD_TAG, 160, wl,
                FgThread.get().getLooper());//NativeDaemonConnector
                
		// 创建一个线程,其Runnable对象就是mConnector
        mThread = new Thread(mConnector, NETD_TAG);

        mDaemonHandler = new Handler(FgThread.get().getLooper());

        Watchdog.getInstance().addMonitor(this);//看来我们的NetworkManagementService服务很重要,都必须使用看门狗服务来管理
    }

    static NetworkManagementService create(Context context, String socket)
            throws InterruptedException {
        final NetworkManagementService service = new NetworkManagementService(context, socket);//直接创建对象
        final CountDownLatch connectedSignal = service.mConnectedSignal;
        if (DBG) Slog.d(TAG, "Creating NetworkManagementService");
        service.mThread.start();//这个会在1.2中具体讲解,
        if (DBG) Slog.d(TAG, "Awaiting socket connection");
        connectedSignal.await();
        if (DBG) Slog.d(TAG, "Connected");
        service.connectNativeNetdService();
        return service;
    }
    private static final String NETD_SERVICE_NAME = "netd";
    public static NetworkManagementService create(Context context) throws InterruptedException {
        return create(context, NETD_SERVICE_NAME);//注意这里的NETD_SERVICE_NAME是"netd"和Netd进程中的"/dev/socket/netd"是对应的
    }

  可以看到NMService的静态方法create主要干了两件事:

  • 创建NMService的实例对象并返回
  • 构建NativeDaemonConnector实例对象mConnector,并启动

1.2 NetworkManagementService如何接收Netd传递的消息

  NetworkManagementService也启动OK了,我们要直奔主题了NetworkManagementService是如何接收Netd传递的消息呢,这个就要轮到我们的NativeDaemonConnector的登场了,NativeDaemonConnector在和Netd的交互中起到了丰功至伟的作用。基本全程都是它在操盘!至于为啥说它是丰功至伟,详解章节1.3。


1.3 NativeDaemonConnector处理Netd的消息

//NativeDaemonConnector.java
final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
		...
	    NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
            int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl,
            Looper looper) {
        mCallbacks = callbacks;//这个回调接口很重要
        mSocket = socket;//这里传入的参数是“netd”
        mResponseQueue = new ResponseQueue(responseQueueSize);
        mWakeLock = wl;
        if (mWakeLock != null) {
            mWakeLock.setReferenceCounted(true);
        }
        mLooper = looper;
        mSequenceNumber = new AtomicInteger(0);
        TAG = logTag != null ? logTag : "NativeDaemonConnector";
        mLocalLog = new LocalLog(maxLogSize);
    }
    ...
}

  可以看到NativeDaemonConnector实现了Runnable,而在前面章节1.1中以NativeDaemonConnector的实例为参数构建了mThread 线程,然后调用了其start方法,那么接着肯定的执行了NativeDaemonConnector的run方法。那还能咋样呢,接着分析继续干呗。

	//NativeDaemonConnector.java
    public void run() {
        mCallbackHandler = new Handler(mLooper, this);//构建Handler对象,并且该Handler对象的Callback是NativeDaemonConnector自己(因为它有实现了Handler.Callback接口),

        while (true) {
            try {
                listenToSocket();//开始循环执行,详见章节1.3.1
            } catch (Exception e) {
                loge("Error in NativeDaemonConnector: " + e);
                SystemClock.sleep(5000);
            }
        }
    }

1.3.1 NativeDaemonConnector.listenToSocket

  在正式开始分析listenToSocket源码之前,如果各位小伙伴对LocalSocket还不是很了解的可以先看看Android Framework层和Native层通过LocalSocket实现通信

	//NativeDaemonConnector.java
    private void listenToSocket() throws IOException {
        LocalSocket socket = null;

        try {
            socket = new LocalSocket();//
            LocalSocketAddress address = determineSocketAddress();

            socket.connect(address);//和Netd的“netd”建立连接

            InputStream inputStream = socket.getInputStream();
            synchronized (mDaemonLock) {
                mOutputStream = socket.getOutputStream();
            }

            mCallbacks.onDaemonConnected();//通知回调接口,和Netd的"netd"的LocalSocket通信通道已经建立成功了
 FileDescriptor[] fdList = null;
            byte[] buffer = new byte[BUFFER_SIZE];
            int start = 0;

            while (true) {
                int count = inputStream.read(buffer, start, BUFFER_SIZE - start);//读取Netd发送过来的内容
                if (count < 0) {
                    loge("got " + count + " reading with start = " + start);
                    break;
                }
                fdList = socket.getAncillaryFileDescriptors();

                // Add our starting point to the count and reset the start.
                count += start;
                start = 0;

                for (int i = 0; i < count; i++) {
                    if (buffer[i] == 0) {
                        // Note - do not log this raw message since it may contain
                        // sensitive data
                        final String rawEvent = new String(
                                buffer, start, i - start, StandardCharsets.UTF_8);

                        boolean releaseWl = false;
                        try {
                            final NativeDaemonEvent event =
                                    NativeDaemonEvent.parseRawEvent(rawEvent, fdList);//将Netd发送过来的数据封装成NativeDaemonEvent对象

                            log("RCV <- {" + event + "}");

                            if (event.isClassUnsolicited()) {
                                // TODO: migrate to sending NativeDaemonEvent instances
                                if (mCallbacks.onCheckHoldWakeLock(event.getCode())
                                        && mWakeLock != null) {
                                    mWakeLock.acquire();
                                    releaseWl = true;
                                }
                                Message msg = mCallbackHandler.obtainMessage(
                                        event.getCode(), uptimeMillisInt(), 0, event.getRawEvent());
                                if (mCallbackHandler.sendMessage(msg)) {//通过Handler发送消息,然后在该Handler对应handleMessage中进行相关的处理,而我们的NativeDaemonConnector实现了Handler.Callback的详见章节1.2.3
                                    releaseWl = false;
                                }
                            } else {
                                mResponseQueue.add(event.getCmdNumber(), event);
                            }
                        } catch (IllegalArgumentException e) {
                            log("Problem parsing message " + e);
                        } finally {
                            if (releaseWl) {
                                mWakeLock.release();
                            }
                        }

                        start = i + 1;
                    }
                }

                if (start == 0) {
                    log("RCV incomplete");
                }

                // We should end at the amount we read. If not, compact then
                // buffer and read again.
                if (start != count) {
                    final int remaining = BUFFER_SIZE - start;
                    System.arraycopy(buffer, start, buffer, 0, remaining);
                    start = remaining;
                } else {
                    start = 0;
                }
            }
        } catch (IOException ex) {
            loge("Communications error: " + ex);
            throw ex;
        } finally {
            synchronized (mDaemonLock) {
                if (mOutputStream != null) {
                    try {
                        loge("closing stream for " + mSocket);
                        mOutputStream.close();
                    } catch (IOException e) {
                        loge("Failed closing output stream: " + e);
                    }
                    mOutputStream = null;
                }
            }

            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException ex) {
                loge("Failed closing socket: " + ex);
            }
        }
    }

1.3.2 NativeDaemonConnector.handleMessage

	//NativeDaemonConnector.java
    public boolean handleMessage(Message msg) {
        final String event = (String) msg.obj;
        final int start = uptimeMillisInt();
        final int sent = msg.arg1;
        try {
            if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {//通知回调接口,可以通过onEvent进行处理了,详见章节1.4
                log(String.format("Unhandled event '%s'", event));
            }
        } catch (Exception e) {
            loge("Error handling '" + event + "': " + e);
        } finally {
            if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
                mWakeLock.release();
            }
            final int end = uptimeMillisInt();
            if (start > sent && start - sent > WARN_EXECUTE_DELAY_MS) {
                loge(String.format("NDC event {%s} processed too late: %dms", event, start - sent));
            }
            if (end > start && end - start > WARN_EXECUTE_DELAY_MS) {
                loge(String.format("NDC event {%s} took too long: %dms", event, end - start));
            }
        }
        return true;
    }

  通过前面的章节我们知道NativeDaemonConnector实现了Handler.Callback接口,所以前面的Hander发送的消息最终由其处理,而通过代码看到最终在handleMessage方法中会调用回调接口的mCallbacks.onEvent方法进行处理。而我们这里的mCallbacks就是在NMService中构建NativeDaemonConnector时传递过来的回调接口类NetdCallbackReceiver。


1.4 NativeDaemonConnector接口回调类

  通过前面章节分析可知,NativeDaemonConnector接收到的Netd传递的消息最后经由NetdCallbackReceiver回调类的的onEvent方法进行处理,而在该方法中根据不同的消息,调用对应的notifyXXXX方法,而notifyXXXX方法最后通知mObservers实观察者感兴趣的内容。而这里我们以以太网络接口更改为例来说明。

//NetworkManagementService.java
 private class NetdCallbackReceiver implements INativeDaemonConnectorCallbacks {
        @Override
        public void onDaemonConnected() {
			...
        }

        @Override
        public boolean onCheckHoldWakeLock(int code) {
            return code == NetdResponseCode.InterfaceClassActivity;
        }
 @Override
        public boolean onEvent(int code, String raw, String[] cooked) {
            String errorMessage = String.format("Invalid event from daemon (%s)", raw);
            switch (code) {
            case NetdResponseCode.InterfaceChange:
                    /*
                     * a network interface change occured
                     * Format: "NNN Iface added "
                     *         "NNN Iface removed "
                     *         "NNN Iface changed  "
                     *         "NNN Iface linkstatus  "
                     */
                    if (cooked.length < 4 || !cooked[1].equals("Iface")) {
                        throw new IllegalStateException(errorMessage);
                    }
                    if (cooked[2].equals("added")) {
                        notifyInterfaceAdded(cooked[3]);//网络接口添加,譬如以太网模块的加载
                        return true;
                    } else if (cooked[2].equals("removed")) {
                        notifyInterfaceRemoved(cooked[3]);
                        return true;
                    } else if (cooked[2].equals("changed") && cooked.length == 5) {
                        notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up"));
                        return true;
                    } else if (cooked[2].equals("linkstate") && cooked.length == 5) {
                        notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up"));
                        return true;
                    }
                    throw new IllegalStateException(errorMessage);
                    // break;
            default: break;
            }
            return false;
        }
    }

    private void notifyInterfaceAdded(String iface) {
        final int length = mObservers.beginBroadcast();
        try {
            for (int i = 0; i < length; i++) {
                try {
                    mObservers.getBroadcastItem(i).interfaceAdded(iface);//这里的mObservers即注册的观察者,有可能是WIFI,Ethernet,Phone等,而关于观察者的注册这个会在以太网络和NetworkManagementService交互中讲到,小解请看1.4.1
                } catch (RemoteException | RuntimeException e) {
                }
            }
        } finally {
            mObservers.finishBroadcast();
        }
    }

1.4.1 NetworkManagementService.registerObserver

	//NetworkManagementService.java
    private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
            new RemoteCallbackList<INetworkManagementEventObserver>();//关于INetworkManagementEventObserver的详见1.4.2
            
    @Override
    public void registerObserver(INetworkManagementEventObserver observer) {
        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
        mObservers.register(observer);
    }

    @Override
    public void unregisterObserver(INetworkManagementEventObserver observer) {
        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
        mObservers.unregister(observer);
    }

  这里我们可以看到mObservers通过registerObserver注入的,其中我们的以太网也属于这个行列,至于怎么注册的这个我们这里先不分析,后面会有准们章节分析。这里我们只需要知道WIFI,Ethernet,Phone都是观察者会注册如此,当有感兴趣的事情发生时候立马响应!

1.4.2 INetworkManagementEventObserver

//INetworkManagementEventObserver.aidl
interface INetworkManagementEventObserver {
    /**
     * Interface configuration status has changed.
     *
     * @param iface The interface.
     * @param up True if the interface has been enabled.
     */
    void interfaceStatusChanged(String iface, boolean up);

    /**
     * Interface physical-layer link state has changed.  For Ethernet,
     * this method is invoked when the cable is plugged in or unplugged.
     *
     * @param iface The interface.
     * @param up  True if the physical link-layer connection signal is valid.
     */
    void interfaceLinkStateChanged(String iface, boolean up);

    /**
     * An interface has been added to the system
     *
     * @param iface The interface.
     */
    void interfaceAdded(String iface);

    /**
     * An interface has been removed from the system
     *
     * @param iface The interface.
     */
    void interfaceRemoved(String iface);


    /**
     * An interface address has been added or updated.
     *
     * @param iface The interface.
     * @param address The address.
     */
    void addressUpdated(String iface, in LinkAddress address);

    /**
     * An interface address has been removed.
     *
     * @param iface The interface.
     * @param address The address.
     */
    void addressRemoved(String iface, in LinkAddress address);

    /**
     * A networking quota limit has been reached. The quota might not
     * be specific to an interface.
     *
     * @param limitName The name of the limit that triggered.
     * @param iface The interface on which the limit was detected.
     */
    void limitReached(String limitName, String iface);

    /**
     * Interface data activity status is changed.
     *
     * @param iface The interface.
     * @param active  True if the interface is actively transmitting data, false if it is idle.
     * @param tsNanos Elapsed realtime in nanos when the state of the network interface changed.
     */
    void interfaceClassDataActivityChanged(String label, boolean active, long tsNanos);

    /**
     * Message is received from network interface.
     *
     * @param message The message
     */
    void interfaceMessageRecevied(String message);

    /**
     * Information about available DNS servers has been received.
     *
     * @param iface The interface on which the information was received.
     * @param lifetime The time in seconds for which the DNS servers may be used.
     * @param servers The IP addresses of the DNS servers.
     */
    void interfaceDnsServerInfo(String iface, long lifetime, in String[] servers);

    /**
     * A route has been added or updated.
     */
    void routeUpdated(in RouteInfo route);

    /**
     * A route has been removed.
     */
    void routeRemoved(in RouteInfo route);
    
  }

  这里可以看到INetworkManagementEventObserver是一个aidl类型的接口类,既然是aidl保证了其可以通过Binder跨进程传输数据,其源码路径为frameworks/base//core/java/android/net/INetworkManagementEventObserver.aidl。可以看到它的接口方法主要是用于通知各种网络状态的改变!


1.5 NetworkManagementService如何接收Netd传递的消息小结

  通过前面章节的分析,我们对于NMService如何接收Netd传递的消息大伙应该心中已经有谱了,但是我们还是趁热打铁小结一波:

  • 当Netd进程接收到内核上报消息然后就要传递给上层,这里的上层就是NMService了,然后我们NMService借助NativeDaemonConnector工具类封装的LocalSocket完成了和Netd进程的消息传递

  • 消息传递到NMService之后,NMService还是要击鼓传花继续传递,然后NMService通过INetworkManagementEventObserver回调类将消息传递给已经注册的观察者门

  整个流程已将分析OK了,如果小伙伴还是有什么疑问或者有不清晰的地方,请参照下面的时序图在撸一篇:

Android网络框架情景分析之NetworkManagementService和Netd交互深入分析二_第2张图片


1.6 NetworkManagementService如何向Netd传递消息

  通过前面的章节我们解决了一个世纪大难题即NMService如何接收Netd传递过来的消息,而我们知道NMService和Netd的交互是双向的,那么我们下面即将要下一个高峰冲锋了即NMService如何向Netd传递消息。而这其中也离不开NativeDaemonConnector的功劳了,都是它都是它来显示的!而NMService最终向Netd传递消息的终极接口如下:

	//NetworkManagementService.java
    public void setInterfaceConfig(String iface, InterfaceConfiguration cfg) {
        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
        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);//详见章节1.7
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        }
    }

即将传递过来的消息经过层层封装成Command 对象,最后借助NativeDaemonConnector工具类对象mConnector的execute方法将消息传递给Netd进程处理。


1.7 NativeDaemonConnector如何向Netd传递消息

  不来虚的直接上代码!

    public NativeDaemonEvent execute(long timeoutMs, String cmd, Object... args)
            throws NativeDaemonConnectorException {
        final NativeDaemonEvent[] events = executeForList(timeoutMs, cmd, args);
        if (events.length != 1) {
            throw new NativeDaemonConnectorException(
                    "Expected exactly one response, but received " + events.length);
        }
        return events[0];
    }

    public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
            throws NativeDaemonConnectorException {
        if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
                    + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
        }

        final long startTime = SystemClock.elapsedRealtime();

        final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();

        final StringBuilder rawBuilder = new StringBuilder();
        final StringBuilder logBuilder = new StringBuilder();
        final int sequenceNumber = mSequenceNumber.incrementAndGet();

        makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);

        final String rawCmd = rawBuilder.toString();
        final String logCmd = logBuilder.toString();

        log("SND -> {" + logCmd + "}");

        synchronized (mDaemonLock) {
            if (mOutputStream == null) {
                throw new NativeDaemonConnectorException("missing output stream");
            } else {
                try {
                    mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
                } catch (IOException e) {
                    throw new NativeDaemonConnectorException("problem sending command", e);
                }
            }
        }

        NativeDaemonEvent event = null;
        do {
            event = mResponseQueue.remove(sequenceNumber, timeoutMs, logCmd);
            if (event == null) {
                loge("timed-out waiting for response to " + logCmd);
                throw new NativeDaemonTimeoutException(logCmd, event);
            }
            if (VDBG) log("RMV <- {" + event + "}");
            events.add(event);
        } while (event.isClassContinue());

        final long endTime = SystemClock.elapsedRealtime();
        if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
            loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
        }

        if (event.isClassClientError()) {
            throw new NativeDaemonArgumentException(logCmd, event);
        }
        if (event.isClassServerError()) {
            throw new NativeDaemonFailureException(logCmd, event);
        }

        return events.toArray(new NativeDaemonEvent[events.size()]);
    }

  这里可以看到最后会调用到NativeDaemonConnector的executeForList方法,这里里面的处理就比较简单了借助前面已经互通的LocalSocket将相关的命令发送给Netd进程进行相关的处理。整个的传递消息时序图如下所示:

Android网络框架情景分析之NetworkManagementService和Netd交互深入分析二_第3张图片



小结

  NMService和Netd的交互过程分析完成,至此Android以太网框架情景分析之NetworkManagementService和Netd三者交互逻辑分析三分之二已经完成,还剩下最后的一小部分即Ethernet端如何借助NMService和Netd进行交互的,这个会在后面的篇章中讲述到。好了,万分不舍,也只能是下期见了!希望各位能多多点赞,或者评论,拍砖亦可!

  关于Ethernet端如何借助NMService和Netd进行交互的,详见博客EthernetServiceImpl和NetworkManagementService交互深入分析

你可能感兴趣的:(Android网络框架情景分析之NetworkManagementService和Netd交互深入分析二)