Android 11 WiFi启动流程

您所在的位置:网站首页 Android系统启动流程详解 Android 11 WiFi启动流程

Android 11 WiFi启动流程

2023-09-06 00:59| 来源: 网络整理| 查看: 265

       欢迎大家一起学习探讨通信之WLAN。本节重点基于Android11分析讨论WiFi开启流程。用户点击一下“WiFi”开关,WiFi开启了。看似如此简单操作,但系统流程调用还是相当复杂。因Android11版本框架中WiFi打开流程有一部分变化。接下来我们一起分析下Android 11 WiFi的打开流程是怎么样的呢?

 【1】系统启动时,system_server进程中会启动Wifiservice服务,启动该服务在SystemServer.java文件。

private static final String WIFI_SERVICE_CLASS ="com.android.server.wifi.WifiService"; if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) { // Wifi Service must be started first for wifi-related services. mSystemServiceManager.startService(WIFI_SERVICE_CLASS); mSystemServiceManager.startService("com.android.server.wifi.scanner.WifiScanningService"); }

 

       该方法在启动Wifiservice服务时将会被调用到,作用是将服务注册到ServiceManager中。

protected final void publishBinderService(String name, IBinder service, boolean allowIsolated, int dumpPriority) { ServiceManager.addService(name, service, allowIsolated, dumpPriority); }

【2】在Wifiservice启动时,创建WifiServiceImpl实例对象,并将WiFiserviceimpl对象注册到servicemanager中。

这个对象很重要,它是整个系统WiFi服务的管理者,所有的用户端操作请求都将由它初步预处理,然后分发给不同的服务处理执行。

android/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiService.java public WifiService(Context contextBase) { super(contextBase); mWifiContext = new WifiContext(contextBase); WifiInjector injector = new WifiInjector(mWifiContext); WifiAsyncChannel channel = new WifiAsyncChannel(TAG); mImpl = new WifiServiceImpl(mWifiContext, injector, channel); } public void onStart() { Log.i(TAG, "Registering " + Context.WIFI_SERVICE); publishBinderService(Context.WIFI_SERVICE, mImpl); }

  【3】好,接下来到了WifiServiceImpl初始化过程,首先,看下该类的构造函数。

public WifiServiceImpl(Context context, WifiInjector wifiInjector, AsyncChannel asyncChannel) { mContext = context; mWifiInjector = wifiInjector; ... ... ... ... mClientModeImpl = mWifiInjector.getClientModeImpl(); mActiveModeWarden = mWifiInjector.getActiveModeWarden(); ... ... ... ... }

 

    看到WiFiserviceimpl构造函数,基本上做了与WiFi服务管理的相关类的初始化动作。下面简单看下Android11 相对Android9 新增加与Wifi打开相关的几个初始化类:

ClientModeImpl 作为客户端的模式实现,作为客户端事件处理在这里完成,并所有的连接状态变化在这里初始化。ActiveModeWarden 提供WiFi不同操作模式的配置实现。

 

    到这里,系统启动完成,WifiService服务已正常运行,接下来分析用户“打开WiFi”这个简单的操作,Android11 系统背后的运行流程。

 

【4】用户点击WiFi开关按钮打开WiFi,通过Android提供的WiFiManager的接口类,通过AIDL机制将事件传递至WifiSerivce管理服务,在WifiServiceImpl对象中对其处理。

public synchronized boolean setWifiEnabled(String packageName, boolean enable) { //权限检查 boolean isPrivileged = isPrivileged(Binder.getCallingPid(), Binder.getCallingUid()); //检查飞行模式是否打开,同时只运行授权的app打开WiFi if (mSettingsStore.isAirplaneModeOn() && !isPrivileged) { mLog.err("setWifiEnabled in Airplane mode: only Settings can toggle wifi").flush(); return false; } //检查调用客户端是否拥有NETWORK_SETTINGS权限,如有在将事件数据记录。 if (mWifiPermissionsUtil.checkNetworkSettingsPermission(Binder.getCallingUid())) { mWifiMetrics.logUserActionEvent(enable ? UserActionEvent.EVENT_TOGGLE_WIFI_ON : UserActionEvent.EVENT_TOGGLE_WIFI_OFF); } //记录WiFi切换状态。 mWifiMetrics.incrementNumWifiToggles(isPrivileged, enable); //触发WiFi打开动作。 mActiveModeWarden.wifiToggled(); } private boolean isPrivileged(int pid, int uid) { return checkNetworkSettingsPermission(pid, uid) || checkNetworkSetupWizardPermission(pid, uid) || checkNetworkStackPermission(pid, uid) || checkNetworkManagedProvisioningPermission(pid, uid); }

 

 【5】接下来将进入“ActiveModeWarden”类中。先看 WifiService服务调用的方法“mActiveModeWarden.wifiToggled()”。 

android/frameworks/opt/net/wifi/service/java/com/android/server/wifi/ActiveModeWarden.java 在wifiToggled方法中,发送消息“WifiController.CMD_WIFI_TOGGLED”。 public void wifiToggled() { mWifiController.sendMessage(WifiController.CMD_WIFI_TOGGLED); }

   WifiController是ActiveModeWarden的内部类,继承StateMachine类,在ActiveModeWarden构造函数中对其进行初始化;

   WifiService服务启动后,在WifiServiceImpl中,调用mActiveModeWarden.start()对其启动。WifiController状态机状态有三个如下:

 

   

   

     状态机启动时,根据WiFi开启状态进行状态机初始化。因WiFi是由关闭切换至打开,因此,状态机初始到“mDisabledState”状态。

public void start() { if (shouldEnableSta()) { startClientModeManager(); setInitialState(mEnabledState); } else { setInitialState(mDisabledState); } } private boolean shouldEnableSta() { return mSettingsStore.isWifiToggleEnabled() || checkScanOnlyModeAvailable(); }

      WifiController接收到CMD_WIFI_TOGGLED消息,将在“mDisabledState”状态进行处理,

public boolean processMessageFiltered(Message msg) { ... ... ... ... case CMD_WIFI_TOGGLED: case CMD_SCAN_ALWAYS_MODE_CHANGED: if (shouldEnableSta()) { startClientModeManager(); transitionTo(mEnabledState); } break; ... ... ... ...

      这里调用startClientModeManager()方法,启动一个客户端管理对象。

private boolean startClientModeManager() { //ClientListener为ActiveModeWarden的内部监听类。 ClientListener listener = new ClientListener(); //获取客户端管理对象,并设置监听。 ClientModeManager manager = mWifiInjector.makeClientModeManager(listener); listener.setActiveModeManager(manager); manager.start(); //切换客户端模式 if (!switchClientModeManagerRole(manager)) { return false; } mActiveModeManagers.add(manager); return true; }

 

【6】好!到这里我们跳到ClientModeManager类中。在其构造函数中,实例化了ClientModeStateMachine内部类对象mStateMachine 。start方法中,触发设置Station模式“ROLE_CLIENT_SCAN_ONLY”,并发送

ClientModeStateMachine.CMD_START消息。

ClientModeManager(Context context, @NonNull Looper looper, Clock clock, WifiNative wifiNative, Listener listener, WifiMetrics wifiMetrics, SarManager sarManager, WakeupController wakeupController, ClientModeImpl clientModeImpl) { ... ... ... ... mStateMachine = new ClientModeStateMachine(looper); mDeferStopHandler = new DeferStopHandler(TAG, looper); ... ... ... ... } public void start() { mTargetRole = ROLE_CLIENT_SCAN_ONLY; mStateMachine.sendMessage(ClientModeStateMachine.CMD_START); }

     ClientModeStateMachine类的状态机状态如下,并初始到“mIdleState”状态和启动状态机。

      

ClientModeStateMachine(Looper looper) { super(TAG, looper); addState(mIdleState); addState(mStartedState); addState(mScanOnlyModeState, mStartedState); addState(mConnectModeState, mStartedState); setInitialState(mIdleState); start(); }

 

    接下来在startClientModeManager()调用方法switchClientModeManagerRole(manager)切换客户端设置模式角色。

private boolean switchClientModeManagerRole(@NonNull ClientModeManager modeManager) { if (mSettingsStore.isWifiToggleEnabled()) { //打开WiFi将设置角色为“ROLE_CLIENT_PRIMARY”; modeManager.setRole(ActiveModeManager.ROLE_CLIENT_PRIMARY); } else if (checkScanOnlyModeAvailable()) { modeManager.setRole(ActiveModeManager.ROLE_CLIENT_SCAN_ONLY); } ... ... ... ... }

     在setRole()方法中,将role设置的值保存在mTargetRole变量,并发送消息给ClientModeStateMachine状态机。

public void setRole(@Role int role) { if (role == ROLE_CLIENT_SCAN_ONLY) { mTargetRole = role; mStateMachine.sendMessage(ClientModeStateMachine.CMD_SWITCH_TO_SCAN_ONLY_MODE) } else if (CLIENT_CONNECTIVITY_ROLES.contains(role)) { mTargetRole = role; // Switch client mode manager to connect mode mStateMachine.sendMessage(ClientModeStateMachine.CMD_SWITCH_TO_CONNECT_MODE, role); } }

       程序执行到此,ActiveModeWarden类中WiFicontroller状态机在DisabledState状态处理完了“CMD_WIFI_TOGGLED”这个消息,状态机将跳转至“EnabledState”。通过状态机机制可知,将会执行DisabledState.exit()方法退出DisabledState状态;执行EnabledState.enter()方法进入到EnabledState状态。WiFicontroller状态机在WiFi打开中将停留在该状态。

 

【7】从上流程可知,“ClientModeStateMachine.CMD_START”和“ClientModeStateMachine.CMD_SWITCH_TO_CONNECT_MODE”两条消息还处于待处理状态。

在ClientModeStateMachine类构造函数可知,状态机初始状态为“IdleState”,此时,将执行IdleState.enter()方法进入该状态,并开始处理CMD_START消息。

private class IdleState extends State { ... ... ... ... case CMD_START: // Always start in scan mode first. mClientInterfaceName =mWifiNative.setupInterfaceForClientInScanMode( mWifiNativeInterfaceCallback); if (TextUtils.isEmpty(mClientInterfaceName)) { Log.e(TAG, "Failed to create ClientInterface. Sit in Idle"); mModeListener.onStartFailure(); break; } transitionTo(mScanOnlyModeState); break; ... ... ... ...

       在处理CMD_START消息时,将调用WifiNative类中setupInterfaceForClientInScanMode()方法。

【8】好!先看WifiNative类中该方法的实现。

android/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiNative.java public String setupInterfaceForClientInScanMode( @NonNull InterfaceCallback interfaceCallback) { synchronized (mLock) { //startwifi 加载WiFi驱动 if (!startHal()) { Log.e(TAG, "Failed to start Hal"); mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal(); return null; } //分配对应的接口 Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_STA_FOR_SCAN); if (iface == null) { Log.e(TAG, "Failed to allocate new STA iface"); return null; } iface.externalListener = interfaceCallback; //创建对应的station接口 iface.name = createStaIface(iface); if (TextUtils.isEmpty(iface.name)) { Log.e(TAG, "Failed to create iface in vendor HAL"); mIfaceMgr.removeIface(iface.id); mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal(); return null; } //设置客户端接口 if (!mWifiCondManager.setupInterfaceForClientMode(iface.name, Runnable::run, new NormalScanEventCallback(iface.name), new PnoScanEventCallback(iface.name))) { Log.e(TAG, "Failed to setup iface in wificond=" + iface.name); teardownInterface(iface.name); mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToWificond(); return null; } //设置接口up/down的检测通知 iface.networkObserver = new NetworkObserverInternal(iface.id); if (!registerNetworkObserver(iface.networkObserver)) { Log.e(TAG, "Failed to register network observer for iface=" + iface.name); teardownInterface(iface.name); return null; } //启动wpa_supplicant事件监听 mWifiMonitor.startMonitoring(iface.name); onInterfaceStateChanged(iface, isInterfaceUp(iface.name)); //获取WiFi支持的feature iface.featureSet = getSupportedFeatureSetInternal(iface.name); return iface.name; } }

(1)startHal方法内部调用

       startHal()->startHal()->WifiVendorHal.startVendorHal()->HalDeviceManager.start()->HalDeviceManager.startWifi()

       这里看下启动WiFi的方法“HalDeviceManager.startWifi()”。在这个方法中,将加载WiFi驱动。

private boolean startWifi() { ... ... ... ... synchronized (mLock) { try { if (mWifi == null) { return false; } else { int triedCount = 0; //加载WiFi驱动将尝试4次,START_HAL_RETRY_TIMES=3。 while (triedCount start() ==> wifi.cpp->startInternal() ==> wifi.cpp->initializeModeControllerAndLegacyHal() ==> WifiModeController->initialize() ==> DriverTool->LoadDriver()

      通过调用DriverTool->LoadDriver将返回到Android framework中。下面是LoadDriver()的实现。

android/frameworks/opt/net/wifi/libwifi_hal/include/wifi_hal/driver_tool.cpp bool DriverTool::LoadDriver() { return ::wifi_load_driver() == 0; }

       在wifi_load_driver()方法中,将调用系统接口加载WiFi驱动ko。关于系统insmod接口的调用,本文不做分析。到这里,已梳理完在WifiNative类中调用的startHal()方法。

android/frameworks/opt/net/wifi/libwifi_hal/wifi_hal_common.cpp int wifi_load_driver() { ... ... ... ... insmod(file,args); ... ... ... ... }

(2)调用WifiNl80211Manager类的setupInterfaceForClientMode()方法。

    该类的主要对WiFi 80211nl管理接口的封装,接口在WiFicond守护进程中呈现给WiFi框架。该类提供的接口仅使用与WiFi框架,访问权限受selinux权限保护。

    setupInterfaceForClientMode()方法主要为Station模式设置接口。

android/frameworks/base/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java public boolean setupInterfaceForClientMode(@NonNull String ifaceName, @NonNull @CallbackExecutor Executor executor, @NonNull ScanEventCallback scanCallback, @NonNull ScanEventCallback pnoScanCallback) { ... ... ... ... // Refresh Handlers mClientInterfaces.put(ifaceName, clientInterface); try { IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl(); mWificondScanners.put(ifaceName, wificondScanner); Binder.allowBlocking(wificondScanner.asBinder()); ScanEventHandler scanEventHandler = new ScanEventHandler(executor, scanCallback); mScanEventHandlers.put(ifaceName, scanEventHandler); wificondScanner.subscribeScanEvents(scanEventHandler); PnoScanEventHandler pnoScanEventHandler = new PnoScanEventHandler(executor, pnoScanCallback); mPnoScanEventHandlers.put(ifaceName, pnoScanEventHandler); wificondScanner.subscribePnoScanEvents(pnoScanEventHandler); ... ... ... ... }

 

     到这里,ClientModeStateMachine状态机在IdleState状态成功处理完了CMD_START消息。状态机将转到“mScanOnlyModeState”状态,将会执行以下调用流程(具体原因可查看状态机机制)。

IdleState.exit()->StartedState.enter()->StartedState.exit()->ScanOnlyModeState.enter()。

 

       在状态转时,在StartedState.exit()中,将调用setOperationalMode方法。

public void exit() { mClientModeImpl.setOperationalMode(ClientModeImpl.DISABLED_MODE, null); }

【9】这里将触及开篇提到的ClientModeImpl类。首先看下其构造函数,除了其他对象赋值初始化外,又一个状态机将与我们见面。

android/frameworks/opt/net/wifi/service/java/com/android/server/wifi/ClientModeImpl.java public ClientModeImpl(.......){ super(TAG, looper); ... ... .... .... // CHECKSTYLE:OFF IndentationCheck addState(mDefaultState); addState(mConnectModeState, mDefaultState); addState(mL2ConnectedState, mConnectModeState); addState(mObtainingIpState, mL2ConnectedState); addState(mConnectedState, mL2ConnectedState); addState(mRoamingState, mL2ConnectedState); addState(mDisconnectingState, mConnectModeState); addState(mDisconnectedState, mConnectModeState); // CHECKSTYLE:ON IndentationCheck setInitialState(mDefaultState); ... ... .... .... } public void setOperationalMode(int mode, String ifaceName) { ... ... ... //打开WiFi mode=2,因此状态机进入到mDefaultState状态 if (mode != CONNECT_MODE) { // we are disabling client mode... need to exit connect mode now transitionTo(mDefaultState); } else { ... ... ... ... } }

 

【10】目前,还遗留一个消息“ClientModeStateMachine.CMD_SWITCH_TO_CONNECT_MODE”待处理。此时,轮到该消息要被处理了。

ClientModeStateMachine状态机已处于“ScanOnlyModeState”状态,接收到该消息后,在processMessage方法中并未处理,而抛给父状态“StartedState”处理。在StartedState.processMessage()方法中对该消息进行了处理。

public boolean processMessage(Message message) { ... ... ... ... case CMD_SWITCH_TO_CONNECT_MODE: mRole = message.arg1; // could be any one of possible connect mode roles. //更新WiFi打开状态,发送系统广播通知。 updateConnectModeState(WifiManager.WIFI_STATE_ENABLING, WifiManager.WIFI_STATE_DISABLED); //切换设置clinent接口到连接模式。 if (!mWifiNative.switchClientInterfaceToConnectivityMode( mClientInterfaceName)) { ... ... ... ... mModeListener.onStartFailure(); break; } ... ... ... ... transitionTo(mConnectModeState); break; }

(1)WifiNative类中,mWifiNative.switchClientInterfaceToConnectivityMode()方法

public boolean switchClientInterfaceToConnectivityMode(@NonNull String ifaceName) { synchronized (mLock) { ... ... ... ... //获取设置的接口 final Iface iface = mIfaceMgr.getIface(ifaceName); if (iface == null) { return false; } if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY) { return true; } //调用启动wpa_supplicant服务进程 if (!startSupplicant()) { teardownInterface(iface.name); mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToSupplicant(); return false; } //注册设置接口 if (!mSupplicantStaIfaceHal.setupIface(iface.name)) { return false; } iface.type = Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY; iface.featureSet = getSupportedFeatureSetInternal(iface.name); ... ... ... ... return true; } } WifiNative.startSupplicant()->WifiNative.startAndWaitForSupplicantConnection()-> SupplicantStaIfaceHal.startDaemon()->SupplicantStaIfaceHal.startDaemon_V1_1()-> SupplicantStaIfaceHal.getSupplicantMockableV1_1()-> SupplicantStaIfaceHal.getSupplicantMockable()

 

    这里看下getSupplicantMockable()方法,将在getService的时候启动wpa_supplicant进程。

通过HIDL调用获取wpa_supplicant服务。ISupplicant.getService(supplicantName)调用到ISupplicant.java。

protected ISupplicant getSupplicantMockable() throws RemoteException, NoSuchElementException { synchronized (mLock) { ISupplicant iSupplicant = ISupplicant.getService(supplicantName); if (iSupplicant == null) { throw new NoSuchElementException("Cannot get root service."); } return iSupplicant; } } android/out/soong/intermediates/hardware/interfaces/wifi/supplicant/1.0/android.hardware.wifi.supplicant-V1.0-java_gen_java/gen/srcs/android/hardware/wifi/supplicant/V1_0/ISupplicant.java public static ISupplicant getService(String serviceName) throws android.os.RemoteException { return ISupplicant.asInterface(android.os.HwBinder.getService("[email protected]::ISupplicant", serviceName)); }

 

      在这个方法中将触发启动wpa_supplicant进程,这里需要注意,在manifest.xml中对其需要进行配置,运行时会将服务名称注册到hwservicemanager中。

wpa_supplicant目录下文件调用:

main.c ==> wpa_supplicant.c->wpa_supplicant_init() ==> notify.c->wpas_notify_supplicant_initialized() ==> hidl.cpp->wpas_hidl_init() ==> Hidl_manager.cpp->registerHidlService() int HidlManager::registerHidlService(struct wpa_global *global) { // Create the main hidl service object and register it. supplicant_object_ = new Supplicant(global); if (supplicant_object_->registerAsService("wpa_supplicant") != android::NO_ERROR) { return 1; } return 0; }

 

【11】将wpa_supplicant添加注册到hwservicemanager,SupplicantStaIfaceHal.getSupplicantMockable()执行完成返回。

这里再深入看下“supplicant_object_->registerAsService("wpa_supplicant")”是如何通过调用注册的呢?

android/out/soong/.intermediates/hardware/interfaces/wifi/supplicant/1.3/[email protected]_genc++/gen/android/hardware/wifi/supplicant/1.3/SupplicantAll.cpp android/system/libhidl/transport/ServiceManagement.cpp android/system/hwservicemanager/ServiceManager.cpp supplicant_object_->registerAsService("wpa_supplicant") ==> ISupplicant.hal ==> ISupplicantAll.cpp->registerAsService() ==> ::android::hardware::details::registerAsServiceInternal(this, serviceName) ==> ServiceManagement.cpp->registerAsServiceInternal() ==> ServiceManager->addWithChain() ==> ServiceManager->addImpl()

 

【12】wpa_supplicant注册完成后,SupplicantStaIfaceHal类中将收到回调通知信息,

private final IServiceNotification mServiceNotificationCallback = new IServiceNotification.Stub() { public void onRegistration(String fqName, String name, boolean preexisting) { synchronized (mLock) { if (!initSupplicantService()) { supplicantServiceDiedHandler(mDeathRecipientCookie); } else { } } } };

       返回通知的调用逻辑。

SupplicantStaIfaceHal.initSupplicantService() -> SupplicantStaIfaceHal.getSupplicantMockable()

    经过一系列调用,wpa_supplicant进程服务已启动完成。现回到WifiNative类中switchClientInterfaceToConnectivityMode()方法。

在该方法中,wpa_supplicant启动完成后,将调用SupplicantStaIfaceHal.setupIface()方法设置接口。

设置成功将执行完WifiNative.switchClientInterfaceToConnectivityMode()方法并退出。

[关键log打印]:WifiNative: Successfully switched to connectivity mode on iface=Iface:{Name=wlan0,Id=1,Type=STA_CONNECTIVITY}

 

【13】ClientModeManager类中,状态机ClientModeStateMachine在StartedState状态成功处理完“CMD_SWITCH_TO_CONNECT_MODE”消息。状态将转到“mConnectModeState”,

调用mConnectModeState.enter()方法。

public void enter() { //设置客户端模式为连接模式 mClientModeImpl.setOperationalMode(ClientModeImpl.CONNECT_MODE, mClientInterfaceName); mModeListener.onStarted(); //发送系统广播,通知WiFi已打开。 updateConnectModeState(WifiManager.WIFI_STATE_ENABLED, WifiManager.WIFI_STATE_ENABLING); //通知SAR管理着WiFi已打开 mSarManager.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); }

ClientModeImpl.setOperationalMode方法实现如下,设置模式为CONNECT_MODE。因此将走else流程。ClientModeImpl类中状态机将由DefaultState转到“DisconnectedState”状态。

状态机转到DisconnectedState状态,执行流程如下:

ConnectModeState.() ->disconnectedstate.enter() public void setOperationalMode(int mode, String ifaceName) { mModeChange = true; if (mode != CONNECT_MODE) { transitionTo(mDefaultState); } else { if (ifaceName != null) { mInterfaceName = ifaceName; //更新设置是否支持80211ax。 updateInterfaceCapabilities(ifaceName); //状态转到DisconnectedState transitionTo(mDisconnectedState); mWifiScoreReport.setInterfaceName(ifaceName); } else { transitionTo(mDefaultState); } } sendMessageAtFrontOfQueue(CMD_SET_OPERATIONAL_MODE); }

 

【14】在此过程中,ActiveModeWarden类中设置的ClientLister将被触发回调。 wifiScaner.setScanningEnabled()发送消息CMD_ENABLE,给到WiFiscanningSerivceimpl类中。

系统调用到这里,WiFi已处于打开状态,并将进行扫描网络,待连接。WiFi打开流程分析完成。

ActiveModeWarden.ClientListener -> ScanRequestProxy.enableScanning() -> ScanRequestProxy.enableScanningInternal() -> wifiScaner.setScanningEnabled()

注:

       对以上所述专业知识有修正意见或建议,可随时留言反馈。如感兴趣更多通信知识,可关注“通信之WLAN”微信公众号。

谢谢大家支持~!

 

 

 

 

 



【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3