在前两节简单介绍了连接管理的大致框架,数据链接的准备工作,包括APN的初始化与默认APN使能,DcTracker的构造,包括各种事件的注册等工作。但是数据链接的打开不止是只有用户主动去打开,Android可以提供数据业务的对象主要有,移动数据网络、WIFI、蓝牙、网线等,这些连接本身都可以独立使用,但是对于用户来说,每一时刻又最多只能使用一种方式接入网络,那么当这些功能同时打开时,比如即使用户打开了移动数据连接,但是又打开了wifi,那么只要wifi畅通,移动数据链接是不会用于上网的,那究竟如何选择最佳的接入环境呢?这就需要提供一个能够动态管理他们的打开与断开的功能,Android专门设计了一套管理方法来实现上面的这种机制,包括ConnectivityManager、ConnectivityService、NetworkAgent等对象之间的关系以及消息流走向,本节在这些知识的基础上介绍连接管理的核心机制,即连接管理中的评分机制,其中ConnectivityService是管理员身份,没种网络都会去向它注册,网络的使用权全靠它来分配。

连接管理通过一个评分机制来实现不同接入方式的选择。具体来说就是,每一种上网方式在初始化时,都向ConnectivityService标明自己网络的分值(比如数据连接50,WIFI60,蓝牙69,网线70),当有更高分数的网络就绪时,就将当前分值低的连接断开。而当当前网络被断开时,就寻找当前就绪的其他网络连接,选取分值高的进行接入。并且,每一个网络接入时,都会进行有效性检测,如果检测不通过,将会被扣掉一定分数,此时该网络的优先级也会相应的下降。下面我们利用三个部分来分析评分机制的原理:
1、NetworkFactory
2、NetworkAgent
3、NetworkMonitor
其中NetworkFactory是每一种网络持有一个,比如WIFI和Telephony会分别注册一个,但是NetworkAgent和NetworkMonitor是一种数据类型就会有一个,比如数据连接总的APN TYPE有8种,其中任意一种链接上之后都会各注册一个。

1、NetworkFactory

NetworkFactory直译就是网络工厂,开机之后每种网络都必须注册自己的NetworkFactory,NetworkFactory的作用是用来创建NetworkAgent,同时作为ConnectivityService与网络之间的通讯枢纽

private DctController(PhoneProxy[] phones) {for (int i = 0; i < mPhoneNum; ++i) {// Register for radio state changePhoneBase phoneBase = (PhoneBase)mPhones[i].getActivePhone();updatePhoneBaseForIndex(i, phoneBase);}
}    private void updatePhoneBaseForIndex(int index, PhoneBase phoneBase) {phoneBase.getServiceStateTracker().registerForDataConnectionAttached(mRspHandler,EVENT_DATA_ATTACHED + index, null);phoneBase.getServiceStateTracker().registerForDataConnectionDetached(mRspHandler,EVENT_DATA_DETACHED + index, null);mNetworkFilter[index] = new NetworkCapabilities();mNetworkFilter[index].addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL);mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA);mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_IA);mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_RCS);mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_XCAP);mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);mNetworkFilter[index].addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);mNetworkFactory[index] = new TelephonyNetworkFactory(this.getLooper(),mPhones[index].getContext(), "TelephonyNetworkFactory", phoneBase,mNetworkFilter[index]);mNetworkFactory[index].setScoreFilter(50);mNetworkFactoryMessenger[index] = new Messenger(mNetworkFactory[index]);cm.registerNetworkFactory(mNetworkFactoryMessenger[index], "Telephony");
}

可以看出来一个NetworkFactory 支持多种网络类型(NetworkCapabilities),网络类型与APN的TYPE相对应。
以移动数据网络为例,TelephonyNetworkFactory 将会继承NetworkFactory ,并重写其中两个重要的方法,needNetworkFor和releaseNetworkFor,这两个方法就是ConnectivityService与移动网络之间桥梁,分别负责请求当前网络和断开当前网络。

