Android network框架分析目录

Android network框架分析之EthernetService启动流程
Android network框架分析之NetworkFactory与NetworkAgent深入分析
Android network框架分析之NetworkManagementService和netd交互深入分析(一)
Android network框架分析之NetworkManagementService和netd交互深入分析(二)
Android network框架分析之EthernetServiceImpl和NetworkManagementService交互深入分析
Android network框架分析之AsyncChannel原理分析以及实操演练
Android 9.0(P)适配以太网功能


Android network框架分析之NetworkManagementService和netd交互深入分析(二)

  • 0. 引言
  • 1. NetworkManagementService和Netd交互深入分析
    • 1.1 NetworkManagementService的启动
    • 1.2 NetworkManagementService如何接收Netd传递的消息
    • 1.3 NativeDaemonConnector处理Netd的消息
      • 1.3.1 NativeDaemonConnector.listenToSocket
      • 1.3.2 NativeDaemonConnector.handleMessage
    • 1.4 NativeDaemonConnector接口回调类
      • 1.4.1 NetworkManagementService.registerObserver
      • 1.4.2 INetworkManagementEventObserver
    • 1.5 NetworkManagementService如何接收Netd传递的消息小结
    • 1.6 NetworkManagementService如何向Netd传递消息
    • 1.7 NativeDaemonConnector如何向Netd传递消息
  • 2. 小结

0. 引言

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

注意: 本篇章演示的源码是Android 7.1,其中涉及的源码路径如下所示:

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

1. NetworkManagementService和Netd交互深入分析

  在正式开始分析前,还是把祖传的Netd和NetworkManagementService交互的架构图奉上,镇场子!

1.1 NetworkManagementService的启动

  地球人都知道NetworkManagementService服务属于system_server进程,而通过博客源码详解Android 9.0§ 系统启动流程之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);

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

 //NetworkManagementService.javaprivate 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对象就是mConnectormThread = 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();启动 NMService 中的一个线程if (DBG) Slog.d(TAG, "Awaiting socket connection");connectedSignal.await();//connectedSignal 用于等待某个事情的发生。此处是等待 mThread 完成初始化工作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.javapublic 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

 //NativeDaemonConnector.javaprivate 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 datafinal 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 instancesif (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.3releaseWl = 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.javapublic 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.4log(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就是在NetworkManagementService中构建NativeDaemonConnector时传递过来的回调接口类NetdCallbackReceiver。

1.4 NativeDaemonConnector接口回调类

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

//NetworkManagementService.javaprivate class NetdCallbackReceiver implements INativeDaemonConnectorCallbacks {@Overridepublic void onDaemonConnected() {...}@Overridepublic boolean onCheckHoldWakeLock(int code) {return code == NetdResponseCode.InterfaceClassActivity;}@Overridepublic 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 <name>"*         "NNN Iface removed <name>"*         "NNN Iface changed <name> <up/down>"*         "NNN Iface linkstatus <name> <up/down>"*/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.javaprivate final RemoteCallbackList<INetworkManagementEventObserver> mObservers =new RemoteCallbackList<INetworkManagementEventObserver>();//关于INetworkManagementEventObserver的详见1.4.2@Overridepublic void registerObserver(INetworkManagementEventObserver observer) {mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);mObservers.register(observer);}@Overridepublic 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进程接收到内核上报消息然后就要传递给上层,这里的上层就是NetworkManagementService了,然后我们NetworkManagementService借助NativeDaemonConnector工具类封装的LocalSocket完成了和Netd进程的消息传递

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

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

1.6 NetworkManagementService如何向Netd传递消息

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

 //NetworkManagementService.javapublic 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进程进行相关的处理。整个的传递消息时序图如下所示:


2. 小结

  NetworkManagementService和Netd的交互过程分析完成,至此Android以太网框架情景分析之NetworkManagementService和Netd三者交互逻辑分析三分之二已经完成,还剩下最后的一小部分即Ethernet端如何借助NMService和Netd进行交互的,这个会在后面的篇章中讲述到。

