編輯:關於Android編程
在Android手機中,熱點也是一個較為常用的功能。對於framework開發者來說,要開發、維護SoftAp,了解framework中熱點開關的具體流程是非常有必要的。下面就對這部分內容做一些介紹,以供後續查閱。
當我們在設置中打開熱點時,會調用WifiManager::setWifiApEnabled(),參數enabled為true;間接調用同名的WifiServiceImpl::setWifiApEnabled():
/** * Start AccessPoint mode with the specified * configuration. If the radio is already running in * AP mode, update the new configuration * Note that starting in access point mode disables station * mode operation * @param wifiConfig SSID, security and channel details as * part of WifiConfiguration * @return {@code true} if the operation succeeds, {@code false} otherwise * * @hide Dont open up yet */ public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { try { mService.setWifiApEnabled(wifiConfig, enabled); return true; } catch (RemoteException e) { return false; } }
/** * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)} * @param wifiConfig SSID, security and channel details as * part of WifiConfiguration * @param enabled true to enable and false to disable */ public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { enforceChangePermission(); ConnectivityManager.enforceTetherChangePermission(mContext); if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) { throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user."); } // null wifiConfig is a meaningful input for CMD_SET_AP if (wifiConfig == null || isValid(wifiConfig)) { mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget(); } else { Slog.e(TAG, "Invalid WifiConfiguration"); } }參數中的wifiConfig對象保存了在Settings中操作時保留的熱點信息,如熱點名稱、密鑰和加密方式等等。與Wifi本身的打開和關閉類似,Wifi熱點的打開流程也是通過WifiController狀態機向WifiStateMachine轉發消息的。與前面介紹的Wifi打開流程類似,CMD_SET_AP消息在ApStaDisabledState狀態處理:
case CMD_SET_AP: if (msg.arg1 == 1) { mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj, true); transitionTo(mApEnabledState);//此時WifiController的狀態停留在ApEnabledState }由此轉入WifiStateMachine進行打開流程:
/** * TODO: doc */ public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) { if (enable) { sendMessage(CMD_START_AP, wifiConfig); } else { sendMessage(CMD_STOP_AP); } }WifiStateMachine::InitialState會處理該消息:
case CMD_START_AP: if (mWifiNative.loadDriver() == false) { loge("Failed to load driver for softap"); } else { if (enableSoftAp() == true) { setWifiApState(WIFI_AP_STATE_ENABLING, 0);//該函數中會發送廣播,告知外界當前熱點的啟動階段 transitionTo(mSoftApStartingState);//切換狀態 } else { setWifiApState(WIFI_AP_STATE_FAILED, WifiManager.SAP_START_FAILURE_GENERAL); transitionTo(mInitialState); } } break;首先肯定是先加載驅動,驅動加載成功後通過enableSoftAp()配置Wifi熱點:
/* SoftAP configuration */ private boolean enableSoftAp() { if (WifiNative.getInterfaces() != 0) { if (!mWifiNative.toggleInterface(0)) { if (DBG) Log.e(TAG, "toggleInterface failed"); return false; } } else { if (DBG) Log.d(TAG, "No interfaces to toggle"); } try { mNwService.wifiFirmwareReload(mInterfaceName, "AP");//加載固件 if (DBG) Log.d(TAG, "Firmware reloaded in AP mode"); } catch (Exception e) { Log.e(TAG, "Failed to reload AP firmware " + e); } if (WifiNative.startHal() == false) {//啟動HAL層 /* starting HAL is optional */ Log.e(TAG, "Failed to start HAL"); } return true; }Wifi熱點首先需要綁定端口信息,再以AP模式通過NetworkManagementService在wlan0端口下加載固件;同時熱點功能也需要HAL層的支持。
setWifiApState()會發送廣播,告知當前熱點打開的過程信息;同理,也有setWifiState(),告知外界當前Wifi打開的過程信息;如果我們有必要知道當前熱點打開的過程進行到什麼階段了,可以監聽WifiManager.WIFI_AP_STATE_CHANGED_ACTION廣播。最後狀態切換到SoftApStartingState,如果流程有誤,則會重新進入InitialState。
接著看SoftApStartingState::enter():
public void enter() { final Message message = getCurrentMessage(); if (message.what == CMD_START_AP) { final WifiConfiguration config = (WifiConfiguration) message.obj; if (config == null) { mWifiApConfigChannel.sendMessage(CMD_REQUEST_AP_CONFIG);//1 - 獲取先前或者默認的配置信息 } else { mWifiApConfigChannel.sendMessage(CMD_SET_AP_CONFIG, config);//2 - 將上層傳入的配置信息寫到本地文件 startSoftApWithConfig(config);//開啟熱點 } } else { throw new RuntimeException("Illegal transition to SoftApStartingState: " + message); } }首先會判斷打開熱點時傳入的WifiConfiguration對象是否為null;如果為空,則會向WifiApConfigStore發送CMD_REQUEST_AP_CONFIG消息,請求一個熱點配置信息
。我們一起介紹這兩個分支過程。回過頭看InitialState狀態的enter():
public void enter() { WifiNative.stopHal(); mWifiNative.unloadDriver(); if (mWifiP2pChannel == null) { mWifiP2pChannel = new AsyncChannel(); mWifiP2pChannel.connect(mContext, getHandler(), mWifiP2pServiceImpl.getP2pStateMachineMessenger()); } if (mWifiApConfigChannel == null) { mWifiApConfigChannel = new AsyncChannel(); mWifiApConfigStore = WifiApConfigStore.makeWifiApConfigStore( mContext, getHandler());//WifiApConfigStore也是一個小的狀態機,此時會構建mWifiApConfigStore對戲,並啟動狀態機 mWifiApConfigStore.loadApConfiguration();//在WifiApConfigStore中加載默認的熱點配置信息 mWifiApConfigChannel.connectSync(mContext, getHandler(), mWifiApConfigStore.getMessenger());//創建AsyncChannel對象,以供向WifiApConfigStore發送消息 } if (mWifiConfigStore.enableHalBasedPno.get()) { // make sure developer Settings are in sync with the config option mHalBasedPnoEnableInDevSettings = true; } }在創建完mWifiApConfigStore對象後,會調用mWifiApConfigStore.loadApConfiguration()加載熱點配置信息:
void loadApConfiguration() { DataInputStream in = null; try { WifiConfiguration config = new WifiConfiguration(); in = new DataInputStream(new BufferedInputStream(new FileInputStream( AP_CONFIG_FILE))); int version = in.readInt(); if ((version != 1) && (version != 2)) { Log.e(TAG, "Bad version on hotspot configuration file, set defaults"); setDefaultApConfiguration(); return; } config.SSID = in.readUTF(); if (version >= 2) { config.apBand = in.readInt(); config.apChannel = in.readInt(); } int authType = in.readInt(); config.allowedKeyManagement.set(authType); if (authType != KeyMgmt.NONE) { config.preSharedKey = in.readUTF(); } mWifiApConfig = config; } catch (IOException ignore) { setDefaultApConfiguration(); } finally { if (in != null) { try { in.close(); } catch (IOException e) {} } } }主要是從/misc/wifi/softap.conf文件中讀取其中的信息,並賦給WifiApConfigStore的成員變量mWifiApConfig,這個變量保存的就是當前SoftAp的配置信息。該文件一開始會有默認的信息保存其中,如果我們從沒配置過熱點,拿到的就是系統默認的信息;如果,上層配置了熱點;我們也會將新的配置信息更新到softap.conf中,以供下載再次加載。再看消息處理過程:
case WifiStateMachine.CMD_REQUEST_AP_CONFIG: mReplyChannel.replyToMessage(message, WifiStateMachine.CMD_RESPONSE_AP_CONFIG, mWifiApConfig);向WifiStateMachine回復CMD_RESPONSE_AP_CONFIG消息,並附帶mWifiApConfig對象。在SoftApStartingState::enter()中,如果config不為空,我們直接去調用startSoftApWithConfig()啟動SoftAP;如果一開始config為null,通過處理CMD_RESPONSE_AP_CONFIG,獲取到新的config對象,也應該去開啟SoftAP了:
case WifiStateMachine.CMD_RESPONSE_AP_CONFIG: WifiConfiguration config = (WifiConfiguration) message.obj; if (config != null) { startSoftApWithConfig(config); } else { loge("Softap config is null!");//config依然為null,則熱點打開失敗 sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL);//SoftApStartingState處理,狀態重新切換到InitialState } break;
如果一開始的config對象不為空,從代碼可知我們會先發送CMD_SET_AP_CONFIG消息,通知WifiApConfigStore更新配置信息,看處理流程:
class InactiveState extends State { public boolean processMessage(Message message) { switch (message.what) { case WifiStateMachine.CMD_SET_AP_CONFIG: WifiConfiguration config = (WifiConfiguration)message.obj; if (config.SSID != null) { mWifiApConfig = config;//將上層傳入的配置信息先保存到成員變量中 transitionTo(mActiveState);//切換狀態 } else { Log.e(TAG, "Try to setup AP config without SSID: " + message); }首先將傳入的配置對象保存到mWifiApConfig,接著切換狀態:
class ActiveState extends State { public void enter() { new Thread(new Runnable() { public void run() { writeApConfiguration(mWifiApConfig);//更新配置信息到本地 sendMessage(WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED);//發送更新完成消息 } }).start(); } public boolean processMessage(Message message) { switch (message.what) { //TODO: have feedback to the user when we do this //to indicate the write is currently in progress case WifiStateMachine.CMD_SET_AP_CONFIG: deferMessage(message); break; case WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED: transitionTo(mInactiveState); break; default: return NOT_HANDLED; } return HANDLED; } }enter()函數中,會調用writeApConfiguration()將mWifiApConfig的信息更新到/misc/wifi/softap.conf文件中,供下次加載使用:
private void writeApConfiguration(final WifiConfiguration config) { DataOutputStream out = null; try { out = new DataOutputStream(new BufferedOutputStream( new FileOutputStream(AP_CONFIG_FILE))); out.writeInt(AP_CONFIG_FILE_VERSION); out.writeUTF(config.SSID); out.writeInt(config.apBand); out.writeInt(config.apChannel); int authType = config.getAuthType(); out.writeInt(authType); if(authType != KeyMgmt.NONE) { out.writeUTF(config.preSharedKey); } } catch (IOException e) { Log.e(TAG, "Error writing hotspot configuration" + e); } finally { if (out != null) { try { out.close(); } catch (IOException e) {} } } }處理比較簡單,接著給自己發送CMD_SET_AP_CONFIG_COMPLETED消息,告知配置信息更新已經完畢,並重新進入InactiveState,重新等待下次配置信息的更新處理。
我們再返回到WifiStateMachine::SoftApStartingState處理CMD_RESPONSE_AP_CONFIG,如果再次獲取後的config依然為null,則通知熱點打開失敗。接著就是真正開啟熱點的函數處理:
/* Current design is to not set the config on a running hostapd but instead * stop and start tethering when user changes config on a running access point * * TODO: Add control channel setup through hostapd that allows changing config * on a running daemon */ private void startSoftApWithConfig(final WifiConfiguration configuration) { // set channel final WifiConfiguration config = new WifiConfiguration(configuration); if (DBG) { Log.d(TAG, "SoftAp config channel is: " + config.apChannel); } //We need HAL support to set country code and get available channel list, if HAL is //not available, like razor, we regress to original implementaion (2GHz, channel 6) if (mWifiNative.isHalStarted()) {//因為SoftAp需要HAL層的支持,所有首先要進行確定,再繼續配置 //set country code through HAL Here if (mSetCountryCode != null) { if (!mWifiNative.setCountryCodeHal(mSetCountryCode.toUpperCase(Locale.ROOT))) { if (config.apBand != 0) { Log.e(TAG, "Fail to set country code. Can not setup Softap on 5GHz"); //countrycode is mandatory for 5GHz sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL); return; } } } else { if (config.apBand != 0) { //countrycode is mandatory for 5GHz Log.e(TAG, "Can not setup softAp on 5GHz without country code!"); sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL); return; } } if (config.apChannel == 0) { config.apChannel = chooseApChannel(config.apBand); if (config.apChannel == 0) { if(mWifiNative.isGetChannelsForBandSupported()) { //fail to get available channel sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_NO_CHANNEL); return; } else { //for some old device, wifiHal may not be supportedget valid channels are not //supported config.apBand = 0; config.apChannel = 6; } } } } else { //for some old device, wifiHal may not be supported config.apBand = 0; config.apChannel = 6; } // Start hostapd on a separate thread new Thread(new Runnable() {//開啟一個新線程,來啟動hostapd;我們支持wpa_s是支持Wifi的,hostapd則是支持SoftAP的 public void run() { try { mNwService.startAccessPoint(config, mInterfaceName);//通過NetworkManagerService,在無線端口上,按傳入的配置信息開啟SoftAP; } catch (Exception e) { loge("Exception in softap start " + e); try { mNwService.stopAccessPoint(mInterfaceName); mNwService.startAccessPoint(config, mInterfaceName); } catch (Exception e1) { loge("Exception in softap re-start " + e1); sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL);//打開失敗,狀態會重新切換到InitialState;等待下一次過程 return; } } if (DBG) log("Soft AP start successful"); sendMessage(CMD_START_AP_SUCCESS);//打開成功 } }).start(); }如果最後熱點打開成功,發送CMD_START_AP_SUCCESS,看處理過程,SoftApStartingState:
case CMD_START_AP_SUCCESS: setWifiApState(WIFI_AP_STATE_ENABLED, 0);//發送廣播,告知SoftAp已經成功打開 transitionTo(mSoftApStartedState);//切換狀態 break; case CMD_START_AP_FAILURE: setWifiApState(WIFI_AP_STATE_FAILED, message.arg1);//發送廣播,告知SoftAp未成功打開 transitionTo(mInitialState);//切換到初始狀態最終狀態在SoftApStartedState:
class SoftApStartedState extends State { @Override public boolean processMessage(Message message) { logStateAndMessage(message, getClass().getSimpleName()); switch(message.what) { case CMD_STOP_AP: if (DBG) log("Stopping Soft AP"); /* We have not tethered at this point, so we just shutdown soft Ap */ try { mNwService.stopAccessPoint(mInterfaceName); } catch(Exception e) { loge("Exception in stopAccessPoint()"); } setWifiApState(WIFI_AP_STATE_DISABLED, 0); transitionTo(mInitialState); break; case CMD_START_AP: // Ignore a start on a running access point break; // Fail client mode operation when soft AP is enabled case CMD_START_SUPPLICANT: loge("Cannot start supplicant with a running soft AP"); setWifiState(WIFI_STATE_UNKNOWN); break; case CMD_TETHER_STATE_CHANGE: TetherStateChange stateChange = (TetherStateChange) message.obj; if (startTethering(stateChange.available)) { transitionTo(mTetheringState); } break; default: return NOT_HANDLED; } return HANDLED; }
到這裡,一個完整的SoftAp打開流程就結束了。
關閉SoftAp的方法調用與打開SoftAp一致,不過enabled此時是為false:
public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled)由第一部分的內容可知WifiController狀態機在處理完SoftAp打開後,停在ApEnabledState狀態,那麼我們看它是怎麼處理CMD_SET_AP的:
case CMD_SET_AP: if (msg.arg1 == 0) { mWifiStateMachine.setHostApRunning(null, false);//在WifiStateMachine中開始熱點關閉流程 transitionTo(mApStaDisabledState);//切換到初始狀態 } break;有前述可知,如果參數enabled為false,mag.arg1就應該為0,調用setHostApRunning()走關閉流程,並將WifiController中的狀態重置為ApStaDisabledState,等待下一次流程的開始。看setHostApRunning():
/** * TODO: doc */ public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) { if (enable) { sendMessage(CMD_START_AP, wifiConfig); } else { sendMessage(CMD_STOP_AP); } }發送CMD_STOP_AP消息;已知SoftAp成功打開後,WifiStateMachine停留在SoftApStartedState,看其處理:
case CMD_STOP_AP: if (DBG) log("Stopping Soft AP"); /* We have not tethered at this point, so we just shutdown soft Ap */ try { mNwService.stopAccessPoint(mInterfaceName);//直接關閉SoftAp } catch(Exception e) { loge("Exception in stopAccessPoint()"); } setWifiApState(WIFI_AP_STATE_DISABLED, 0);//發送廣播,告知外界SoftAp的狀態 transitionTo(mInitialState);//切換到初始狀態首先,通過NetworkManagermentService關閉SoftAp,並發送廣播通知SoftAp的狀態改變;最後WifiStateMachine切換到InitialState:
public void enter() { WifiNative.stopHal(); mWifiNative.unloadDriver(); if (mWifiP2pChannel == null) { mWifiP2pChannel = new AsyncChannel(); mWifiP2pChannel.connect(mContext, getHandler(), mWifiP2pServiceImpl.getP2pStateMachineMessenger()); } if (mWifiApConfigChannel == null) { mWifiApConfigChannel = new AsyncChannel(); mWifiApConfigStore = WifiApConfigStore.makeWifiApConfigStore( mContext, getHandler()); mWifiApConfigStore.loadApConfiguration(); mWifiApConfigChannel.connectSync(mContext, getHandler(), mWifiApConfigStore.getMessenger()); } if (mWifiConfigStore.enableHalBasedPno.get()) { // make sure developer Settings are in sync with the config option mHalBasedPnoEnableInDevSettings = true; } }停掉HAL層,卸載驅動;重新等待下一次Wifi/SoftAp的啟動過程。到此,熱點關閉的動作就結束了。
PS:
WifiManager中提供了兩個關於SoftAp的操作函數:
1、設置SoftAP的配置信息
/** * Sets the Wi-Fi AP Configuration. * @return {@code true} if the operation succeeded, {@code false} otherwise * * @hide Dont open yet */ public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) { try { mService.setWifiApConfiguration(wifiConfig); return true; } catch (RemoteException e) { return false; } }設置Wi-Fi AP的配置信息,它真正的處理過程是向WifiApConfigStore發送CMD_SET_AP_CONFIG消息,告知其要更新配置信息了。這一部分處理在第一部分已經分析過。
2、獲取當前SoftAp正在使用的配置信息
/** * Gets the Wi-Fi AP Configuration. * @return AP details in WifiConfiguration * * @hide Dont open yet */ public WifiConfiguration getWifiApConfiguration() { try { return mService.getWifiApConfiguration(); } catch (RemoteException e) { return null; } }它真正的處理過程是向WifiApConfigStore發送CMD_REQUEST_AP_CONFIG消息,請求WifiApConfigStore::mWifiApConfig成員,第一部分也已經說過,該變量保存的就是當前SoftAp正在使用的配置信息。
1、效果展示2、布局文件<?xml version=1.0 encoding=utf-8?><RelativeLayout xmlns:
本文實例講述了Android編程重寫ViewGroup實現卡片布局的方法。分享給大家供大家參考,具體如下:實現效果如圖:實現思路1. 重寫onMeasure(int wi
工具/原料JDKEclipseADTJDK的安裝和Java環境變量的設置1、JDK下載地址 JDK下載地址:http://www.or
先給大家說下項目需求:TextView顯示一段文字,格式為:白雪公主(姓名,字數不確定)向您發來了2(消息個數,不確定)條消息這段文字中名字和數字的長度是不確定的,還要求