private class TelephonyNetworkFactory extends NetworkFactory {protected void needNetworkFor(NetworkRequest networkRequest, int score) {// figure out the apn type and enable itif (!SubscriptionManager.isUsableSubIdValue(mPhone.getSubId())) {mPendingReq.put(networkRequest.requestId, networkRequest);return;}if (getRequestPhoneId(networkRequest) == mPhone.getPhoneId()) { DcTrackerBase dcTracker =((PhoneBase)mPhone).mDcTracker;String apn = apnForNetworkRequest(networkRequest);if (dcTracker.isApnSupported(apn)) {requestNetwork(networkRequest, dcTracker.getApnPriority(apn));}} else {mPendingReq.put(networkRequest.requestId, networkRequest);}}  protected void releaseNetworkFor(NetworkRequest networkRequest) {if (!SubscriptionManager.isUsableSubIdValue(mPhone.getSubId())) {mPendingReq.remove(networkRequest.requestId);return;}if (getRequestPhoneId(networkRequest) == mPhone.getPhoneId()) { DcTrackerBase dcTracker =((PhoneBase)mPhone).mDcTracker;String apn = apnForNetworkRequest(networkRequest);if (dcTracker.isApnSupported(apn)) {releaseNetwork(networkRequest);}}}

再看NetworkFactory 的注册cm.registerNetworkFactory(mNetworkFactoryMessenger[index], "Telephony");其中mNetworkFactoryMessenger是一个包装了mNetworkFactory的Messenger对象,这个主要是建立AsyncChannel通道时用。

@ConnectivityService.java
public void registerNetworkFactory(Messenger messenger, String name) {enforceConnectivityInternalPermission();NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel());mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
}

handleRegisterNetworkFactory处理EVENT_REGISTER_NETWORK_FACTORY消息

private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {mNetworkFactoryInfos.put(nfi.messenger, nfi);nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);
}

在这里,ConnectivityService做了两个事情:
1、将新注册的NetworkFactoryInfo 保存到mNetworkFactoryInfos中;
2、利用刚才创建的AsyncChannel向NetworkAgent发起单向连接请求;
nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);即利用传入的Messenger对象建立起ConnectivityService与NetworkFactory的通讯通道,ConnectivityService后续的消息都将通过这个asyncChannel传入到数据网络中的NetworkFactory。
当asyncChannel通道建立成功后ConnectivityService会收到CMD_CHANNEL_HALF_CONNECTED消息。

