簡(jiǎn)介
WifiScanningService用于自動(dòng)掃描wlan
源碼
7.1
服務(wù)端和客戶端通信的案例
WifiScanner 與 WifiScanningService的通信--AsyncChannel
WifiScanner構(gòu)造方法
public WifiScanner(Context context, IWifiScanner service, Looper looper) {
mContext = context;
mService = service;
Messenger messenger = null;
try {
messenger = mService.getMessenger();//獲取了WifiScannerService的Handler
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
if (messenger == null) {
throw new IllegalStateException("getMessenger() returned null! This is invalid.");
}
mAsyncChannel = new AsyncChannel();
mInternalHandler = new ServiceHandler(looper);
mAsyncChannel.connectSync(mContext, mInternalHandler, messenger);
// We cannot use fullyConnectSync because it sends the FULL_CONNECTION message
// synchronously, which causes WifiScanningService to receive the wrong replyTo value.
mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);//建立起通信
}
詳細(xì)分析
1.WifiScanningService開(kāi)機(jī)啟動(dòng)
com.android.server.SystemServer
mSystemServiceManager.startService(
"com.android.server.wifi.scanner.WifiScanningService");
2.WifiScanningService狠半。其實(shí)現(xiàn)類(lèi)為WifiScanningServiceImpl
public class WifiScanningService extends SystemService {
static final String TAG = "WifiScanningService";
private final WifiScanningServiceImpl mImpl;
private final HandlerThread mHandlerThread;
public WifiScanningService(Context context) {
super(context);
Log.i(TAG, "Creating " + Context.WIFI_SCANNING_SERVICE);
mHandlerThread = new HandlerThread("WifiScanningService");
mHandlerThread.start();
mImpl = new WifiScanningServiceImpl(getContext(), mHandlerThread.getLooper(),
WifiScannerImpl.DEFAULT_FACTORY, BatteryStatsService.getService(),
WifiInjector.getInstance());
}
@Override
public void onStart() {
Log.i(TAG, "Publishing " + Context.WIFI_SCANNING_SERVICE);
publishBinderService(Context.WIFI_SCANNING_SERVICE, mImpl);
}
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
Log.i(TAG, "Starting " + Context.WIFI_SCANNING_SERVICE);
mImpl.startService();
}
}
}
3.實(shí)現(xiàn)類(lèi)分析:WifiScanningServiceImpl
1)adb查看:adb shell dumpsys wifiscanner
2)com.android.server.wifi.scanner.WifiScanningServiceImpl
public void startService() {
mClientHandler = new ClientHandler(mLooper);
mBackgroundScanStateMachine = new WifiBackgroundScanStateMachine(mLooper);
mWifiChangeStateMachine = new WifiChangeStateMachine(mLooper);
mSingleScanStateMachine = new WifiSingleScanStateMachine(mLooper);
mPnoScanStateMachine = new WifiPnoScanStateMachine(mLooper);
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(
WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED);
if (DBG) localLog("SCAN_AVAILABLE : " + state);
if (state == WifiManager.WIFI_STATE_ENABLED) {
mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_LOADED);
mSingleScanStateMachine.sendMessage(CMD_DRIVER_LOADED);
mPnoScanStateMachine.sendMessage(CMD_DRIVER_LOADED);
} else if (state == WifiManager.WIFI_STATE_DISABLED) {
mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED);
mSingleScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED);
mPnoScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED);
}
}
}, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE));
mBackgroundScanStateMachine.start();
mWifiChangeStateMachine.start();
mSingleScanStateMachine.start();
mPnoScanStateMachine.start();
}
3)狀態(tài)的改變通過(guò)監(jiān)聽(tīng)廣播WifiManager.WIFI_SCAN_AVAILABLE(wifi_scan_available)
注:
廣播的狀態(tài)跟WifiStateMachine.DriverStartedState綁定在一起
final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_ENABLED);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
4.具體分析廣播觸發(fā)導(dǎo)致的業(yè)務(wù)分發(fā)。以打開(kāi)wifi為例
當(dāng)接受到信息為WifiManager.WIFI_STATE_ENABLED時(shí)缝左,處理如下:
單獨(dú)從WifiSingleScanStateMachine狀態(tài)機(jī)說(shuō)起:
1)狀態(tài)4:
DefaultState --- DriverStartedState --- IdleState
--- ScanningState
2)當(dāng)接收到廣播(打開(kāi)wifi發(fā)送的廣播)所帶值為WifiManager.WIFI_STATE_ENABLED
mSingleScanStateMachine.sendMessage(CMD_DRIVER_LOADED);
a.默認(rèn)初始狀態(tài)為DefaultState成肘,處理消息CMD_DRIVER_LOADED
WifiScanningServiceImpl.SingleScanStateMachine.DefaultState
class DefaultState extends State {
······
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_DRIVER_LOADED:
transitionTo(mIdleState);//處理消息時(shí)卖局,轉(zhuǎn)化到新的狀態(tài)
return HANDLED;
······
}
}
}
1)transitionTo(mIdleState)
先退后進(jìn)。
退:默認(rèn)DefaultState双霍,無(wú)可退
進(jìn):DriverStartedState.enter --> IdleState.enter
class IdleState extends State {
@Override
public void enter() {
tryToStartNewScan();//執(zhí)行掃描
}
···
}
void tryToStartNewScan() {
······
if (mScannerImpl.startSingleScan(settings, this)) {//調(diào)用mScannerImpl砚偶,執(zhí)行掃描動(dòng)作。mScannerImpl = mScannerImplFactory.create(mContext, mLooper, mClock);即SupplicantWifiScannerImpl
······
transitionTo(mScanningState);//執(zhí)行成功洒闸,則切換狀態(tài)
} else {
······
}
}
此方法分兩部分分析:
11)第一部分:mScannerImpl.startSingleScan(settings, this)
SupplicantWifiScannerImpl
public boolean startSingleScan(WifiNative.ScanSettings settings,
WifiNative.ScanEventHandler eventHandler) {
if (eventHandler == null || settings == null) {
Log.w(TAG, "Invalid arguments for startSingleScan: settings=" + settings
+ ",eventHandler=" + eventHandler);
return false;
}
if (mPendingSingleScanSettings != null
|| (mLastScanSettings != null && mLastScanSettings.singleScanActive)) {
Log.w(TAG, "A single scan is already running");
return false;
}
synchronized (mSettingsLock) {
mPendingSingleScanSettings = settings;
mPendingSingleScanEventHandler = eventHandler;
processPendingScans();//真正處理的掃描業(yè)務(wù)
return true;
}
}
private void processPendingScans() {
synchronized (mSettingsLock) {
······
if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive)
&& !allFreqs.isEmpty()) {
······
boolean success = mWifiNative.scan(freqs, hiddenNetworkIdSet);//WifiNative是跟wpa_supplicant的接口
if (success) {
······
} else {
······
}
} else if (isHwPnoScanRequired()) {
····
}
}
}
com.android.server.wifi.WifiNative
scan --> scanWithParams --> doBooleanCommand --> doBooleanCommandNative
private boolean scanWithParams(String freqList, String hiddenNetworkIdList) {
StringBuilder scanCommand = new StringBuilder();
scanCommand.append("SCAN TYPE=ONLY");//可關(guān)注此日志染坯。TAG:WifiNative-wlan0, SCAN TYPE=
if (freqList != null) {
scanCommand.append(" freq=" + freqList);
}
if (hiddenNetworkIdList != null) {
scanCommand.append(" scan_id=" + hiddenNetworkIdList);
}
return doBooleanCommand(scanCommand.toString());
}
com_android_server_wifi_WifiNative.cpp
android_net_wifi_doBooleanCommand --> doBooleanCommand --> doCommand
wifi.c(hardware/libhardware_legacy/wifi/wifi.c)
wifi_command --> wifi_send_command --> wpa_ctrl_request
wpa_supplicant
wpa_ctrl_request
至此,wpa_supplicant掃描業(yè)務(wù)已經(jīng)分析結(jié)束丘逸,問(wèn)題:掃描所得的結(jié)果单鹿,怎么被掃描服務(wù)獲取呢?
我們知道WifiMonitor是用來(lái)監(jiān)聽(tīng)wpa_supplicant數(shù)據(jù)改變深纲,而其他對(duì)象需要被WifiMonitor通知仲锄,則要調(diào)用WifiMonitor.registerHandler劲妙。
SupplicantWifiScannerImpl調(diào)用mWifiNative.scan,那是不是SupplicantWifiScannerImpl有WifiMonitor的registerHandler昼窗?
com.android.server.wifi.scanner.SupplicantWifiScannerImpl
public SupplicantWifiScannerImpl(Context context, WifiNative wifiNative,
ChannelHelper channelHelper, Looper looper, Clock clock) {
······
mEventHandler = new Handler(looper, this);
······
WifiMonitor.getInstance().registerHandler(mWifiNative.getInterfaceName(),
WifiMonitor.SCAN_FAILED_EVENT, mEventHandler);
WifiMonitor.getInstance().registerHandler(mWifiNative.getInterfaceName(),
WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
}
具體WifiMonitor怎么監(jiān)聽(tīng)怎么分發(fā)機(jī)制是趴,這里我們先不提,可關(guān)注:WifiService之WifiMonitor深入分析澄惊。
WifiMonitor.SCAN_RESULTS_EVENT代表掃描所得的結(jié)果的通道
public boolean handleMessage(Message msg) {
switch(msg.what) {
······
case WifiMonitor.SCAN_RESULTS_EVENT:
mAlarmManager.cancel(mScanTimeoutListener);
pollLatestScanData();//拉取最新的wifi列表數(shù)據(jù)
processPendingScans();
break;
default:
// ignore unknown event
}
return true;
}
private void pollLatestScanData() {
synchronized (mSettingsLock) {
if (mLastScanSettings == null) {
// got a scan before we started scanning or after scan was canceled
return;
}
if (DBG) Log.d(TAG, "Polling scan data for scan: " + mLastScanSettings.scanId);
ArrayList<ScanDetail> nativeResults = mWifiNative.getScanResults();//從wpa_supplicant拉取數(shù)據(jù)
······
if (mLastScanSettings.backgroundScanActive) {
·······
}
if (mLastScanSettings.singleScanActive
&& mLastScanSettings.singleScanEventHandler != null) {
······
mLastScanSettings.singleScanEventHandler
.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);//通知WifiScanningServiceImpl.WifiSingleScanStateMachine
}
if (mLastScanSettings.hwPnoScanActive
&& mLastScanSettings.pnoScanEventHandler != null) {
······
}
mLastScanSettings = null;
}
}
此方法分兩部分分析:
111)mWifiNative.getScanResults()
com.android.server.wifi.WifiNative
getScanResults --> getRawScanResults --> doStringCommandWithoutLogging --> doStringCommandNative
private String getRawScanResults(String range) {
return doStringCommandWithoutLogging("BSS RANGE=" + range + " MASK=0x29d87");////可關(guān)注此日志唆途。TAG:WifiNative-wlan0, BSS RANGE=
}
com_android_server_wifi_WifiNative.cpp
android_net_wifi_doStringCommand --> doStringCommand --> doCommand
wifi.c(hardware/libhardware_legacy/wifi/wifi.c)
wifi_command --> wifi_send_command --> wpa_ctrl_request
wpa_supplicant
wpa_ctrl_request
112)mLastScanSettings.singleScanEventHandler
.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE)
com.android.server.wifi.scanner.WifiScanningServiceImpl
public void onScanStatus(int event) {
if (DBG) localLog("onScanStatus event received, event=" + event);
switch(event) {
case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE:
sendMessage(CMD_SCAN_RESULTS_AVAILABLE);//狀態(tài)機(jī)的業(yè)務(wù)分發(fā)
break;
······
}
}
此時(shí)分析12)價(jià)值來(lái)了
12)第二部分:transitionTo(mScanningState)
com.android.server.wifi.scanner.WifiScanningServiceImpl
先退后進(jìn)。
退:無(wú)掸驱,因?yàn)镾canningState和IdleState共同一DriverStartedState
進(jìn):ScanningState.enter
class ScanningState extends State {
private WorkSource mScanWorkSource;
@Override
public void enter() {
······
}
@Override
public void exit() {
······
}
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_SCAN_RESULTS_AVAILABLE:
mWifiMetrics.incrementScanReturnEntry(
WifiMetricsProto.WifiLog.SCAN_SUCCESS,
mActiveScans.size());
reportScanResults(mScannerImpl.getLatestSingleScanResults());//把結(jié)果通知出去
mActiveScans.clear();
transitionTo(mIdleState);
return HANDLED;
······
}
}
}
結(jié)合112)可知肛搬,最后來(lái)到processMessage的CMD_SCAN_RESULTS_AVAILABLE
void reportScanResults(ScanData results) {
······
WifiScanner.ParcelableScanData parcelableAllResults =
new WifiScanner.ParcelableScanData(allResults);
for (RequestInfo<Void> entry : mSingleScanListeners) {
logCallback("singleScanResults", entry.clientInfo, entry.handlerId,
describeForLog(allResults));
entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableAllResults);
}
}
WifiScanningServiceImpl.ExternalClientInfo.reportEvent
public void reportEvent(int what, int arg1, int arg2, Object obj) {
if (!mDisconnected) {
mChannel.sendMessage(what, arg1, arg2, obj);//把數(shù)據(jù)發(fā)送給客戶端
}
}
這里說(shuō)完了掃描的服務(wù)的處理業(yè)務(wù)。接下來(lái)的關(guān)鍵在于客戶端的業(yè)務(wù)處理
5.客戶端怎么監(jiān)聽(tīng)服務(wù)端數(shù)據(jù)的變化毕贼。以自動(dòng)連接wifi為例
1)mSingleScanListeners為客戶端的監(jiān)聽(tīng)者温赔。它是怎么收集監(jiān)聽(tīng)者呢?
com.android.server.wifi.scanner.WifiScanningServiceImpl
弄清楚回到客戶端(WifiScanner)與服務(wù)端(WifiScanningServiceImpl)通信的具體方法AsyncChannel鬼癣,就明白:
private class ClientHandler extends Handler {
ClientHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
······
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {//客戶端和服務(wù)端建立連接時(shí)陶贼,進(jìn)行的初始化操作
ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo);
if (client != null) {
logw("duplicate client connection: " + msg.sendingUid + ", messenger="
+ msg.replyTo);
client.mChannel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
return;
}
AsyncChannel ac = new AsyncChannel();
ac.connected(mContext, this, msg.replyTo);
client = new ExternalClientInfo(msg.sendingUid, msg.replyTo, ac);
client.register();
ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
AsyncChannel.STATUS_SUCCESSFUL);
localLog("client connected: " + client);
return;
}
······
case WifiScanner.CMD_REGISTER_SCAN_LISTENER:
logScanRequest("registerScanListener", ci, msg.arg2, null, null, null);
mSingleScanListeners.addRequest(ci, msg.arg2, null, null);//客戶端怎么處理呢?
replySucceeded(msg);
break;
······
}
}
2)客戶端WifiScanner怎么處理待秃?
a.客戶端監(jiān)聽(tīng)
android.net.wifi.WifiScanner
private class ServiceHandler extends Handler {
ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
···
Object listener = getListener(msg.arg2);//這里獲取監(jiān)聽(tīng)對(duì)象拜秧。這里跟一般的監(jiān)聽(tīng)方法不一樣
···
switch (msg.what) {
···
case CMD_SCAN_RESULT :
if (DBG) Log.d(TAG, "CMD_SCAN_RESULT ");
((ScanListener) listener).onResults(
((ParcelableScanData) msg.obj).getResults());
break;
}
}
監(jiān)聽(tīng)對(duì)象的來(lái)源?
因WifiConnectivityManager包裹了WifiScanner,我們先看WifiConnectivityManager
com.android.server.wifi.WifiConnectivityManager
public WifiConnectivityManager(Context context, WifiStateMachine stateMachine,
WifiScanner scanner, WifiConfigManager configManager, WifiInfo wifiInfo,
WifiQualifiedNetworkSelector qualifiedNetworkSelector,
WifiInjector wifiInjector, Looper looper, boolean enable) {
mStateMachine = stateMachine;
mScanner = scanner;
·······
// Register for all single scan results
mScanner.registerScanListener(mAllSingleScanListener);//查看mAllSingleScanListener章郁⊥鞯客戶端進(jìn)行數(shù)據(jù)監(jiān)聽(tīng)
·······
}
b.客戶端對(duì)監(jiān)聽(tīng)數(shù)據(jù)進(jìn)行處理
private class AllSingleScanListener implements WifiScanner.ScanListener {
······
@Override
public void onResults(WifiScanner.ScanData[] results) {//數(shù)據(jù)回調(diào)處
······
boolean wasConnectAttempted = handleScanResults(mScanDetails, "AllSingleScanListener");//自動(dòng)連接的具體方法
······
}
·······
}
private boolean handleScanResults(List<ScanDetail> scanDetails, String listenerName) {
localLog(listenerName + " onResults: start QNS");
WifiConfiguration candidate =
mQualifiedNetworkSelector.selectQualifiedNetwork(false,
mUntrustedConnectionAllowed, scanDetails,
mStateMachine.isLinkDebouncing(), mStateMachine.isConnected(),
mStateMachine.isDisconnected(),
mStateMachine.isSupplicantTransientState());//獲取是否有可連接的網(wǎng)絡(luò)。WifiQualifiedNetworkSelector涉及wifi評(píng)分機(jī)制暖庄。有興趣的可自行分析
mWifiLastResortWatchdog.updateAvailableNetworks(
mQualifiedNetworkSelector.getFilteredScanDetails());
mWifiMetrics.countScanResults(scanDetails);
if (candidate != null) {//如果網(wǎng)絡(luò)存在聊替,則連接
localLog(listenerName + ": QNS candidate-" + candidate.SSID);
connectToNetwork(candidate);
return true;
} else {
return false;
}
}
private void connectToNetwork(WifiConfiguration candidate) {
······
if (currentConnectedNetwork != null
&& (currentConnectedNetwork.networkId == candidate.networkId
|| currentConnectedNetwork.isLinked(candidate))) {
localLog("connectToNetwork: Roaming from " + currentAssociationId + " to "
+ targetAssociationId);
mStateMachine.autoRoamToNetwork(candidate.networkId, scanResultCandidate);
} else {
localLog("connectToNetwork: Reconnect from " + currentAssociationId + " to "
+ targetAssociationId);
mStateMachine.autoConnectToNetwork(candidate.networkId, scanResultCandidate.BSSID);//調(diào)用WifiStateMachine的自動(dòng)連接
}
}
com.android.server.wifi.WifiStateMachine
public void autoConnectToNetwork(int networkId, String bssid) {
Thread.dumpStack();
synchronized (mWifiReqCountLock) {
if (hasConnectionRequests()) {
sendMessage(CMD_AUTO_CONNECT, networkId, 0, bssid);//狀態(tài)機(jī)開(kāi)始處理
}
}
}
此時(shí),WifiStateMachine處于DisconnectedState狀態(tài)培廓。
根據(jù)狀態(tài)機(jī)處理業(yè)務(wù)邏輯惹悄,子狀態(tài)無(wú)法處理,則父狀態(tài)處理肩钠。
ConnectModeState
class ConnectModeState extends State {
@Override
public void enter() {
······
}
@Override
public void exit() {
······
}
@Override
public boolean processMessage(Message message) {
case CMD_AUTO_CONNECT:
······
/* Save the network config */
logd("CMD_AUTO_CONNECT will save config -> " + config.SSID
+ " nid=" + Integer.toString(netId));
result = mWifiConfigManager.saveNetwork(config, WifiConfiguration.UNKNOWN_UID);//保存網(wǎng)絡(luò)狀態(tài)
······
if (mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ false,
lastConnectUid) && mWifiNative.reconnect()) {//選擇網(wǎng)絡(luò)并且重新連接
······
} else {
······
}
break;
}
}
問(wèn)題:reconnect之后俘侠,怎么知道連接成功了呢?
關(guān)注WifiMonitor對(duì)wpa_supplicant的監(jiān)聽(tīng)蔬将。結(jié)果為:WifiMonitor發(fā)送事件CTRL-EVENT-CONNECTED出來(lái),轉(zhuǎn)化為Message.what為NETWORK_CONNECTION_EVENT
問(wèn)題:DisconnectedState怎么最后轉(zhuǎn)化成了ConnectedState央星?
DisconnectedState先切換到狀態(tài)mObtainingIpState霞怀,再?gòu)膍ObtainingIpState切換到ConnectedState
補(bǔ)充流程:
保存網(wǎng)絡(luò)
mWifiConfigManager.saveNetwork(config, WifiConfiguration.UNKNOWN_UID);
com.android.server.wifi.WifiConfigManager
saveNetwork --> saveConfig --> mWifiConfigStore.saveConfig()
com.android.server.wifi.WifiConfigStore
saveConfig --> mWifiNative.saveConfig()
com.android.server.wifi.WifiNative
saveConfig --> doBooleanCommand --> doBooleanCommandNative
關(guān)注日志。TAG:WifiNative-wlan0 SAVE_CONFIG
選擇網(wǎng)絡(luò)
mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ false,
lastConnectUid)
com.android.server.wifi.WifiConfigManager
selectNetwork --> selectNetworkWithoutBroadcast --> mWifiConfigStore.selectNetwork(
mConfiguredNetworks.getForCurrentUser(netId),
mConfiguredNetworks.valuesForCurrentUser())
com.android.server.wifi.WifiConfigStore
selectNetwork --> mWifiNative.selectNetwork(config.networkId)
com.android.server.wifi.WifiNative
selectNetwork --> doBooleanCommand --> doBooleanCommandNative
關(guān)注日志莉给。TAG:WifiNative-wlan0 SELECT_NETWORK
重新連接
com.android.server.wifi.WifiNative
reconnect --> doBooleanCommand --> doBooleanCommandNative
關(guān)注日志毙石。TAG:WifiNative-wlan0 RECONNECT
小結(jié):
a.自動(dòng)連接最后仍會(huì)回到WifiStateMachine的操作
b.WifiMonitor監(jiān)聽(tīng)了wpa_supplicant的消息發(fā)送
總結(jié)
1.wifi掃描服務(wù)廉沮,作為系統(tǒng)服務(wù),主要定時(shí)掃描wifi
2.為了更好的解耦客戶端徐矩,同時(shí)滞时,更好的服務(wù)客戶端,采用了AsyncChannel雙handler進(jìn)程通信
3.客戶端可以根據(jù)定時(shí)掃描的結(jié)果進(jìn)行wifi自動(dòng)連接滤灯,關(guān)鍵類(lèi):WifiConnectivityManager
客戶端類(lèi):WifiScanner