編輯:關於Android編程
Android Wifi work station Framework and Architecture
with wpa_supplicant 0.8.X, BCM4329.
轉載請注明出處。
Settings/Wifi UI part structure
WifiSettings是主對話框
167
168 @Override
169 public void onActivityCreated(Bundle savedInstanceState) {
170 // We don't call super.onActivityCreated() here, since it assumes we already set up
171 // Preference (probably in onCreate()), while WifiSettings exceptionally set it up in
172 // this method.
173
174 mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
175 mWifiManager.asyncConnect(getActivity(), new WifiServiceHandler());
176 if (savedInstanceState != null
177 && savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
178 mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
179 mAccessPointSavedState = savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
180 }
1101 public void asyncConnect(Context srcContext, Handler srcHandler) {
1102 mAsyncChannel.connect(srcContext, srcHandler, getMessenger());
1103 }
establish a half connect between WifiManager and WifiService.
1117 public void connectNetwork(WifiConfiguration config) {
1118 if (config == null) {
1119 return;
1120 }
1121 mAsyncChannel.sendMessage(CMD_CONNECT_NETWORK, config);
1122 }
Activity創建的時候會建立和WifiService的AsyncChannel的連接,WifiService同樣會建立一個AsyncChannelHandler來處理來自這個Channel的命令消息。AsyncChannel的所謂異步主要用來傳遞比較耗時的操作並把結果返回給原請求者。AsyncChannel兩端分別有一個MessageHandler,srcHandler請求destHandler,destHandler把結果發回給srcHandler,主要是用於處理比較耗時的操作且在WifiManager中處理返回結果。
嚴格來說,在WifiService的Interface方法之外再做出一個通道提供額外的方法並不是一個什麼好的設計。命令是可以通過同步接口發送給WifiService的,但是WifiService的處理結果如果通過broadcast或intent給WifiManager,則又解耦的過度;如果WifiManager實現一個binder做event sink,又有點小題大做,所以這兒引入這麼個AsyncChannel實在是不得以而為之。
WifiSettings界面使能Wifi時會使用定時器請求掃描,獲取掃描結果,列出AP列表。Scanner使用定時器,周期性向WifiService請求主動掃描,定時原理是發出本次掃描請求後延遲1s發送下次請求。相關代碼如下:
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.startScanActive()) {
mRetry = 0;
} else if (++mRetry >= 3) {
mRetry = 0;
Toast.makeText(getActivity(), R.string.wifi_fail_to_scan,
Toast.LENGTH_LONG).show();
return;
}
sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
}
}
列出AP列表的代碼如下:
In WifiSettings.java
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);
}
}
}
private void updateAccessPoints() {
final int wifiState = mWifiManager.getWifiState();
switch (wifiState) {
case WifiManager.WIFI_STATE_ENABLED:
// AccessPoints are automatically sorted with TreeSet.
final Collection<AccessPoint> accessPoints = constructAccessPoints();
getPreferenceScreen().removeAll();
if (mInXlSetupWizard) {
((WifiSettingsForSetupWizardXL)getActivity()).onAccessPointsUpdated(
getPreferenceScreen(), accessPoints);
} else {
for (AccessPoint accessPoint : accessPoints) {
// When WAPI is not customized to be on all
// WAPI APs will be invisible
if (accessPoint.isVisible()) {
getPreferenceScreen().addPreference(accessPoint);
}
}
}
break;
case WifiManager.WIFI_STATE_ENABLING:
getPreferenceScreen().removeAll();
break;
case WifiManager.WIFI_STATE_DISABLING:
addMessagePreference(R.string.wifi_stopping);
break;
case WifiManager.WIFI_STATE_DISABLED:
addMessagePreference(R.string.wifi_empty_list_wifi_off);
break;
}
}
可以看出對ADHOC的AP,隱藏AP(無SSID的)做了過濾處理。
當在AP列表上,長按某個AP時彈出三菜單Menu - ContextMenu。
這個Menu主要是連接該AP,修改該AP,忘記該AP,其處理代碼如下:
@Override
public boolean onContextItemSelected(MenuItem item) {
if (mSelectedAccessPoint == null) {
return super.onContextItemSelected(item);
}
switch (item.getItemId()) {
case MENU_ID_CONNECT: {
if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
if (!requireKeyStore(mSelectedAccessPoint.getConfig())) {
mWifiManager.connectNetwork(mSelectedAccessPoint.networkId);
}
} else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) {
/** Bypass dialog for unsecured networks */
mSelectedAccessPoint.generateOpenNetworkConfig();
mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig());
} else {
showConfigUi(mSelectedAccessPoint, true);
}
return true;
}
case MENU_ID_FORGET: {
mWifiManager.forgetNetwork(mSelectedAccessPoint.networkId);
return true;
}
case MENU_ID_MODIFY: {
showConfigUi(mSelectedAccessPoint, true);
return true;
}
}
return super.onContextItemSelected(item);
}
可以看出如果該AP已經經過配置,那麼直接連接,如果沒有經過配置且該AP沒有密碼,那麼直接連接,否則則彈出配置對話框先進行配置(選擇加密類型和輸入密碼)。
當點擊AP列表中的某個AP時,直接根據該AP的情況進行處理,代碼如下:
387 @Override
388 public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
389 if (preference instanceof AccessPoint) {
390 mSelectedAccessPoint = (AccessPoint) preference;
391 /** Bypass dialog for unsecured, unsaved networks */
392 if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
393 mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
394 mSelectedAccessPoint.generateOpenNetworkConfig();
395 mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig());
396 } else {
397 showConfigUi(mSelectedAccessPoint, false);
398 }
399 } else {
400 return super.onPreferenceTreeClick(screen, preference);
401 }
402 return true;
403 }
可以看出和長按菜單的處理邏輯相似。
當使用Setttings主界面或Settings/Wifi界面的Switcher開關Wifi時,處理代碼會調用到mWifiEnabler的setSwitch方法,然後會通知其listener,涉及方法如下:
88 public void setSwitch(Switch switch_) {
89 if (mSwitch == switch_) return;
90 mSwitch.setOnCheckedChangeListener(null);
91 mSwitch = switch_;
92 mSwitch.setOnCheckedChangeListener(this);
93
94 final int wifiState = mWifiManager.getWifiState();
95 boolean isEnabled = wifiState == WifiManager.WIFI_STATE_ENABLED;
96 boolean isDisabled = wifiState == WifiManager.WIFI_STATE_DISABLED;
97 mSwitch.setChecked(isEnabled);
98 mSwitch.setEnabled(isEnabled || isDisabled);
99 }
100
101 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
102 //Do nothing if called as a result of a state machine event
103 if (mStateMachineEvent) {
104 return;
105 }
106 // Show toast message if Wi-Fi is not allowed in airplane mode
107 if (isChecked && !WirelessSettings.isRadioAllowed(mContext, Settings.System.RADIO_WIFI)) {
108 Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
109 // Reset switch to off. No infinite check/listenenr loop.
110 buttonView.setChecked(false);
111 }
112
113 // Disable tethering if enabling Wifi
114 int wifiApState = mWifiManager.getWifiApState();
115 if (isChecked && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
116 (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
117 mWifiManager.setWifiApEnabled(null, false);
118 }
119
120 if (mWifiManager.setWifiEnabled(isChecked)) {
121 // Intent has been taken into account, disable until new state is active
122 mSwitch.setEnabled(false);
123 } else {
124 // Error
125 Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();
126 }
127 }
可以看出當不是AP時,是使能Wifi,默認行為是根據配置挑選出AP進行連接。
另外的兩個類的解釋:
WifiConfigController是MVC中的Controller
WifiDialog是單擊的config對話框
連接和DHCP過程中顯示的字符串xml文件如下:
In /packages/apps/Settings/res/values/arrays.xml
223 <!-- Wi-Fi settings -->
224
225 <!-- Match this with the order of NetworkInfo.DetailedState. --> <skip />
226 <!-- Wi-Fi settings. The status messages when the network is unknown. -->
227 <string-array name="wifi_status">
228 <!-- Status message of Wi-Fi when it is idle. -->
229 <item></item>
230 <!-- Status message of Wi-Fi when it is scanning. -->
231 <item>Scanning\u2026</item>
232 <!-- Status message of Wi-Fi when it is connecting. -->
233 <item>Connecting\u2026</item>
234 <!-- Status message of Wi-Fi when it is authenticating. -->
235 <item>Authenticating\u2026</item>
236 <!-- Status message of Wi-Fi when it is obtaining IP address. -->
237 <item>Obtaining IP address\u2026</item>
238 <!-- Status message of Wi-Fi when it is connected. -->
239 <item>Connected</item>
240 <!-- Status message of Wi-Fi when it is suspended. -->
241 <item>Suspended</item>
242 <!-- Status message of Wi-Fi when it is disconnecting. -->
243 <item>Disconnecting\u2026</item>
244 <!-- Status message of Wi-Fi when it is disconnected. -->
245 <item>Disconnected</item>
246 <!-- Status message of Wi-Fi when it is a failure. -->
247 <item>Unsuccessful</item>
248 </string-array>
至此UI界面響應部分介紹完畢,下面看WifiManager.
WifiManager是WifiService的客戶端接口的封裝類,WifiService是服務實現,接口是IWifiManager。
In IWifiManager.aidl
32interface IWifiManager {};
In WifiService.java
public class WifiService extends IWifiManager.Stub {};
In WifiManager.java, NOT the intermmediate file of IWifiManager.aidl
485 public WifiManager(IWifiManager service, Handler handler) {
486 mService = service;
487 mHandler = handler;
488 }
In ContextImpl.java每個Activity關聯的Contect會得到WIFI_SERVICE,然後構造WifiManager封裝類。代碼如下:
449 registerService(WIFI_SERVICE, new ServiceFetcher() {
450 public Object createService(ContextImpl ctx) {
451 IBinder b = ServiceManager.getService(WIFI_SERVICE);
452 IWifiManager service = IWifiManager.Stub.asInterface(b);
453 returnnew WifiManager(service, ctx.mMainThread.getHandler());
454 }});
幾個同步操作的控制流示例:
WifiSettings=>WifiManager::reconnect(…)()||||
=>WifiService:: reconnect ()=>WifiStateMachine :: reconnectCommand()
Scanner=>WifiManager::startScanActive()|||| =>WifiService::startScan(active)=>WifiStateMachine ::startScan(active)
WifiEnabler=> WifiManager::setWifiEnabled() |||| =>WifiService::setWifiEnabled()=>WifiStateMachine ::setWifiEnabled()
WifiManager中大部分同步命令都是這麼個流程;耗時的異步命令則是通過AsyncChannel發送給WifiService.
WifiService Part structure
In WifiService.java
public class WifiService extends IWifiManager.Stub {};
WifiService線程的啟動如下
In SystemServer.java
384 try {
385 Slog.i(TAG, "Wi-Fi P2pService");
386 wifiP2p = new WifiP2pService(context);
387 ServiceManager.addService(Context.WIFI_P2P_SERVICE, wifiP2p);
388 } catch (Throwable e) {
389 reportWtf("starting Wi-Fi P2pService", e);
390 }
391
392 try {
393 Slog.i(TAG, "Wi-Fi Service");
394 wifi = new WifiService(context);
395 ServiceManager.addService(Context.WIFI_SERVICE, wifi);
396 } catch (Throwable e) {
397 reportWtf("starting Wi-Fi Service", e);
398 }
WifiService的構造函數本質是啟動了一個帶有消息隊列的線程做WifiService,兩個Handler attach到該消息隊列上。
428 HandlerThread wifiThread = new HandlerThread("WifiService");
429 wifiThread.start();
430 mAsyncServiceHandler = new AsyncServiceHandler(wifiThread.getLooper());
431 mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
AsyncServiceHandler做為destHandler用於處理下行的來自WifiManager客戶端的命令消息,而WifiStateMachineHandler做為srcHandler用於向WifiStateMachine.mSmhandler發送異步命令。現在的WifiStatemachine實現是和WifiService使用同一個looper,在同一個線程中,所以mAsyncServiceHandler、mWifiStateMachineHandler、WifiStateMachine.mSmhandler是使用同一個looper,運行在wifiThread中。WifiStateMachine籍由StateMachine具備有單獨looper在單獨線程運行的能力。
WifiSerivce作為一個service,WifiService.java在frameworks/base/services/java/com/android/server/目錄下。但其實現使用的WifiStateMachine在frameworks/base/wifi/java/android/net/wifi/目錄下。
WifiStateMachine狀態機使用WifiNative wpa_ctrl和wpa_supplicant通訊, WifiMonitor監聽wpa_supplicant的異步消息。因為WifiStateMachine的核心是狀態機,所以其接口都是轉換成命令投入狀態機消息隊列。WifiService傳來的命令和WifiMonitor監聽到的響應匯入WifiStateMachine的消息隊列,驅動WifiStateMachine轉動起來。
WifiStateMachine繼承自StateMachine,是一個hierarchical state machine which processes messages and can have states arranged hierarchically,其細節參見StateMachine.java. WifiStateMachine的hierarchy如下圖所示:
下面考察初始狀態到Wifi使能掃描AP連接AP查詢AP過程的狀態機運轉。
In WifiStateMachine.java
狀態機會啟動,進入InitialState狀態。然後UI打開WifiEnabler最終WifiService:: setWifiEnabled會被執行到。相關代碼如下:
553 public WifiStateMachine(Context context, String wlanInterface) {
649 if (DBG) setDbg(true);
650
651 //start the state machine
652 start();
653 }
680 public void setWifiEnabled(boolean enable) {
681 mLastEnableUid.set(Binder.getCallingUid());
682 if (enable) {
683 /* Argument is the state that is entered prior to load */
684 sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_STATE_ENABLING, 0));
685 sendMessage(CMD_START_SUPPLICANT);
686 } else {
687 sendMessage(CMD_STOP_SUPPLICANT);
688 /* Argument is the state that is entered upon success */
689 sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_DISABLED, 0));
690 }
691 }
在InitialState.enter()中,初始情況下WifiDriver是沒有加載的,所以進入DriverUnloadedState狀態;
然後setWifiEnabled向狀態機發出CMD_LOAD_DRIVER with WIFI_STATE_ENABLING和CMD_START_SUPPLICANT兩個命令。
在DriverUnloadedState狀態下,處理CMD_LOAD_DRIVER命令,向WifiP2pService發送WIFI_ENABLE_PENDING命令,也就是說通知WifiP2pService需要退出要啟用Wifi了,因為WifiService和WifiP2pService同一時刻只能使用一個,然後轉到WaitForP2pDisableState狀態等待WifiP2pService的響應。
在WaitForP2pDisableState狀態下收到WifiP2pService的響應WIFI_ENABLE_PROCEED,轉到DriverLoadingState狀態加載Wifi driver;對於其它請求會deferMessage到以後狀態處理。
在DriverLoadingState狀態啟用線程異步加載Wifi driver,當加載成功時,向狀態機自身發送消息CMD_LOAD_DRIVER_SUCCESS驅動狀態機轉換到DriverLoadedState,加載失敗則發送消息CMD_LOAD_DRIVER_FAILED驅動狀態機轉換到DriverFailedState狀態(會經由DriverUnloadedState)。
在DriverLoadedState狀態下處理setWifiEnabled發出的defer到此的第二個命令CMD_START_SUPPLICANT,加載網卡芯片固件,啟動wpa_supplicant,啟動WifiMonitor接收wpa_supplicant的消息,轉換狀態到SupplicantStartingState等待WifiMonitor的事件(wpa_supplicant接入成功事件)。
在SupplicantStartingState狀態下收到WifiMonitor. SUP_CONNECTION_EVENT表接入wpa_supplicant成功,regulate國家代號,初始化一些表征wpa_s的字段,轉換狀態至DriverStartedState;對於其它命令,defer處理。
經SupplicantStartedState.enter()和DriverStartedState.enter()中使用CMD_SET_COUNTRY_CODE和CMD_SET_FREQUENCY_BAND設置國家代號和頻段,在本狀態處理這兩個命令;這兩個命令是要發送給芯片的,所以在固件和驅動加載好後發送。設置好頻段後,就向自身發送消息CMD_START_SCAN啟動一次active scan。然後經由狀態ConnectModeState轉換狀態至DisconnectedState。
在DisconnectedState下收到掃描結果WifiMonitor.SCAN_RESULTS_EVENT,消息路由給父狀態SupplicantStartedState的processMessage進行處理,獲取掃描結果並保存。在此狀態下SupplicantStartedState處理與AP配置相關的命令,ConnectModeState處理與AP連接相關的命令。例如可以配置某個AP,然後請求connectNetwork,對於CMD_CONNECT_NETWORK,會使用Connect相關狀態ConnectModeState.processMessage()處理,啟動AP關聯,然後轉換狀態到DisconnectingState與舊AP斷開連接與新AP建立連接。
在DisconnectingState狀態收到關聯到新AP成功消息WifiMonitor.NETWORK_CONNECTION_EVENT後,使用ConnectModeState.processMessage處理,記錄關聯上的SSID,BSSID,獲取AP信號強度和速度,廣播消息,轉換狀態至ConnectingState進行IP獲取。
在ConnectingState.enter()中使用靜態IP或DHCP獲取IP,配置IP,成功則轉入ConnectedState狀態。
在ConnectedState狀態下,可以處理配置AP、連接新的AP、斷開連接等,還有掃描請求,另外還要查詢當前連接AP的信號強度和網絡流量信息,在狀態欄顯示Wifi狀態圖標。這時通過CMD_RSSI_POLL命令實現的,當WifiService構造時,允許enableRssiPolling,一次CMD_ENABLE_RSSI_POLL會引發定時的CMD_RSSI_POLL,當屏幕未關閉時,CMD_RSSI_POLL就會定時向自身狀態機發送;然後ConnectedState.processMessage調用fetchRssiAndLinkSpeedNative()處理CMD_RSSI_POLL。
MainActivity.java代碼:package siso.refreshablev;import android.app.Activity;import and
依照郭霖老師的《第一行代碼Android》,今天我要來學習Activity,首先來初步了解Activity,基本上就是照葫蘆畫瓢的模式,有點回到當初敲java的hello
1. View 樹的繪圖流程當 Activity 接收到焦點的時候,它會被請求繪制布局,該請求由Android framework 處理.繪制是從根節點開始,
Viewpager實現帶下面帶圓點的過渡頁,先來效果圖實現這個功能需要幾個步驟,如下 1、布局用的是FrameLayout,需要了解的可以在網上百度。簡單來說就是,他會前