@Override
public void handleMessage(Message msg) {NetworkInfo info;switch (msg.what) {case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {handleAsyncChannelHalfConnect(msg);break;}
}
private void handleAsyncChannelHalfConnect(Message msg) {AsyncChannel ac = (AsyncChannel) msg.obj;if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {  //此时是链接的是NetworkFactory,走这个pathif (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {// A network factory has connected.  Send it all current NetworkRequests.for (NetworkRequestInfo nri : mNetworkRequests.values()) {if (nri.isRequest == false) continue;NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,(nai != null ? nai.getCurrentScore() : 0), 0, nri.request);}} else {mNetworkFactoryInfos.remove(msg.obj);}} else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {}
}

此时是链接的是NetworkFactory,走这个path,mNetworkFactoryInfos是在handleRegisterNetworkFactory时保存的。
在这里,ConnectivityService通过AsyncChannel通道向当前的NetworkFactory发起CMD_REQUEST_NETWORK的请求,需要注意的是,该请求所附带的第二个参数选择,由于当前处于初始化阶段,因此当前的mNetworkForRequestId中为空,也就是说此时传递的第二个参数必然为0。
我们接下来看NetworkFactory收到该请求时的处理:

@NetworkFactory.java
public void handleMessage(Message msg) {switch (msg.what) {case CMD_REQUEST_NETWORK: {handleAddRequest((NetworkRequest)msg.obj, msg.arg1);break;}}
}
private void handleAddRequest(NetworkRequest request, int score) {NetworkRequestInfo n = mNetworkRequests.get(request.requestId);if (n == null) {if (DBG) log("got request " + request + " with score " + score);n = new NetworkRequestInfo(request, score);mNetworkRequests.put(n.request.requestId, n);} else {n.score = score;}evalRequest(n);
}

接下来评估网络评分,是需要链接网络还是断开网路

private void evalRequest(NetworkRequestInfo n) {if (n.requested == false && n.score < mScore &&n.request.networkCapabilities.satisfiedByNetworkCapabilities(mCapabilityFilter) && acceptRequest(n.request, n.score)) {needNetworkFor(n.request, n.score);n.requested = true;} else if (n.requested == true &&(n.score > mScore || n.request.networkCapabilities.satisfiedByNetworkCapabilities(mCapabilityFilter) == false || acceptRequest(n.request, n.score) == false)) {releaseNetworkFor(n.request);n.requested = false;}
}

该逻辑就是整个网络评价系统最关键的地方,如果NetworkRequestInfo没有被requested过,并且其分值(n.score)小于当前NetworkFactory自己的分值(mScore),那么就说明,当前NetworkFactory所处的网络优先级高于其他网络的优先级,就会触发当前NetworkFactory所在网络的needNetworkFor()流程,也就是连接建立流程,并将标记NetworkRequestInfo.requested=true。
当NetworkRequestInfo被requested过(也就是当前网络被needNetworkFor过),此时如果再次收到请求,并且携带的新score大于当前NetworkFactory所处网络的mScore,那么就说明当前NetworkFactory所在网络优先级已经不是最高,需要将其releaseNetworkFor掉,并标记NetworkRequestInfo.requested=false。

evalRequest中调用TelephonyNetworkFactory 重写的needNetworkFor或者releaseNetworkFor,分别是链接网络和断开网络,后续的流程如下图(请求网络的情况)

这里写图片描述

在此数据链接的NetworkFactory算是创建完毕,并将自己注册到ConnectivityService中。

2、NetworkAgent

前面提到NetworkFactory是在系统初始化时就被创建,而NetworkAgent是在真正接入网络时才会创建,NetworkAgent的创建在DataConnection状态机里的DcActiveState状态时。

private class DcActiveState extends State {@Override public void enter() {// If we were retrying there maybe more than one, otherwise they'll only be one.notifyAllOfConnected(Phone.REASON_CONNECTED);mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED,mNetworkInfo.getReason(), null);mNetworkInfo.setExtraInfo(mApnSetting.apn);updateTcpBufferSizes(mRilRat);final NetworkMisc misc = new NetworkMisc();misc.subscriberId = mPhone.getSubscriberId();mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),"DcNetworkAgent", mNetworkInfo, makeNetworkCapabilities(), mLinkProperties,50, misc);}
}
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {super(looper);mContext = context;ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),new LinkProperties(lp), new NetworkCapabilities(nc), score, misc);
}

当网络链接完成之后,就会新建一个DcNetworkAgent,接着分析NetworkAgent的构造,和NetworkFactory类似,也是将自己注册到ConnectivityService中去,继续看registerNetworkAgent

public void registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,LinkProperties linkProperties, NetworkCapabilities networkCapabilities,int currentScore, NetworkMisc networkMisc) {NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),new NetworkInfo(networkInfo), new LinkProperties(linkProperties),new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler,new NetworkMisc(networkMisc), mDefaultRequest);synchronized (this) {nai.networkMonitor.systemReady = mSystemReady;}mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
}private void handleRegisterNetworkAgent(NetworkAgentInfo na) {mNetworkAgentInfos.put(na.messenger, na);assignNextNetId(na);na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger);NetworkInfo networkInfo = na.networkInfo;na.networkInfo = null;updateNetworkInfo(na, networkInfo);
}

在这里,ConnectivityService做了三个事情:
1、将新注册的NetworkAgentInfo保存到mNetworkAgentInfos中;
2、利用刚才创建的AsyncChannel向NetworkAgent发起单向连接请求;
3、更新最新的NetworkAgentInfo状态;

@Override
public void handleMessage(Message msg) {NetworkInfo info;switch (msg.what) {case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {handleAsyncChannelHalfConnect(msg);break;}
}

以上流程和NetworkFactory注册时几乎一模一样的模式

private void handleAsyncChannelHalfConnect(Message msg) {AsyncChannel ac = (AsyncChannel) msg.obj;if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {  }  else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {  //此时是链接的是NetworkAgent,走这个pathif (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {// A network agent has requested a connection.  Establish the connection.mNetworkAgentInfos.get(msg.replyTo).asyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);} }
}

唯一的区别是在handleAsyncChannelHalfConnect中这里,当ConnectivityService与NetworkAgent之间单向通道建立完成后,又发起了双向通道的请求,此时在NetworkAgent端,将会收到CMD_CHANNEL_FULL_CONNECTION的消息,建立双向通道的目的是,有时候网络也需要通过AsyncChannel向ConnectivityService发送消息。至此,NetworkAgent的初始化完毕。
现在的问题是NetworkAgent如何影响网络链接的?
NetworkAgent提供了两种方法更新评分管理:
1、sendNetworkScore

public void sendNetworkScore(int score) {queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new Integer(score));
}

2、sendNetworkInfo

public void sendNetworkInfo(NetworkInfo networkInfo) {queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo));
}

先来分析第二种情况,比如移动数据网络的断开时就会调用此方法:

@DataConnection.java
private class DcActiveState extends State {public void exit() {mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,mNetworkInfo.getReason(), mNetworkInfo.getExtraInfo());mNetworkAgent.sendNetworkInfo(mNetworkInfo);mNetworkAgent = null;}
}

接着就会进入ConnectivityService

@Override
public void handleMessage(Message msg) {NetworkInfo info;case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: {NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);info = (NetworkInfo) msg.obj;updateNetworkInfo(nai, info);break;}
}
private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {if (state == NetworkInfo.State.CONNECTED && !networkAgent.created) {} else if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.SUSPENDED) {networkAgent.asyncChannel.disconnect();        }

由于是断开数据网络,因此这里是断开AsyncChannel,从而进入

AsyncChannel.CMD_CHANNEL_DISCONNECTED@Overridepublic void handleMessage(Message msg) {NetworkInfo info;switch (msg.what) {case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {handleAsyncChannelDisconnected(msg);break;}}
private void handleAsyncChannelDisconnected(Message msg) {  NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);  if (nai != null) {  //删掉当前NetworkAgent对象  mNetworkAgentInfos.remove(msg.replyTo);  final ArrayList<NetworkAgentInfo> toActivate = new ArrayList<NetworkAgentInfo>();  for (int i = 0; i < nai.networkRequests.size(); i++) {  NetworkRequest request = nai.networkRequests.valueAt(i);  NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);  if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {  mNetworkForRequestId.remove(request.requestId);  //将0分更新到各个NetworkFactory中  sendUpdatedScoreToFactories(request, 0);  }  }  }
}
private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, int score) {for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, 0,networkRequest);}
}

在这里,由于当前连接是断开状态,因此其分值必然为0,这样就把他的0分值通知到各个NetworkFactory中,由NetworkFactory判断是否需要开启自己的网络,通知方法同样是CMD_REQUEST_NETWORK,也就是说,无论是直接更新NetworkAgent中的分数,还是更新NetworkAgent的状态,最终都会触发NetworkFactory中的评分机制。

3、NetworkMonitor

NetworkMonitor的构造是在注册NetworkAgent,构造NetworkAgentInfo是创建的,其实质ping网络是在updateNetworkInfo中,细节不分析,但是NetworkMonitor对网络可用性的评分是有影响的,即当网络链接上之后,会去ping当前网络是否可用,如果不可用则会影响getCurrentScore获取的分数值,getCurrentScore是每次网络评分获取的分数的必经之路:

private int getCurrentScore(boolean pretendValidated) {int score = currentScore;if (!everValidated && !pretendValidated) score -= UNVALIDATED_SCORE_PENALTY;if (score < 0) score = 0;if (networkMisc.explicitlySelected) score = EXPLICITLY_SELECTED_NETWORK_SCORE;return score;
}

当一个网络连接建立时,系统将用该连接Ping一个Google的网站来判断该连接是否真的可以上网,如果不可以,那么就会扣掉该网络40分,从而可能导致该网络的评分低于其他网络评分
如果是用户指定了网络那么分数直接等于EXPLICITLY_SELECTED_NETWORK_SCORE(100分)
至此网络评分就分析完毕

链接:https://www.jianshu.com/p/541a21bf82d6

