Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android -- Wifi熱點的打開與關閉流程簡介

Android -- Wifi熱點的打開與關閉流程簡介

編輯:關於Android編程

在Android手機中,熱點也是一個較為常用的功能。對於framework開發者來說,要開發、維護SoftAp,了解framework中熱點開關的具體流程是非常有必要的。下面就對這部分內容做一些介紹,以供後續查閱。

一、SoftAp打開流程

當我們在設置中打開熱點時,會調用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的方法調用與打開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. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved