Android4.4.2原始碼分析之WiFi模組(二)
接著上一篇繼續對WiFi原始碼的分析
onResume方法中
6>,首先是呼叫WiFiEnabler的resume方法對switch進行管理
接下來註冊廣播
getActivity().registerReceiver(mReceiver, mFilter);
廣播監聽的action如下
//wifi狀態改變的action mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); //WiFi掃描到附近可用WiFi時的廣播 mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); // mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
檢視WiFiManager發現各action定義如下,以及在監聽到對應廣播後各處理如下
i>
/** * Broadcast intent action indicating that Wi-Fi has been enabled, disabled, * enabling, disabling, or unknown. One extra provides this state as an int. * Another extra provides the previous state, if available. * * @see #EXTRA_WIFI_STATE * @see #EXTRA_PREVIOUS_WIFI_STATE */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String WIFI_STATE_CHANGED_ACTION = "android.net.wifi.WIFI_STATE_CHANGED";
WIFI_STATE_CHANGED_ACTION:當WiFi被開啟、關閉、正在開啟、正在關閉或者位置狀態即wifi狀態發生改變時系統會自動傳送該廣播,該廣播會附帶有兩個值,一個是int型表示改變後的state,可通過欄位EXTRA_WIFI_STATE獲取,還有一個是int型的改變前的state(如果有的話)可通過欄位EXTRA_PREVIOUS_WIFI_STATE獲取
當監聽到該廣播後會進行如下處理:更新WiFi狀態(在WiFiEnbabler中也監聽了該廣播,用於當WiFi狀態改變時對switch進行更新)
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN));
updateWifiState方法如下
private void updateWifiState(int state) {
Activity activity = getActivity();
if (activity != null) {
//重新載入選單 ,該方法會呼叫activity中的onCreateOptionsMenu載入actionbar
activity.invalidateOptionsMenu();
}
switch (state) {
case WifiManager.WIFI_STATE_ENABLED://開啟WiFi
mScanner.resume();//從下面的方法中可以看到,該方法是用於開啟WiFi的掃描,並記錄掃描次數
return; // not break, to avoid the call to pause() below
case WifiManager.WIFI_STATE_ENABLING://正在開啟WiFi
addMessagePreference(R.string.wifi_starting);
break;
case WifiManager.WIFI_STATE_DISABLED://關閉WiFi
//使用者可以在wlan-->高階選項中去設定時是否隨時都可以掃描(關閉WiFi後也可以掃描),根據使用者的選擇,
//設定在關閉WLAN後顯示介面上的文字
setOffMessage();
break;
}
mLastInfo = null;
mLastState = null;
mScanner.pause();//移除message通知
}
因為更新的方法中涉及到了Scanner,這裡的Scanner是自定義的內部類,繼承自handler,程式碼如下
private class Scanner extends Handler {
private int mRetry = 0;
void resume() {
if (!hasMessages(0)) {
sendEmptyMessage(0);
}
}
void forceScan() {
removeMessages(0);
sendEmptyMessage(0);
}
void pause() {
mRetry = 0;
removeMessages(0);
}
@Override
public void handleMessage(Message message) {
if (mWifiManager.startScan()) {
mRetry = 0;
} else if (++mRetry >= 3) {
mRetry = 0;
Activity activity = getActivity();
if (activity != null) {
Toast.makeText(activity, R.string.wifi_fail_to_scan,
Toast.LENGTH_LONG).show();
}
return;
}
sendEmptyMessageDelayed(0, 10*1000);//10s後再次傳送message
}
}
可以看到,掃描附近可用WiFi的方法為mWifiManager.startScan()該方法對使用者可見,可直接呼叫
ii>
/**
* An access point scan has completed, and results are available from the supplicant.
* Call {@link #getScanResults()} to obtain the results.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS";
SCAN_RESULT_AVAILABLE_ACTION:WiFi掃描結束時系統會發送該廣播,使用者可以監聽該廣播通過呼叫WifiManager的getScanResults方法來獲取到掃描結果
else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
updateAccessPoints();
當用戶掃描到WiFi或者WiFi資訊發生改變時都需要去更新WiFi列表,更新WiFi列表的方法為updateAccessPoints(),掃描載入列表這塊感覺相當複雜,慢慢分析
首先在掃描結束後可以通過getScanResult()獲取到掃描後的WiFi列表,返回值為List<ScanResult>,所返回的每個WiFi會攜帶如下資訊
各欄位分別代表什麼含義呢?
BSSID:The address of the access point,接入點地址(String值)
SSID:The network name,WiFi名字(String值)
capabilities:Describes the authentication,key management,and encryption schemes supported by the access point,描述接入點的身份驗證,金鑰管理和加密方案(String值)
wifiSsid:ASCII encode SSID,This will replace SSID when we deprecate it ,SSID的ASCII碼編碼,當不支援它時我們可以用它來代替ssid(WifiSsid值)
timestamp:timestamp in microseconds (since boot)when this result was last seen,距離上一次的更改的微秒數
level:對於level的定義從原始碼中可以看到,表示訊號的強度,屬於int型數值,
/**
* The detected signal level in dBm, also known as the RSSI.
*
* <p>Use {@link android.net.wifi.WifiManager#calculateSignalLevel} to convert this number into
* an absolute signal level which can be displayed to a user.
*/
public int level;
對於訊號強度的顯示可以通過如下程式碼
mWifiLevel.setImageLevel(WifiManager.calculateSignalLevel(mList.get(position).level,4));
frequency:The primary 20 MHz frequency (in MHz) of the channel over which the client is communicating with the access point.客戶端與接入點通訊的頻率
我們一般顯示WiFi列表用到的是SSID、level和capabilities
Android原始碼中掃描到WiFi後就需要去載入列表,在接收到SCAN_RESULT_AVAILABLE_ACTION廣播後呼叫updateAccessPoints方法進行更新列表,在該方法中會根據WiFi的開關狀態來對UI進行更新,只有在WiFi開啟時 才會去更新列表,這裡不再對其他情況進行贅述,在WiFi開啟時會通過如下程式碼載入
private void updateAccessPoints() {
// Safeguard from some delayed event handling
if (getActivity() == null) return;
if (isRestrictedAndNotPinProtected()) {
addMessagePreference(R.string.wifi_empty_list_user_restricted);
return;
}
final int wifiState = mWifiManager.getWifiState();
switch (wifiState) {
case WifiManager.WIFI_STATE_ENABLED:
// AccessPoints are automatically sorted with TreeSet.
//獲取到接入點列表
final Collection<AccessPoint> accessPoints = constructAccessPoints();
if (!getResources().getBoolean(R.bool.set_wifi_priority)) {
getPreferenceScreen().removeAll();
}
if(accessPoints.size() == 0) {
addMessagePreference(R.string.wifi_empty_list_wifi_on);
}
if (!getResources().getBoolean(R.bool.set_wifi_priority)) {
for (AccessPoint accessPoint : accessPoints) {
<pre name="code" class="java">
//WiFisettings的xml檔案的根節點為preferencescreen,所以通過如下方法新增
preferencegetPreferenceScreen().addPreference(accessPoint); } }
if (accessPoints.isEmpty()){ addMessagePreference(R.string.wifi_empty_list_wifi_on); }
break; case WifiManager.WIFI_STATE_ENABLING://如果WiFi處於正在開啟的狀態,則清除列表 。。。。。。。。。 } }
那麼接入點列表的獲取是如何進行的呢?
private List<AccessPoint> constructAccessPoints() {
ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
/** Lookup table to more quickly update AccessPoints by only considering objects with the
* correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */
//key為ssid,value為接入點ScanResult
Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
if (getResources().getBoolean(R.bool.set_wifi_priority)) {
emptyCategory();
}
//getConfiguredNetwors可以返回一個配置列表,獲取到配置好的網路連線,該列表存放了關於已經連線過的接入點WiFi的資訊,
//返回的列表中包括如下欄位,當WiFi 關閉時會返回null
<pre name="code" class="java"> /** <ul>
* <li>networkId</li>
* <li>SSID</li>
* <li>BSSID</li>
* <li>priority</li>
* <li>allowedProtocols</li>
* <li>allowedKeyManagement</li>
* <li>allowedAuthAlgorithms</li>
* <li>allowedPairwiseCiphers</li>
* <li>allowedGroupCiphers</li>
* </ul>
*/
final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
if (configs != null) {
for (WifiConfiguration config : configs) {
if (config.SSID != null) {
AccessPoint accessPoint = new AccessPoint(getActivity(), config);
accessPoint.update(mLastInfo, mLastState);
accessPoints.add(accessPoint);
apMap.put(accessPoint.ssid, accessPoint);
if (getResources().getBoolean(R.bool.set_wifi_priority)) {
SetAPCategory(accessPoint, mConfigedAP);
}
}
}
if (getResources().getBoolean(R.bool.set_wifi_priority)) {
if (mConfigedAP != null && mConfigedAP.getPreferenceCount() == 0) {
getPreferenceScreen().removePreference(mConfigedAP);
}
}
}
//獲取到WiFi掃描結果,返回附近可用WiFi,包括已經連線的或者已經儲存的WiFi
final List<ScanResult> results = mWifiManager.getScanResults();
if (results != null) {
for (ScanResult result : results) {
// Ignore hidden and ad-hoc networks.
if (result.SSID == null || result.SSID.length() == 0 ||
result.capabilities.contains("[IBSS]")) {
continue;
}
boolean found = false;
for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
if (accessPoint.update(result))
found = true;
}
if (!found) {
AccessPoint accessPoint = new AccessPoint(getActivity(), result);
accessPoints.add(accessPoint);
apMap.put(accessPoint.ssid, accessPoint);
if (getResources().getBoolean(R.bool.set_wifi_priority)) {
SetAPCategory(accessPoint, mUnKnownAP);
}
}
}
if (getResources().getBoolean(R.bool.set_wifi_priority)) {
if(mUnKnownAP !=null && mUnKnownAP.getPreferenceCount() == 0){
getPreferenceScreen().removePreference(mUnKnownAP);
}
}
}
// Pre-sort accessPoints to speed preference insertion
Collections.sort(accessPoints);
return accessPoints;
}
呼叫mWifiManager.getConfigureNetworks()方法獲取到的是已經配置過連線過的WiFi列表,列表包含下列值
對於掃描到的WiFi的保護方式通過判斷scanresult的capabilities欄位是否包含對應的string來判斷屬於何種保護方式
boolean wpa = result.capabilities.contains("WPA-PSK");
boolean wpa2 = result.capabilities.contains("WPA2-PSK")
iii>
/**
* The network IDs of the configured networks could have changed.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String NETWORK_IDS_CHANGED_ACTION = "android.net.wifi.NETWORK_IDS_CHANGED";
NETWORK_IDS_CHANGED:所配置的網路的網路ID可能已經改變
iv>
/**
* Broadcast intent action indicating that the state of establishing a connection to
* an access point has changed.One extra provides the new
* {@link SupplicantState}. Note that the supplicant state is Wi-Fi specific, and
* is not generally the most useful thing to look at if you are just interested in
* the overall state of connectivity.
* @see #EXTRA_NEW_STATE
* @see #EXTRA_SUPPLICANT_ERROR
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String SUPPLICANT_STATE_CHANGED_ACTION =
"android.net.wifi.supplicant.STATE_CHANGE";
SUPPLICANT_STATE_CHANGED_ACTION:正在建立的連線狀態已經改變,該廣播會攜帶兩個值
v>
/**
* Broadcast intent action indicating that the configured networks changed.
* This can be as a result of adding/updating/deleting a network. If
* {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is set to true the new configuration
* can be retreived with the {@link #EXTRA_WIFI_CONFIGURATION} extra. If multiple
* Wi-Fi configurations changed, {@link #EXTRA_WIFI_CONFIGURATION} will not be present.
* @hide
*/
public static final String CONFIGURED_NETWORKS_CHANGED_ACTION =
"android.net.wifi.CONFIGURED_NETWORKS_CHANGE";
CONFIGURED_NETWORKS_CHANGED_ACTION:當WiFi列表中的網路新增、更新或者刪除時系統會發送該廣播,但是該廣播對使用者隱藏,無法呼叫
vi>
/**
* Broadcast intent action indicating that the state of Wi-Fi connectivity
* has changed. One extra provides the new state
* in the form of a {@link android.net.NetworkInfo} object. If the new
* state is CONNECTED, additional extras may provide the BSSID and WifiInfo of
* the access point.
* as a {@code String}.
* @see #EXTRA_NETWORK_INFO
* @see #EXTRA_BSSID
* @see #EXTRA_WIFI_INFO
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String NETWORK_STATE_CHANGED_ACTION = "android.net.wifi.STATE_CHANGE";
NETWORK_STATE_CHANGED_ACTION:WiFi連線發生改變時系統會發送該廣播,通過欄位EXTRA_NETWORK_INFO可以獲取到WiFi連線的狀態,如果是已連線的狀態,則會有額外的兩個欄位,欄位EXTRA_BSSID可以獲取到所連線的WiFi的bssid,欄位EXTRA_WIFI_INFO可以獲取到所連線的WiFi的資訊獲取到wifiinfo例項
vii>
/**
* The RSSI (signal strength) has changed.
* @see #EXTRA_NEW_RSSI
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String RSSI_CHANGED_ACTION = "android.net.wifi.RSSI_CHANGED";
WIFI_RSSI_CHANGED:當WiFi訊號強度發生改變時系統會發送該廣播,通過欄位EXTRA_NEW_RSSI可以獲取到改變後的wifi訊號強度,當然也需要去更新WiFi列表