Android 网络评分机制相关推荐

  1. 【Android】Android网络评分机制简单总结

    文章参考于: 三.Android 网络评分机制 - 简书在前两节简单介绍了连接管理的大致框架,数据链接的准备工作,包括APN的初始化与默认APN使能,DcTracker的构造,包括各种事件的注册等工作 ...

  2. android网络重试机制,okhttp源码解析(四):重试机制

    前言 这一篇我们分析okhttp的重试机制,一般如果网络请求失败,我们会考虑连续请求多次,增大网络请求成功的概率,那么okhttp是怎么实现这个功能的呢? 正文 首先还是回到之前的Intercepto ...

  3. [RK3288][Android6.0] WiFi之NetworkFactory形成的评分机制

    Platform: Rockchip OS: Android 6.0 Kernel: 3.10.92 NetworkFactory作为网络评分机制中一个重要角色而存在,每个模块实现需要继承Networ ...

  4. android网络的评分机制、连接国内ap wifi不回连问题

    前言: 本文介绍了android下网络的评分机制,同时分析wifi连接国内ap时,重新打开wifi后,wifi不回连ap的问题,并提供解决方法. android下可以有多种网络存在,如:wifi.mo ...

  5. Android连接管理的评分机制(WIFI,Ethernet,BT,移动数据

    Android可以支持:移动数据网络.WIFI.蓝牙.网线等,这些连接本身都可以独立使用,连接管理通过一个评分机制来实现不同接入方式的选择.具体来说就是,每一种上网方式在初始化时,都向Connecti ...

  6. (一百九十六)Android Q 学习WiFi的评分机制(三)

    前言:之前在(一百九十六)Android Q 学习WiFi的评分机制(二)梳理了CS对WiFi score变化的处理,主要是rematchAllNetworksAndRequests方法中的处理,其中 ...

  7. Android P WiFi自动连接评分机制

    1.WifiConnectivityManager的初始化 frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiState ...

  8. Android 8.0/9.0 wifi 自动连接评分机制

    今天了解了一下Wifi自动连接时的评分机制,总结如下: WifiConnectivityManager的初始化: /frameworks/opt/net/wifi/service/java/com/a ...

  9. Android Framework层播放器评分机制

    本文涉及源码版本为:Oreo 8.0.0_r4 /frameworks/av/media/libmedia/mediaplayer.cpp /frameworks/av/include/media/m ...

  10. Android应用后台网络管控机制

    应用后台网络管控机制 概述 在维护手管应用时,经常遇到与应用后台网络控制相关的问题,在解决这些问题的过程中,学习了下应用后台网络控制的流程以及一些日志的分析方法,现在把它总结一下,方便自己以及他人的学 ...

最新文章

  1. Spring Boot 多版本更新,紧急修复 RFD 安全漏洞
  2. 三菱d700变频器接线图_昆明市三菱恒压供水变频器接线图
  3. List Tuple Dictionary 区别
  4. Android --- Serializable 接口与 Parcelable 接口的使用方法和区别,怎么选择?
  5. python基础(part1)--注释/变量/del语句
  6. 有关Spring缓存性能的更多信息
  7. apache禁止多目录运行php文件下载,Nginx Apache下如何禁止指定目录运行PHP脚本
  8. C#在控制台工程中嵌入winform窗体
  9. 操作系统面试相关总结
  10. sql的内连接、左连接、右连接
  11. RK3288 Android7.1软件开发指南
  12. 离散小波变换wavedec matlab,MATLAB小波变换指令及其功能介绍(超级有用)
  13. C#程序设计--控制台程序输出上下三角形和菱形
  14. Sofa memcached client
  15. 详解vue中数据传递(父传子、子传父、兄弟之间以及vuex)代码附上
  16. 电脑xls图标未正常显示
  17. 实现外网Ping通WSL(网卡桥接方式实现)
  18. 标准成本还是实际成本 成本核算标准选择
  19. 跟我一起写 Makefile
  20. win10python安装配置selenium

热门文章

  1. SharePoint Designer中无法显示任何列表
  2. c语言的编译器还真是不好理解...
  3. IIS连接oralce数据提示“System.Data.OracleClient 需要 Oracle 客户端软件 8.1.7 或更高版本”...
  4. android -------- 打开本地浏览器或指定浏览器加载,打电话,打开第三方app
  5. SharePoint 2013 创建web应用程序报错This page can’t be displayed
  6. 执行对象cocos2d-x 2.x action动作整理集合
  7. Object类的wait和notify详解
  8. redis 哨兵的原理
  9. gluoncv 目标检测,训练自己的数据集
  10. worldcloud库的使用