wifi详解(二)
1 Wifi模块解析和启动流程
1.1 框架分析
WIFI整体框架如图所示:
首先,用户程序使用WifiManager类来管理Wifi模块,它能够获得Wifi模块的状态,配置和控制Wifi模块,而所有这些操作都要依赖 Wifiservice类来实现。
WifiService和WifiMonitor类是Wifi框架的核心,如图所示。下面先来看看WifiService是什么时候,怎么被创建和初始化 的。
在systemServer启动之后,它会创建一个 ConnectivityServer对象,这个对象的构造函数会创建一个WifiService的实例,代码如下所示:
framework/base/services/java/com/android/server/ConnectivityService.java
{
……
case ConnectivityManager.TYPE_WIFI:
if(DBG) Slog.v(TAG, "Starting Wifi Service.");
WifiStateTrackerwst = new WifiStateTracker(context,mHandler); //创建WifiStateTracker实例
WifiService wifiService = newWifiService(context,wst);//创建WifiService实例
ServiceManager.addService(Context.WIFI_SERVICE,wifiService); //向服务管理系统添加Wifi服务
wifiService.startWifi(); //启动Wifi
mNetTrackers[ConnectivityManager.TYPE_WIFI]= wst;
wst.startMonitoring(); //启动WifiMonitor中的WifiThread线程
……
}
WifiService的主要工作:WifiMonitor和Wpa_supplicant的启动和关闭,向Wpa_supplicant发送命令。
WifiMonitor的主要工作:阻塞监听并接收来自Wpa_supplicant的消息,然后发送给WifiStateTracker。
上面两个线程通过AF_UNIX套接字和Wpa_supplicant通信,在通信过程中有两种连接方式:控制连接和监听连接。它们创建代码如下:
ctrl_conn =wpa_ctrl_open(ifname);
.. .. ..
monitor_conn = wpa_ctrl_open(ifname);
1.2 Wifi启动流程
(1)使能Wifi
要想使用Wifi模块,必须首先使能Wifi,当你第一次按下Wifi使能按钮时,WirelessSettings会实例化一个WifiEnabler 对象,实例化代码如下:
packages/apps/settings/src/com/android/settings/WirelessSettings.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
……
CheckBoxPreferencewifi = (CheckBoxPreference) findPreference(KEY_TOGGLE_WIFI);
mWifiEnabler=new WifiEnabler(this, wifi);
……
}
WifiEnabler类的定义大致如下,它实现了一个监听接口,当WifiEnabler对象被初始化后,它监听到你按键的动作,会调用响应函数 onPreferenceChange(),这个函数会调用WifiManager的setWifiEnabled()函数。
public class WifiEnabler implementsPreference.OnPreferenceChangeListener{
……
public boolean onPreferenceChange(Preference preference,Objectvalue) {
booleanenable = (Boolean)value;
……
if (mWifiManager.setWifiEnabled(enable)) {
mCheckBox.setEnabled(false);
……
}
……
}
我们都知道Wifimanager只是个服务代理,所以它会调用WifiService的setWifiEnabled()函数,而这个函数会调用 sendEnableMessage()函数,了解android消息处理机制的都知道,这个函数最终会给自己发送一个 MESSAGE_ENABLE_WIFI的消息,被WifiService里面定义的handlermessage()函数处理,会调用 setWifiEnabledBlocking()函数。下面是调用流程:
mWifiEnabler.onpreferencechange()=>mWifiManage.setWifienabled()=>mWifiService.setWifiEnabled()=>mWifiService.sendEnableMessage()=>mWifiService.handleMessage()=>mWifiService.setWifiEnabledBlocking().
在 setWifiEnabledBlocking()函数中主要做如下工作:加载Wifi驱动,启动wpa_supplicant,注册广播接收器,启动 WifiThread监听线程。代码如下:
……
if (enable) {
if(!mWifiStateTracker.loadDriver()) {
Slog.e(TAG,"Failed toload Wi-Fi driver.");
setWifiEnabledState(WIFI_STATE_UNKNOWN,uid);
return false;
}
if(!mWifiStateTracker.startSupplicant()) {
mWifiStateTracker.unloadDriver();
Slog.e(TAG, "Failed tostart supplicant daemon.");
setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
return false;
}
registerForBroadcasts();
mWifiStateTracker.startEventLoop();
……
至此,Wifi使能结束,自动进入扫描阶段。
(2) 扫描AP
当驱动加载成功后,如果配置文件的AP_SCAN = 1,扫描会自动开始,WifiMonitor将会从supplicant收到一个消息EVENT_DRIVER_STATE_CHANGED,调用 handleDriverEvent(),然后调用mWifiStateTracker.notifyDriverStarted(),该函数向消息队列 添加EVENT_DRIVER_STATE_CHANGED,handlermessage()函数处理消息时调用scan()函数,并通过 WifiNative将扫描命令发送到wpa_supplicant。
Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java
private void handleDriverEvent(Stringstate) {
if(state == null) {
return;
}
if(state.equals("STOPPED")) {
mWifiStateTracker.notifyDriverStopped();
}else if (state.equals("STARTED")) {
mWifiStateTracker.notifyDriverStarted();
}else if (state.equals("HANGED")) {
mWifiStateTracker.notifyDriverHung();
}
}
Frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java
case EVENT_DRIVER_STATE_CHANGED:
switch(msg.arg1) {
case DRIVER_STARTED:
/**
*Set the number of allowed radio channels according
*to the system setting, since it gets reset by the
*driver upon changing to the STARTED state.
*/
setNumAllowedChannels();
synchronized(this) {
if(mRunState == RUN_STATE_STARTING) {
mRunState= RUN_STATE_RUNNING;
if(!mIsScanOnly) {
reconnectCommand();
}else {
// In somesituations, supplicant needs to be kickstarted to
// start thebackground scanning
scan(true);
}
}
}
break;
上面是启动Wifi 时,自动进行的AP的扫描,用户当然也可以手动扫描AP,这部分实现在WifiService里面,WifiService通过startScan()接 口函数发送扫描命令到supplicant。
Frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java
public boolean startScan(booleanforceActive) {
enforceChangePermission();
switch(mWifiStateTracker.getSupplicantState()) {
caseDISCONNECTED:
caseINACTIVE:
caseSCANNING:
caseDORMANT:
break;
default:
mWifiStateTracker.setScanResultHandling(
WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY);
break;
}
return mWifiStateTracker.scan(forceActive);
}
然后下面的流程同上面的自动扫描,我们来分析一下手动扫描从哪里开始的。我们应该知道手动扫描是通过菜单键的扫描键来响应的,而响应该动作的应该是 WifiSettings类中Scanner类的handlerMessage()函数,它调用WifiManager的 startScanActive(),这才调用WifiService的startScan()。
packages/apps/Settings/src/com/android/settings/wifiwifisettings.java
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE,MENU_ID_SCAN, 0, R.string.wifi_menu_scan)
.setIcon(R.drawable.ic_menu_scan_network);
menu.add(Menu.NONE,MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
.setIcon(android.R.drawable.ic_menu_manage);
returnsuper.onCreateOptionsMenu(menu);
}
当按下菜单键时,WifiSettings就会调用这个函数绘制菜单。如果选择扫描按钮,WifiSettings会调用 onOptionsItemSelected()。
packages/apps/Settings/src/com/android/settings/wifiwifisettings.java
public booleanonOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
caseMENU_ID_SCAN:
if(mWifiManager.isWifiEnabled()) {
mScanner.resume();
}
return true;
caseMENU_ID_ADVANCED:
startActivity(new Intent(this,AdvancedSettings.class));
return true;
}
returnsuper.onOptionsItemSelected(item);
}
private class Scanner extends Handler {
private int mRetry = 0;
void resume() {
if (!hasMessages(0)) {
sendEmptyMessage(0);
}
}
void pause() {
mRetry= 0;
mAccessPoints.setProgress(false);
removeMessages(0);
}
@Override
public voidhandleMessage(Message message) {
if(mWifiManager.startScanActive()){
mRetry = 0;
}else if (++mRetry >= 3) {
mRetry = 0;
Toast.makeText(WifiSettings.this,R.string.wifi_fail_to_scan,
Toast.LENGTH_LONG).show();
return;
}
mAccessPoints.setProgress(mRetry!= 0);
sendEmptyMessageDelayed(0, 6000);
}
}
这里的mWifiManager.startScanActive()就会调用WifiService里 的startScan()函数,下面的流程和上面的一样,这里不赘述。
当supplicant完成了这个扫描命令后,它会发送一个消息给上 层,提醒他们扫描已经完成,WifiMonitor会接收到这消息,然后再发送给WifiStateTracker。
Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java
void handleEvent(int event, String remainder) {
switch (event) {
caseDISCONNECTED:
handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED,remainder);
break;
case CONNECTED:
handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED,remainder);
break;
case SCAN_RESULTS:
mWifiStateTracker.notifyScanResultsAvailable();
break;
case UNKNOWN:
break;
}
}
WifiStateTracker将会广播SCAN_RESULTS_AVAILABLE_ACTION消息:
Frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java
public voidhandleMessage(Message msg) {
Intent intent;
……
case EVENT_SCAN_RESULTS_AVAILABLE:
if(ActivityManagerNative.isSystemReady()) {
mContext.sendBroadcast(newIntent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
}
sendScanResultsAvailable();
/**
* On receiving the first scanresults after connecting to
* the supplicant, switch scanmode over to passive.
*/
setScanMode(false);
break;
……
}
由于WifiSettings类注册了intent,能够处理SCAN_RESULTS_AVAILABLE_ACTION消息,它会调用 handleEvent(),调用流程如下所示。
WifiSettings.handleEvent()=>WifiSettings.updateAccessPoints() => mWifiManager.getScanResults()=> mService.getScanResults()=> mWifiStateTracker.scanResults() =>WifiNative.scanResultsCommand()……
将 获取AP列表的命令发送到supplicant,然后supplicant通过Socket发送扫描结果,由上层接收并显示。这和前面的消息获取流程基本 相同。
(3)配置,连接AP
当用户选择一个活跃的AP时,WifiSettings响应打开一个对话框来配 置AP,比如加密方法和连接AP的验证模式。配置好AP后,WifiService添加或更新网络连接到特定的AP。
packages/apps/settings/src/com/android/settings/wifi/WifiSetttings.java
public booleanonPreferenceTreeClick(PreferenceScreen screen,Preference preference) {
if (preference instanceofAccessPoint) {
mSelected= (AccessPoint) preference;
showDialog(mSelected, false);
} else if (preference ==mAddNetwork) {
mSelected= null;
showDialog(null,true);
} else if (preference ==mNotifyOpenNetworks) {
Secure.putInt(getContentResolver(),
Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
mNotifyOpenNetworks.isChecked()? 1 : 0);
} else {
returnsuper.onPreferenceTreeClick(screen, preference);
}
return true;
}
配置好以后,当按下“Connect Press”时,WifiSettings通过发送LIST_NETWORK命令到supplicant来检查该网络是否配置。如果没有该网络或没有配置 它,WifiService调用addorUpdateNetwork()函数来添加或更新网络,然后发送命令给supplicant,连接到这个网络。 下面是从响应连接按钮到WifiService发送连接命令的代码:
packages/apps/settings/src/com/android/settings/wifi/WifiSetttings.java
public void onClick(DialogInterfacedialogInterface, int button) {
if (button ==WifiDialog.BUTTON_FORGET && mSelected != null) {
forget(mSelected.networkId);
} else if (button ==WifiDialog.BUTTON_SUBMIT && mDialog !=null) {
WifiConfigurationconfig = mDialog.getConfig();
if(config == null) {
if (mSelected != null&& !requireKeyStore(mSelected.getConfig())) {
connect(mSelected.networkId);
}
}else if (config.networkId != -1) {
if (mSelected != null) {
mWifiManager.updateNetwork(config);
saveNetworks();
}
}else {
intnetworkId =mWifiManager.addNetwork(config);
if (networkId != -1) {
mWifiManager.enableNetwork(networkId,false);
config.networkId =networkId;
if (mDialog.edit || requireKeyStore(config)){
saveNetworks();
} else {
connect(networkId);
}
}
}
}
}
Frameworks\base\wifi\java\android\net\wifi\WifiManager.java
public intupdateNetwork(WifiConfiguration config) {
if(config == null ||config.networkId < 0) {
return-1;
}
returnaddOrUpdateNetwork(config);
}
private intaddOrUpdateNetwork(WifiConfiguration config) {
try {
return mService.addOrUpdateNetwork(config);
} catch (RemoteExceptione) {
return-1;
}
}
WifiService.addOrUpdateNetwork()通过调用mWifiStateTracker.setNetworkVariable()将连接命令发送到Wpa_supplicant。
(4) 获取IP地址
当连接到supplicant后,WifiMonitor就会通知WifiStateTracker。
Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java
Public void Run(){
if (connectToSupplicant()) {
// Send a message indicatingthat it is now possible to send commands
// tothe supplicant
mWifiStateTracker.notifySupplicantConnection();
}else {
mWifiStateTracker.notifySupplicantLost();
return;
}
……
}
WifiStateTracker 发送EVENT_SUPPLICANT_CONNECTION消息到消息队列,这个消息有自己的handlermessage()函数处理,它会启动一个 DHCP线程,而这个线程会一直等待一个消息事件,来启动DHCP协议分配IP地址。
frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java
void notifySupplicantConnection() {
sendEmptyMessage(EVENT_SUPPLICANT_CONNECTION);
}
public void handleMessage(Message msg) {
Intent intent;
switch (msg.what) {
caseEVENT_SUPPLICANT_CONNECTION:
……
HandlerThread dhcpThread =newHandlerThread("DHCP Handler Thread");
dhcpThread.start();
mDhcpTarget =newDhcpHandler(dhcpThread.getLooper(), this);
……
……
}
当 Wpa_supplicant连接到AP后,它会发送一个消息给上层来通知连接成功,WifiMonitor会接受到这个消息并上报给 WifiStateTracker。
Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java
void handleEvent(int event, String remainder) {
switch(event) {
caseDISCONNECTED:
handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED,remainder);
break;
caseCONNECTED:
handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED,remainder);
break;
……
}
private void handleNetworkStateChange(NetworkInfo.DetailedStatenewState, String data) {
StringBSSID = null;
intnetworkId = -1;
if(newState ==NetworkInfo.DetailedState.CONNECTED) {
Matchermatch = mConnectedEventPattern.matcher(data);
if(!match.find()) {
if(Config.LOGD) Log.d(TAG, "Could not find BSSID in CONNECTEDeventstring");
}else {
BSSID= match.group(1);
try{
networkId= Integer.parseInt(match.group(2));
}catch (NumberFormatException e) {
networkId= -1;
}
}
}
mWifiStateTracker.notifyStateChange(newState,BSSID,networkId);
}
void notifyStateChange(DetailedState newState, StringBSSID, intnetworkId) {
Messagemsg =Message.obtain(
this,EVENT_NETWORK_STATE_CHANGED,
newNetworkStateChangeResult(newState, BSSID, networkId));
msg.sendToTarget();
}
caseEVENT_NETWORK_STATE_CHANGED:
……
configureInterface();
……
private void configureInterface() {
checkPollTimer();
mLastSignalLevel = -1;
if(!mUseStaticIp){ //使用DHCP线程动态IP
if(!mHaveIpAddress && !mObtainingIpAddress) {
mObtainingIpAddress= true;
//发送启动DHCP线程获取IP
mDhcpTarget.sendEmptyMessage(EVENT_DHCP_START);
}
} else{ //使用静态IP,IP信息从mDhcpInfo中获取
intevent;
if(NetworkUtils.configureInterface(mInterfaceName,mDhcpInfo)) {
mHaveIpAddress= true;
event= EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;
if(LOCAL_LOGD) Log.v(TAG, "Static IP configurationsucceeded");
}else {
mHaveIpAddress= false;
event= EVENT_INTERFACE_CONFIGURATION_FAILED;
if(LOCAL_LOGD) Log.v(TAG, "Static IP configuration failed");
}
sendEmptyMessage(event); //发送IP获得成功消息事件
}
}
DhcpThread获取EVENT_DHCP_START消息事件后,调用handleMessage()函数,启动DHCP获取IP地址的服务。
public void handleMessage(Message msg) {
intevent;
switch (msg.what) {
caseEVENT_DHCP_START:
……
Log.d(TAG, "DhcpHandler: DHCP requeststarted");
//启动一个DHCPclient的精灵进 程,为mInterfaceName请求分配一个IP地//址
if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {
event=EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;
if(LOCAL_LOGD)Log.v(TAG, "DhcpHandler: DHCP request succeeded");
} else {
event=EVENT_INTERFACE_CONFIGURATION_FAILED;
Log.i(TAG,"DhcpHandler: DHCP request failed: " +
NetworkUtils.getDhcpError());
}
……
}
这 里调用了一个NetworkUtils.runDhcp()函数,NetworkUtils类是一个网络服务的辅助类,它主要定义了一些本地接口,这些接 口会通过他们的JNI层android_net_NetUtils.cpp文件和DHCP client通信,并获取IP地址。
至此,IP 地址获取完毕,Wifi启动流程结束。
wifi详解(二)的更多相关文章
- .NET DLL 保护措施详解(二)关于性能的测试
先说结果: 加了缓存的结果与C#原生代码差异不大了 我对三种方式进行了测试: 第一种,每次调用均动态编译 第二种,缓存编译好的对象 第三种,直接调用原生C#代码 .net dll保护系列 ------ ...
- PopUpWindow使用详解(二)——进阶及答疑
相关文章:1.<PopUpWindow使用详解(一)——基本使用>2.<PopUpWindow使用详解(二)——进阶及答疑> 上篇为大家基本讲述了有关PopupWindow ...
- Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)
[Android布局学习系列] 1.Android 布局学习之——Layout(布局)详解一 2.Android 布局学习之——Layout(布局)详解二(常见布局和布局参数) 3.And ...
- logback -- 配置详解 -- 二 -- <appender>
附: logback.xml实例 logback -- 配置详解 -- 一 -- <configuration>及子节点 logback -- 配置详解 -- 二 -- <appen ...
- 爬虫入门之urllib库详解(二)
爬虫入门之urllib库详解(二) 1 urllib模块 urllib模块是一个运用于URL的包 urllib.request用于访问和读取URLS urllib.error包括了所有urllib.r ...
- [转]文件IO详解(二)---文件描述符(fd)和inode号的关系
原文:https://www.cnblogs.com/frank-yxs/p/5925563.html 文件IO详解(二)---文件描述符(fd)和inode号的关系 ---------------- ...
- Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)
View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇 ...
- HTTPS详解二:SSL / TLS 工作原理和详细握手过程
HTTPS 详解一:附带最精美详尽的 HTTPS 原理图 HTTPS详解二:SSL / TLS 工作原理和详细握手过程 在上篇文章HTTPS详解一中,我已经为大家介绍了 HTTPS 的详细原理和通信流 ...
- Linux dts 设备树详解(二) 动手编写设备树dts
Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...
- pika详解(二) BlockingConnection
pika详解(二) BlockingConnection 本文链接:https://blog.csdn.net/comprel/article/details/94592348 版权 Blocki ...
随机推荐
- Sql注入中连接字符串常用函数
在select数据时,我们往往需要将数据进行连接后进行回显.很多的时候想将多个数据或者多行数据进行输出的时候,需要使用字符串连接函数.在sqli中,常见的字符串连接函数有concat(),group_ ...
- linux取出某几行
一.从第3000行开始,显示1000行.即显示3000~3999行cat filename | tail -n +3000 | head -n 1000 二.显示1000行到3000行cat file ...
- NSString+URLEncoding.h --使用Obj-C对数据等进行URLEncoding编码
在Objective-c进行网络编程时,经常需要把数据转换成URLEncoding编码,如对+号编码后,变成%2b.这里我们给出一种实现. //NSString+URLEncoding.h #impo ...
- POJ3250Bad Hair Day
http://poj.org/problem?id=3250 题意 : n个牛排成一列向右看,牛 i 能看到牛 j 的头顶,当且仅当牛 j 在牛 i 的右边并且牛 i 与牛 j 之间的所有牛均比牛 ...
- 欧拉工程第57题:Square root convergents
题目链接 Java程序 package projecteuler51to60; import java.math.BigInteger; import java.util.Iterator; impo ...
- lintcode: 翻转链表
题目: 翻转链表 翻转一个链表 样例 给出一个链表1->2->3->null,这个翻转后的链表为3->2->1->null 挑战 在原地一次翻转完成 解题: 递归还 ...
- POJ1118 Lining Up
快弄死我了 最后的原因是abs和fabs的区别... 说点收获:1.cmp函数返回的是int,所以不要直接返回double相减的结果2.define inf 1e9和eps 1e-93.在整数相除得到 ...
- Python 中的 TK编程
可爱的 Python:Python 中的 TK编程 http://www.ibm.com/developerworks/cn/linux/sdk/python/charm-12/ python che ...
- JavaWeb项目开发案例精粹-第3章在线考试系统-006实体层
1. package com.sanqing.po; /* * 学生表,保存学生编号,系统密码 */ public class Student { private String studentID; ...
- 简单的自绘CListBox,重载虚MeasureItem和DrawItem这两个虚函数
[cpp] view plain copy //例如CNewListBox继承自CListBox,重载虚MeasureItem和DrawItem这两个虚函数,代码如下: void CNewListBo ...