Android network框架分析之NetworkManagementService和netd交互深入分析(二)相关推荐

  1. Android DRM框架分析

    Android DRM框架分析 1. DRM框架 2.DRM架构 3.DRM插件 4. 实现 5.DRM插件详情 6.MediaDrm 7.MediaCrypto 8.参考链接 1. DRM框架 An ...

  2. Android WIFI框架分析(1)

    趁做Android WIFI驱动移植,对Android WIFI框架做了深刻的分析,并做此文档共同学习. 对上层WIFI的应用,基本流程为:(1)WIFI初始化  (2)Wifi启动      (3) ...

  3. MTK Android Led框架分析

    1 驱动部分 这部分主要根据驱动源码的初始化部分进行分析 1.1 mtk_leds_drv 路径:/kernel-4.14/drivers/misc/mediatek/leds/mtk_leds_dr ...

  4. android输入法框架分析,Android与iOS输入法开发框架比较谈

    对于任何一个使用手机的人,有一样工具是不可能缺少的,它既不是微信之类的社交工具,也不是支付宝之类的金融工具(事实上这两个都越界了),而是输入法这样的输入工具.更重要的是,输入法还是一种特权工具,因为它 ...

  5. Android Framework框架分析

    转自:微点阅读  https://www.weidianyuedu.com/content/2617738210126.html Android framework analysis (partI z ...

  6. Android RIL框架分析

    1.RIL框架 RIL,Radio Interface Layer.本层为一个协议转换层,提供Android Telephony与无线通信设备之间的抽象层. Android RIL位于Telephon ...

  7. Android灯光系统框架分析

    首先别人的APP要能直接访问到你写的硬件或者不经过任何修改的APP能直接使用你的驱动,就需要使用系统自带的JNI,所以我们需要写出符合系统自带JNI文件的HAL文件和驱动程序,下面具体分析一个这个HA ...

  8. android输入法框架分析,Android输入法架构.ppt

    Android输入法架构 Android输入法架构 裴润升 oppo开发三部 输入法 为系统中其他模块提供输入功能的模块 1 硬键盘 2 软键盘 3 手写 4 语音输入 问题: 输入法和应用分属不同的 ...

  9. android nfc框架分析,Android NFC架构分析

    原创:木头月亮 来自:http://blog.csdn.net/mutouyueliang/archive/2011/03/08/6232028.aspx Android中对NFC的实现代码分布在如下 ...

  10. android 魅族下拉悬停,魅族Flyme大交互之二:下拉悬停与被拉长的按钮

    下拉悬停听起来就是个很厉害的角色,但实际上它非常"单纯". 前面已经提到,侧滑菜单中,选项是从屏幕横向中线开始,向上下排布,虽然这在美观与易用之间找到了平衡点,但当侧滑菜单中内容过 ...

最新文章

  1. android 高德地图纠偏,高德地图纠偏算法(android ,ios)
  2. 为什么说特斯拉在自动驾驶上比Waymo更占优势
  3. AcWing 164. 可达性统计
  4. 为什么Go中有的自定义error会导致内存溢出
  5. php位运算符与逻辑运算_位运算符及PHP中位运算的应用笔记
  6. 正则表达式RegExp对象
  7. 每日算法系列【kentln供题】模糊的数字
  8. Python3合并ts文件
  9. react18 学习(一)
  10. 一份招聘公告暴露英特尔外包芯片计划
  11. 误删的文件不在回收站如何找回?2招任选,完成恢复
  12. 火爆的keychron机械键盘,你还没有入手?【重点:附键盘选购建议】
  13. FTP服务器文件自动上传、下载(bat)
  14. 教你删除鼠标右键菜单,清理“新建”菜单的方法!
  15. 就在尽头的下雨天,迷糊的前方
  16. 康泰克采样器完整版-Native Instruments Kontakt v6.3.1 Full WiN
  17. PHP数据类型与数组
  18. 编程英语:常见代码错误 error 语句学习(16)
  19. (一)关于UGUI怪物多行血条实现————DNF怪物血条
  20. shiro反序列化漏洞修复

热门文章

  1. TCP/IP协议都有哪几层协议
  2. MATLAB之方程组求解(八)
  3. Android 中this的用法
  4. python图像锐化滤波_Laplacian滤波器实现并用于图像锐化
  5. AMD GPU任务调度(1)—— 用户态分析
  6. java中,HashMap为什么每次扩容的倍数是2,而不是1.5或者2.5?
  7. 论文选题的原则、来源与方法
  8. 2006技术盘点 多项无线技术被高估
  9. JS 平方 开方 笔记
  10. (阿里云笔记)购置域名+云解析DNS域名