🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
首先来看WifiStateMachine的构造函数,其内容较多,我们分两段来介绍。 **1、WifiStateMachine构造函数分析之一** **WifiStateMachine.java::WifiStateMachine构造函数代码段一** ~~~ public WifiStateMachine(Context context, String wlanInterface) { super(TAG); mContext = context; mInterfaceName = wlanInterface; // 创建一个NetworkInfo,它实际上代表一个网络设备的状态信息(status of a network interface) mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI,0, NETWORKTYPE, ""); // 和BatteryStatsService交互,BSS注册的服务名叫“batteryinfo” mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo")); // 创建和NewtorkManagmentService交互的Binder客户端 IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); mNwService = INetworkManagementService.Stub.asInterface(b); /* 判断系统是否支持Wi-Fi Display功能。本书不讨论WFD,感兴趣的读者可阅读笔者的一篇博文 http:// blog.csdn.net/innost/article/details/8474683 " Android Wi-Fi Display(Miracast)介绍"。 */ mP2pSupported = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT); /* WifiNative:用于和wpa_supplicant交互。它和4.2.3节中控制API知识相关。 WifiMonitor:内部将创建一个线程,并借助WifiNative去接收并处理来自WPAS的信息。 WifiConfigStore:它对应一个配置文件,位置为/data/misc/wifi/ifconfig.txt。 该文件用于存储每个无线网络的配置项。例如代理地址、静态IP地址等。读者可在Settings 中选择某个无线网络,长按以弹出修改对话框,然后选择“高级选项”即可设置这些信息。 */ mWifiNative = new WifiNative(mInterfaceName); mWifiConfigStore = new WifiConfigStore(context, mWifiNative); mWifiMonitor = new WifiMonitor(this, mWifiNative); // 用于保存DHCP的一些信息 mDhcpInfoInternal = new DhcpInfoInternal(); // WifiInfo用于存储手机当前连接上的无线网络的一些信息,包括IP地址、ssid等内容 mWifiInfo = new WifiInfo(); // SupplicantStateTracker用于跟踪WPAS的状态,它也是一个StateMachine mSupplicantStateTracker = new SupplicantStateTracker(context, this, mWifiConfigStore,getHandler()); // LinkProperties用于描述网络链接(network link)的一些属性,如IP地址、DNS地址和路由设置 mLinkProperties = new LinkProperties(); // WifiApConfigStore和Soft AP模式有关,用于存储Soft AP模式中使用到的一些配置信息 // WifiApConfigStore是一个StateMachine。配置信息存储于/data// misc/wifi/softap.conf中 WifiApConfigStore wifiApConfigStore = WifiApConfigStore.makeWifiApConfigStore(context, getHandler()); wifiApConfigStore.loadApConfiguration(); // mWifiApConfigChannel的类型是AsyncChannel,它将和wifiApConfigStore中的某个Handler通信 mWifiApConfigChannel.connectSync(mContext, getHandler(),wifiApConfigStore.getMessenger()); mNetworkInfo.setIsAvailable(false); mLinkProperties.clear(); ...... // 设置扫描间隔时间:当驱动不支持Background扫描时,Framework将定时开展扫描工作 // 默认值为300秒 mDefaultFrameworkScanIntervalMs = mContext.getResources() .getInteger(R.integer.config_wifi_framework_scan_interval); /* driver stop延迟,默认是120秒。该变量和emergency calls(紧急呼叫)有关。 处于这种模式下,即使用户选择关闭Wi-Fi,WifiStateMachine也不会立即执行它,而是要 等待一段时间才真正去关闭Wi-Fi。 */ mDriverStopDelayMs = mContext.getResources().getInteger(R.integer.config_wifi_driver_stop_delay); // 是否支持Background扫描 mBackgroundScanSupported = mContext.getResources() .getBoolean(R.bool.config_wifi_background_scan_support); // 和P2P有关。以后再介绍 mPrimaryDeviceType = mContext.getResources().getString(R.string.config_wifi_p2p_device_type); // WIFI_SUSPEND_OPTIMIZATIONS_ENABLED变量用于控制手机睡眠期间是否保持Wi-Fi开启 mUserWantsSuspendOpt.set(Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1); ......// 处理ACTION_START_SCAN广播事件 ......// 处理ACTION_SCREEN_ON/OFF广播事件 ......// 处理ACTION_DELAYED_DRIVER_STOP广播事件 ......// 监视ContentProvider中WIFI_SUSPEND_OPTIMIZATIONS_ENABLED设置的变化 // mScanResultCache用于保存扫描结果 mScanResultCache = new LruCache<String, ScanResult>(SCAN_RESULT_CACHE_SIZE); ......// 申请WakeLock } ~~~ 重点介绍其中的三个对象,分别是WifiNative、WifiMonitor以及SupplicantStateTracker。 **①、WifiNative** 根据上文描述,WifiNative用于和WPAS通信,其内部定义了较多的native方法(对应的JNI模块是android_net_wifi_Wifi)。本节介绍其中最重要的两个方法。 第一个方法是startSupplicant,用于启动WPAS。startSupplicant是一个native函数,其JNI[^①]函数是**android_net_wifi_startSupplicant**,代码如下所示。 **android_net_wifi_Wifi.c::android_net_wifi_startSupplicant** ~~~ static jboolean android_net_wifi_startSupplicant(JNIEnv* env, jobject,jboolean p2pSupported) { return (jboolean)(::wifi_start_supplicant(p2pSupported) == 0); } ~~~ wifi_start_supplicant代码如下所示。 **wifi.c::wifi_start_supplicant** ~~~ int wifi_start_supplicant(int p2p_supported) { char supp_status[PROPERTY_VALUE_MAX] = {'\0'}; int count = 200; // 该宏在build/core/combo/include/arch/linux-arm/AndroidConfig.h中被定义为1 #ifdef HAVE_LIBC_SYSTEM_PROPERTIES const prop_info *pi; unsigned serial = 0, i; #endif // 和P2P有关 if (p2p_supported) {// P2P_SUPPLICANT_NAME值为“p2p_supplicant” strcpy(supplicant_name, P2P_SUPPLICANT_NAME); // P2P _PROP_NAME值为“init.svc.p2p_supplicant” strcpy(supplicant_prop_name, P2P_PROP_NAME); /* P2P_CONFIG_FILE的值为“/data/misc/wifi/p2p_supplicant.conf”。下面这个函数将把 /system/etc/wifi/wpa_supplicant.conf的内容复制到P2P_CONFIG_FILE中。 */ if (ensure_config_file_exists(P2P_CONFIG_FILE) < 0) return -1; } else { strcpy(supplicant_name, SUPPLICANT_NAME);// SUPPLICANT_NAME值为“wpa_supplicant” // SUPP_PROP_NAME值为“init.svc.wpa_supplicant” strcpy(supplicant_prop_name, SUPP_PROP_NAME); } // 如果WPAS已经启动,则直接返回 if (property_get(supplicant_name, supp_status, NULL) && strcmp(supp_status, "running") == 0) return 0; // SUPP_CONFIG_FILE的值为“/data/misc/wifi/wpa_supplicant.conf” if (ensure_config_file_exists(SUPP_CONFIG_FILE) < 0) return -1; // entropy文件,用于增加随机数生成的随机性 if (ensure_entropy_file_exists() < 0) ALOGE("Wi-Fi entropy file was not created"); // 关闭之前创建的wpa_ctrl对象 wifi_wpa_ctrl_cleanup(); for (i=0; i&lt;MAX_CONNS; i++) exit_sockets[i][0] = exit_sockets[i][1] = -1; #ifdef HAVE_LIBC_SYSTEM_PROPERTIES // supplicant_prop_name值为“init.svc.wpa_supplicant” pi = __system_property_find(supplicant_prop_name); ...... #endif property_get("wifi.interface", primary_iface, WIFI_TEST_INTERFACE); /* 通过设置“ctrl.start”属性来启动wpa_supplicant服务。该属性将触发init fork一个子 进程用于运行wpa_supplicant。同时,init还会添加一个新的属性 “init.svc.wpa_supplicant”用于跟踪wpa_supplicant的状态。 */ property_set("ctl.start", supplicant_name); sched_yield(); // 下面这个循环用于查询“init.svc.wpa_supplicant”的属性值 // 如果其值变成“running”,表示wpa_supplicant成功运行 while (count-- > 0) {// count初值为200。while循环最多等待20秒 #ifdef HAVE_LIBC_SYSTEM_PROPERTIES if (pi == NULL) { pi = __system_property_find(supplicant_prop_name); } if (pi != NULL) { __system_property_read(pi, NULL, supp_status); if (strcmp(supp_status, "running") == 0) return 0; else if (pi->serial != serial &&// 如果WPAS已经停止,则直接返回-1 strcmp(supp_status, "stopped") == 0) return -1; } #else ...... #endif usleep(100000);// 等待wpa_supplicant的状态 } return -1; } ~~~ 图5-3显示了wpa_supplicant运行过程中及退出后"init.svc.wpa_supplicant"属性值的变化。 :-: ![](https://box.kancloud.cn/bd1c4c272f9627aad27f765d2b7f5605_653x198.jpg) 图5-3 init.svc.wpa_supplicant属性 >[info] 提示 对Android属性机制和init工作原理感兴趣的读者不妨阅读《深入理解Android:卷Ⅰ》第3章。 第二个要介绍的函数是connectToSupplicant,它将通过WPAS控制API和WPAS建立交互关系。 **WifiNative.java::connectToSupplicant** ~~~ public boolean connectToSupplicant() { // mInterface的值为”wlan0”,由属性“wifi.interface”决定 return connectToSupplicant(mInterface);// 调用native函数 } private native boolean connectToSupplicant(String iface); ~~~ 与connectToSupplicant对应的JNI函数是android_net_wifi_connectToSupplicant,其代码如下所示。 **android_net_wifi_Wifi.cpp::android_net_wifi_connectToSupplicant** ~~~ static jboolean android_net_wifi_connectToSupplicant(JNIEnv* env, jobject, jstring jIface) { ScopedUtfChars ifname(env, jIface); return (jboolean)(::wifi_connect_to_supplicant(ifname.c_str()) == 0); } ~~~ wifi_connect_to_supplicant的代码如下所示。 **wifi.c::wifi_connect_to_supplicant** ~~~ int wifi_connect_to_supplicant(const char *ifname) { char path[256]; /* Android 4.2支持STA和P2P设备并发(concurrent)工作,STA用PRIMARY(值为0)来标示, 而P2P设备用SECONDARY(值为1)代表。is_primary_interface用于判断ifname是否代表STA。 */ if (is_primary_interface(ifname)) { // IFACE_DIR的值为“/data/system/wpa_supplicant”。笔者测试的几个手机中都没有该文件夹 if (access(IFACE_DIR, F_OK) == 0) { snprintf(path, sizeof(path), "%s/%s", IFACE_DIR, primary_iface); } else { strlcpy(path, primary_iface, sizeof(path)); } return wifi_connect_on_socket_path(PRIMARY, path);// PRIMARY值为0 } else { sprintf(path, "%s/%s", CONTROL_IFACE_PATH, ifname); return wifi_connect_on_socket_path(SECONDARY, path);// SECONDARY值为1 } } ~~~ 来看wifi_connect_on_socket_path,其代码如下所示。 **wifi.c::wifi_connect_on_socket_path** ~~~ int wifi_connect_on_socket_path(int index, const char *path) { char supp_status[PROPERTY_VALUE_MAX] = {'\0'}; // 判断wpa_supplicant进程是否已经启动 if (!property_get(supplicant_prop_name, supp_status, NULL) || strcmp(supp_status, "running") != 0) return -1; // 创建第一个wpa_ctrl对象,用于发送命令 ctrl_conn[index] = wpa_ctrl_open(path); ...... // 创建第二个wpa_ctrl对象,用于接收unsolicited event monitor_conn[index] = wpa_ctrl_open(path); ...... // 必须调用wpa_ctrl_attach函数以启用unsolicited event接收功能 if (wpa_ctrl_attach(monitor_conn[index]) != 0) {......} // 创建一个socketpair,它用于触发WifiNative关闭和WPAS的连接 if (socketpair(AF_UNIX, SOCK_STREAM, 0, exit_sockets[index]) == -1) {......} return 0; } ~~~ 由于Android 4.2支持两个并发设备,所以每个并发设备各有两个wpa_ctrl对象。 * ctrl_conn[PRIMARY]、monitor_conn[PRIMARY]:用于STA设备。ctrl_conn用于向WPAS发送命令并接收对应命令的回复,而monitor_conn用于接收来自WPAS的unsolicited event。 * ctrl_conn[SECONDARY]、monitor_conn[SECONDARY]:这两个wpa_ctrl对象用于P2P设备。 另外,exit_sockets保存了socketpair创建的socket句柄,这些句柄用于WifiService通知WifiNative去关闭它和WPAS的连接。 >[info] 提示 wifi.c中,wifi_send_command会使用ctrl_conn中的wpa_ctrl对象向WPAS发送命令并接收回复,而wifi_recv函数将使用monitor_conn中的wpa_ctrl对象接收来自WPAS的消息。这两个函数比较简单,请读者可自行阅读它。 下面来看WifiMonitor,它将使用monitor_conn中的wpa_ctrl对象。 **②、WifiMonitor** WifiMonitor最重要的内容是其内部的WifiMonitor线程,该线程专门用于接收来自WPAS的消息。代码如下所示。 **WifiMonitor.java::MonitorThread** ~~~ class MonitorThread extends Thread { public MonitorThread() { super("WifiMonitor"); } public void run() { if (connectToSupplicant()) {// 连接WPAS, mStateMachine指向WifiStateMachine // 连接成功后,将向WifiStateMachine发送SUP_CONNECTION_EVENT消息 mStateMachine.sendMessage(SUP_CONNECTION_EVENT); } else { mStateMachine.sendMessage(SUP_DISCONNECTION_EVENT); return; } for (;;) { // waitForEvent内部会调用wifi.c中的wifi_wait_on_socket函数 String eventStr = mWifiNative.waitForEvent(); // 解析WPAS的消息格式。EVENT_PREFIX_STR的值为“CTRL-EVENT-” if (!eventStr.startsWith(EVENT_PREFIX_STR)) { ......// 非“CTRL-EVENT-”消息 continue; } // 处理“CTRL-EVENT-”消息 String eventName = eventStr.substring(EVENT_PREFIX_LEN_STR); int nameEnd = eventName.indexOf(' '); if (nameEnd != -1) eventName = eventName.substring(0, nameEnd); ...... int event; if (eventName.equals(CONNECTED_STR))// 对应为“CONNECTED”消息 event = CONNECTED; ...... else if (eventName.equals(STATE_CHANGE_STR))// 对应为“STATE-CHANGED” event = STATE_CHANGE; else if (eventName.equals(SCAN_RESULTS_STR))// 对应为“SCAN-RESULTS” event = SCAN_RESULTS; ...... else if (eventName.equals(DRIVER_STATE_STR))// 对应为“DRIVER-STATE” event = DRIVER_STATE; else if (eventName.equals(EAP_FAILURE_STR))// 对应为“EAP-FAILURE” event = EAP_FAILURE; else event = UNKNOWN; /* 提取消息中的其他信息,以CONNECTED消息为例,其消息全内容为: CTRL-EVENT-CONNECTED - Connection to xx:xx:xx:xx:xx:xx completed 其中,xx:xx:xx:xx:xx:xx代表目标AP的BSSID。 */ String eventData = eventStr; if (event == DRIVER_STATE || event == LINK_SPEED) eventData = eventData.split(" ")[1]; else if (event == STATE_CHANGE || event == EAP_FAILURE) { ...... } ...... if (event == STATE_CHANGE) {// WPAS状态发生变化 handleSupplicantStateChange(eventData); } else if (event == DRIVER_STATE) { handleDriverEvent(eventData); }...... else {// 其他事件处理 handleEvent(event, eventData); } mRecvErrors = 0; } } ...... } ~~~ 上述代码中: * handleSupplicantStateChange用于处理WPAS的状态变化(见下文解释),它将先把这些变化信息交给WifiStateMachine去处理。而WifiStateMachine将根据处理情况再决定是否需要由下一节介绍的SupplicantStateTracker来处理。handleSupplicant StateChange代码比较简单,读者可自行阅读它。 * handleDriverEvent用于处理来Driver的信息[^②]。 * handleEvent用于处理其他消息事件。详情见下文。 >[info] 注意 WPAS的状态指的是wpa_sm状态机中的状态,包括WPA_DISCONNECTED、WPA_INTERFACE_DISABLED、WPA_INACTIVE、WPA_SCANNING、WPA_AUTHENTICATING、WPA_ASSOCIATING、WPA_ASSOCIATED、WPA_4WAY_HANDSHAKE、WPA_GROUP_HANDSHAKE、WPA_COMPLETED共10个状态。WifiService定义了SupplicantState类来描述WPAS的状态,包括DISCONNECTED、INTERFACE_DISABLED、INACTIVE、SCANNING、AUTHENTICATING、ASSOCIATING、ASSOCIATED、FOUR_WAY_HANDSHAKE、GROUP_HANDSHAKE、COMPLETED、DORMANT、UNINITIALIZED、INVALID共13个状态。其中最后三个状态是WifiService定义的,但笔者在代码中没有找到使用它们的地方。 下面简单介绍handleEvent,其代码如下所示。 **WifiMonitor.java::handleEvent** ~~~ void handleEvent(int event, String remainder) { switch (event) { case DISCONNECTED: handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder); break; case CONNECTED:// 该事件表示WPAS成功加入一个无线网络 handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder); break; case SCAN_RESULTS:// 该事件表示WPAS已经完成扫描,客户端可以来查询扫描结果 mStateMachine.sendMessage(SCAN_RESULTS_EVENT);// 处理扫描结果消息 break; case UNKNOWN: break; } } ~~~ 先介绍SupplicantStateTracker。后文再分析CONNECTED和SCAN_RESULTS消息的处理流程。 **③、SupplicantStateTracker** SupplicantStateTracker用于跟踪和处理WPAS的状态变化。根据前面对WPAS中的状态以及WifiService中的状态介绍可知。在WifiService中,WPAS的状态由SupplicantState来表示,而和它相关的状态管理模块就是此处的SupplicantStateTracker。SupplicantStateTracker也从StateMachine派生,并且它还定义了8个状态对象。相关代码如下所示。 **SupplicantStateTracker.java::SupplicantStateTracker** ~~~ public SupplicantStateTracker(Context c, WifiStateMachine wsm,WifiConfigStore wcs, Handler t) { super(TAG, t.getLooper()); mContext = c; mWifiStateMachine = wsm; mWifiConfigStore = wcs; addState(mDefaultState); addState(mUninitializedState, mDefaultState); addState(mInactiveState, mDefaultState); addState(mDisconnectState, mDefaultState); addState(mScanState, mDefaultState); addState(mHandshakeState, mDefaultState); addState(mCompletedState, mDefaultState); addState(mDormantState, mDefaultState); setInitialState(mUninitializedState);// 初始状态为mUninitializedState start();// 启动状态机 } ~~~ * SupplicantState中的AUTHENTICATING、ASSOCIATING、ASSOCIATED、FOUR_WAY_HANDSHAKE和GROUP_HANDSHAKE均对应于此处的mHandshakeState。 * SupplicantState中的UNINITIALIZED和INVALID对应于此处的mUnitializedState。SupplicantStateTracker比较简单,而且它也不影响本章的分析流程。读者可在阅读完本章的基础上,自行对其开展研究。 下面来看WifiStateMachine构造函数的最后一部分。 **2、WifiStateMachine构造函数分析之二** **WifiStateMachine.java::WifiStateMachine构造函数代码段二** ~~~ ......// WifiStateMachine中的状态。说实话,笔者还没见过如此复杂的状态机 addState(mDefaultState); addState(mInitialState, mDefaultState); addState(mDriverUnloadingState, mDefaultState); addState(mDriverUnloadedState, mDefaultState); addState(mDriverFailedState, mDriverUnloadedState); ......// WifiStateMachine一共定义了30个状态 addState(mSoftApStoppingState, mDefaultState); setInitialState(mInitialState);// 设置初始状态为mInitialState ......// 和StateMachine日志记录相关设置 start();// 启动状态机 } ~~~ WifiStateMachine共定义30个状态,其种类和层级关系如图5-4所示。 :-: ![](https://box.kancloud.cn/f6aecfa81a54392f17c67917eba6ce38_1186x524.jpg) 图5-4 WifiStateMachine中的状态及层级关系 图5-4中,箭头所指的状态为父状态。本节先介绍和初始状态的相关代码,其他状态的功能等碰到它们时再来分析。 >[info] 提示 如果算上SupplicantStateTracker中的8个状态以及后续章节将要介绍的P2pStateMachine中的15个状态,Java层中Wi-Fi相关的状态机竟然多达63个状态(还没有计算Wi-Fi模块其他代码中定义的好些个状态机所包含的状态)。笔者很难理解为什么WifiService相关模块会定义如此多的状态。这些状态使得WifiService的分析难度陡增。而且,在整个Wi-Fi模块中,wpa_supplicant作为核心已经完成了绝大部分的工作,为什么WifiService还会如此复杂呢?欢迎读者对此问题和笔者展开讨论。 WifiStateMachine的初始状态是mInitialState,其类型是InitialState。根据前文对HSM的介绍,其enter函数将被调用(由于InitialState的父状态DefaultState并未实现enter函数,故此处略去)。 **WifiStateMachine.java::InitialState:enter** ~~~ class InitialState extends State { public void enter() { // 判断Wlan Driver是否已经加载,其内部实现通过"wlan.driver.status"属性的值来判断 if (mWifiNative.isDriverLoaded()) transitionTo(mDriverLoadedState); else transitionTo(mDriverUnloadedState);// 假设此时驱动还没有加载,故我们将转入此状态 // 获取和WifiP2pService交互的对象 mWifiP2pManager = (WifiP2pManager) mContext.getSystemService( Context.WIFI_P2P_SERVICE); // mWifiP2pChannel用于和WifiP2pService中的某个Handler交互 mWifiP2pChannel.connect(mContext, getHandler(), mWifiP2pManager.getMessenger()); try { mNwService.disableIpv6(mInterfaceName); // 禁止Ipv6,NWService将和Netd交互 } ...... } } ~~~ 结合上述代码,当WifiStateMachine开始运行后,其最终将进入DriverUnloadedState。由于DriverUnloadedState的enter函数没有做什么有意义的工作,所以此处不再讨论它。 至此,WifiService第一条分析路线就算结束。虽然WifiService创建工作涉及的流程并不长,但相信读者也会感觉WifiService的代码难度其实并不算小。从下一节开始,读者还将进一步体会到这一点。 [^①]:可参考《深入理解Android:卷Ⅰ》第2章JNI相关的重要知识。 [^②]:笔者搜索了相关代码,在wlan芯片厂商提供的一些供WPAS使用的动态库中会发送DRIVER-EVENT。相关代码可参考 hardware/broadcom/wlan/bcmdhd/wpa_supplicant_8_lib/driver_cmd_nl80211.c