WiFiAp探究實錄--功能實現與原始碼分析

分类DHCP發表於2024-08-04

wifi熱點說的是wifiAp相關,所以如果原始碼開發的話,這個WifiAp算是一個搜尋程式碼的關鍵字,含義是Wifi Access point,wifi接入點。所以下文中的wifi熱點統一用WifiAp代替

  1. wifiAp開啟方式:設定->更多->行動網路共享->行動式wlan熱點。
  2. wifiAp開啟條件:任何情況下均可。只是有內網外網之分。造成內外網之分的影響條件有sim卡和wifi的連線狀態。注意,這裡所說的是wifi的連線狀態,而不是wifi熱點的連線狀態
  3. wifiAp開發中用處:可用於區域網內的通訊
  4. wifiAp開發中相關問題:
    • 第一,跟WiFiAp相關的有wifiAp的閘道器Ip,以及ip範圍
    • 第二,wifiAp的config:包括初始建立時的defaultvalue:名字(ssid)和密碼(preSharedKey),以及後續修改config
    • 第三,wifiAp的enable狀態
    • 第四,wifiAp的裝置連線列表:一是保證能獲取到當前連線裝置列表,二是當有裝置連線時能夠實時的更新
    • 第五,wifiAp的連線限制:包括最大連線數限制,以及黑白名單機制

先就wifiAp的ip進行說明:

既然是要區域網內通訊,那就要用到ip地址和埠號了(關於埠號的設定屬於開發通訊時的問題,是使用者自定義的可變的,在我的程式裡我規定埠號為80。而ip地址是有規定的,所以只講關於ip的問題)。ip地址是在Android原始碼中規定好的,平常所買的路由器的ip地址一般都是192.168.0.1。Android原始碼中所規定的手機的wifiAp的ip地址為192.168.43.1,這個程式碼中可以看到

  • 建立wifiAp時的ip:在建立wifiAp時相當於閘道器ip,/frameworks/opt/net/wifi/service/java/com/android/server/wifi/SoftApManager.java中開啟wifiAp時規定了ip地址(Android7.0中在該檔案中,如果是其他Android系統可以在WifiStateMachine),具體方法為startThering中
  • wifiAp的ip地址的分配區間:在/frameworks/base/services/core/java/com/android/server/connectivity/Tethering.java中有規定 //usb網路共享的閘道器是192.168.42.1 // USB is 192.168.42.1 and 255.255.255.0 //wifi行動式熱點閘道器是192.168.43.1 // Wifi is 192.168.43.1 and 255.255.255.0 //藍芽共享(個人區域網)限制5個 // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1 // with 255.255.255.0 private String[] mDhcpRange; private static final int TETHER_RETRY_UPSTREAM_LIMIT = 5; private static final String[] DHCP_DEFAULT_RANGE = { "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254", "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254", "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254", "192.168.48.2", "192.168.48.254", }

——————編輯於2017-08-03———————

WifiAp的config分析:

預設的config:

  • 程式碼位置 在恢復出廠設定後開啟WifiAp,初始的wifiAp的名稱是一定的,但是wifiAp的密碼是隨機,這個可以自行測試,實現程式碼位於一個叫做 WifiApConfigStore.java的檔案中,檔案路徑為/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiApConfigStore.java
  • 程式碼實現
程式碼語言:js
複製

  /**   * 構建一個預設的wifiAp,加密型別是WPA2,密碼隨機   *    * We are changing the Wifi Ap configuration storage from secure settings to a   * flat file accessible only by the system. A WPA2 based default configuration   * will keep the device secure after the update.   */   private WifiConfiguration getDefaultApConfiguration() {         WifiConfiguration config = new WifiConfiguration();         //wifiAp的ssid         config.SSID = mContext.getResources().getString(                 R.string.wifi_tether_configure_ssid_default);           //wifiAp的加密方式         config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);         //隨機生成uuid         String randomUUID = UUID.randomUUID().toString();         //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx         config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9, 13);         return config;     } }
 

修改wifiAp的config配置

如果想要修改wifiAp的config配置需要注意,在修改config時,config會直接設定下去,但是並不會立即生效,必須要重啟wifiAp之後才有效。這個可以先拿自己的手機演示確認。

  • 首先獲取到wifiManager物件
程式碼語言:javascript
複製
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
  • 然後獲取到config物件
程式碼語言:javascript
複製
  WifiConfiguration config = wifiManager.getWifiApConfiguration();
  • 有了config之後,就可以對引數進行設定了,比如設定使用者名稱和密碼
程式碼語言:javascript
複製
if (config != null) {
           config.SSID = mWifiInfoBean.getApSsid();
           config.preSharedKey = mWifiInfoBean.getPskKey();
   }

當然,你還可以做其他設定,具體的可以參考WifiConfiguration.java原始碼 到這一步,對於wifiAp的使用者名稱和密碼已經設定成功了,此時若手動重啟wifiAp後config即可生效。如果你想要立刻生效,那就必須要重啟wifiAp了。

  • 重啟wifiAp,將所設定的config設定進去,並重啟熱點,流程是首先判斷WiFi熱點是否處於開啟狀態,如果是,則重啟wifiAp。如果當前wifiAp不處於開啟狀態,則只需要把config設定下去即可
程式碼語言:javascript
複製
if (wifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) {
   //如果wifiAp處於開啟狀態,則關閉並重啟
  wifiManager.setWifiApEnabled(null, false);
  return wifiManager.setWifiApEnabled(config, true);
} else {
   //如果wifiAp不處於開啟狀態,則只需要將config設定下去
    return wifiManager.setWifiApConfiguration(config);
 }

——————編輯於2017-08-04———————

開啟/關閉WifiAp熱點狀態

在對wifiAp進行config修改時已經涉及到了對於wifiAp的開和關,在進行wifiAp進行開關的過程中需要傳入config,如果傳入的為null,則沿用上一次的 config,如果上一次的config不存在,則會去載入預設的config。當開啟wifiAp時會先去判斷wifi的狀態,如果wifi處於開啟狀態則需要關閉WiFi狀態,然後開啟wifiAp。

  • 獲取WiFimanager物件(參考上文)
  • 判斷目前wififAp的開關狀態,如果處於開啟狀態,則不進行任何操作。當然,如果你想自己設定config,那麼就照著上文中配置config的步驟來 /** * if wifiap is enabled */ if (wifiManager.isWifiApEnabled()) { return true; }
  • 判斷wifi的狀態,如果處於開啟狀態,則關閉wifi狀態 /** * Disable Wifi if enabling tethering */ int wifiState = wifiManager.getWifiState(); if (enable && ((wifiState == WifiManager.WIFI_STATE_ENABLING) || (wifiState == WifiManager.WIFI_STATE_ENABLED))) { wifiManager.setWifiEnabled(false); }
  • 接下來就可以呼叫wifiAp開啟的方法了 wifiManager.setWifiApEnabled(null, enable);

已連線裝置列表

讀取wifiAp的已連線裝置列表

這個很糾結,關於wifiAp的這些東西不存在什麼jni介面,只能是透過讀檔案或者是監聽廣播來和底層通訊。Android原始碼中提供了一個讀取已連線設別列表的方法——讀取特定檔案“/proc/net/arp” 來獲取已連線裝置資訊。 程式碼如下:

程式碼語言:javascript
複製
  File file = new File("/proc/net/arp");
     try {
       reader = new BufferedReader(new FileReader(file));
       String line;
       while ((line = reader.readLine()) != null) {
          String[] tokens = line.split("[ ]+");
          if (tokens.length < 6 || tokens[3].length() < 8) {
               continue;
           }
           //角標為3是mac地址,角標為0是ip地址 ,裝置名是根據mac來獲取
       }
   } catch (IOException e) {
      e.printStackTrace();
   } finally {
      if (reader != null) {
        try {
          reader.close();
       } catch (IOException e) {
          e.printStackTrace();
       }
    }

該檔案包含的資料有sscanf(buf, “%s 0x%x 0x%x %s %s %s\n”, ip, &h_type, &flag, hw_addr, mask, dev ) 也就是說tokens 長度為6,可以看到包含已連線裝置的ip和addr,但是裝置名卻沒有說明,這個需要自己根據mac地址來獲取對應的廠商和裝置名。當然,方案提供商也許自己會整合這部分工作,所以具體情況具體考慮

裝置列表實時更新

這個目前Android原始碼中也沒提供任何解決方案,如果是系統開發的,可以在裝置連線時加個廣播,當有裝置連線成功後傳送廣播,然後上層應用可以透過監聽廣播來實時更新裝置列表。

裝置連線限制

裝置連線限制包括最大連線數,以及黑白名單。我只能說目前上層是沒有直接可以呼叫的介面來實現。目前大致只能透過呼叫adb shell命令來實現了。(如果平臺支援的話)

——————編輯於2017-08-09———————

原始碼探究

文中上半部分介紹了wifiAp相關的功能開發,接下來就從原始碼的角度出發,分析為什麼我們可以用這種方式來實現wifiAp的功能 關於Android的WiFiAp的原始碼研究基於andriod7.1.1

WifiAp始於UI

wifiAp程式碼處於package/apps/Settings中,wifiAp開啟的入口在 /packages/apps/Settings/src/com/android/settings/TetherSettings.java 中的onCreateDialog。在TetherSettings中包括藍芽熱點,WiFi熱點,usb熱點的相關問題。

程式碼語言:javascript
複製
  @Override
    public Dialog onCreateDialog(int id) {
        if (id == DIALOG_AP_SETTINGS) {
            final Activity activity = getActivity();
            //開啟WiFiAp的設定
            mDialog = new WifiApDialog(activity, this, mWifiConfig);
            return mDialog;
        }
        return null;
    }

wifiAp的設定彈出框為WifiAPDialog,目錄為: /packages/apps/Settings/src/com/android/settings/wifi/WifiApDialog.java WiFiAp的設定框所載入的xml佈局檔案為wifi_ap_dialog.xml。wifiAp的設定包括四部分:

  1. wifi_ssid:wifiAp的名稱:輸入長度最大限制為32個字元
  2. wifi_security:wifiAp的安全性:提供spinner列表進行選擇,可選項如下
WiFiAp探究實錄--功能實現與原始碼分析
  1. wifi_password:wifiAp的密碼,最大長度限制為63
  2. wifi_ap_band_config:wifiAp的Ap頻段 頻段有spinner列表可選,頻段可選為2.4g和5g。該array是在WifiApDialog程式碼中新增的
WiFiAp探究實錄--功能實現與原始碼分析

所以,如果想要修改wifiApDialog佈局相關的可以修改wifi_ap_dialog.xml佈局檔案。由佈局檔案也可以看出,Android原始碼上層中,wifiAp相關的配置 WifiConfiguration包括四部分,使用者名稱、密碼 、安全性、頻段。

WifiConfiguration配置

在建立WifiApDialog時會傳入一個WifiConfiguration物件,wifiApDialog中顯示的WiFiAp資訊就是從該config中獲取的。在第一次開啟wifiAp物件時所獲取的config對像是系統預設的配置,當使用者進行了修改之後wifiAp的config會被儲存到手機,等下次獲取到的就是修改後的config。

獲取wifiConfig

先來找到建立dialog的地方來看一下config物件,來看一下程式碼是如何在第一次使用時獲取系統預設以及在修改後如何獲取使用者修改的config的:

這裡寫圖片描述
這裡寫圖片描述

wifiAp的config物件是在TetherSettings的initWifiTethering的方法中獲取的。可以看到,mWifiConfig對像透過wifiManager呼叫getWifiApConfiguration()來獲取,當然,原始碼設計有套路,manager只是client的一箇中轉站,真正的還是找的是service,所以找到WifiServiceImpl.java,緊接著是WifiStateMachine.java,一級一級的都是呼叫,最終的實現在WifiApConfigStore.java檔案中,該檔案包含了系統預設的config以及使用者設定的config.

這裡寫圖片描述
這裡寫圖片描述

從這個程式碼可以看出兩個資訊

  • 第一,wifiAp的config資訊儲存在檔案中
  • 第二,優先載入檔案中儲存的config資訊。其中loadApConfiguration用於從檔案中載入wifiAp的配置資訊,如果所載入的config為null—-即表示使用者未對wifiAp進行過資訊設定,則會去呼叫getDefaultApConfiguration來獲取系統的預設設定,並且將獲取到的config寫入到儲存wifiAp的config的檔案

總結來說就是當wifiManager想要獲取config時,會先載入檔案中所儲存的config資訊,如果config資訊從未進行過儲存,則獲取預設的config,並且將config寫入到檔案中去。 config檔案儲存目錄在wifiApConfigStore中已經宣告瞭,位於data目錄下:

這裡寫圖片描述
這裡寫圖片描述

設定wifiConfig

WifiApDialog彈窗可以修改WiFi的配置資訊,按下確定按鈕即可儲存,接下來看一下對config的儲存設定。 對於dialog的確認按鈕的點選事件是在TetherSettings.java中處理的

這裡寫圖片描述
這裡寫圖片描述

這段程式碼做了以下操作

  • 獲取到dialog中填寫的使用者名稱、密碼、加密方式、頻段這些WiFiap的config: mDialog.getConfig()
  • 如果獲取到的config不為null,則將wifiAp的config儲存起來: mWifiManager.setWifiApConfiguration(mWifiConfig),和get時類似,該方法是一路往下呼叫 WifiManager->WifiServiceImpl->WifiStateMachine->WifiApConfigStore,最終的實現就是在WifiApConfigStore中進行將config寫入到檔案。config是要下一次開啟wifiAp時才會生效,所以此時如果wifiAp處於開啟狀態,則關閉wifiAp。注意,從這裡也可以看到,原始碼的實現是在修改wifiAp的config之後,會將ap關閉,並不會自動重啟。在這裡關閉wifiAp呼叫的是ConnectivityManager的例項方法:mCm.stopTethering(int type),該方法經過ConnectivityManager->ConnectivityService->Tethering.java,最終是在Tethering中的stopTethering進行實現

基本上config的設定和獲取就這些了。大致分析完成之後,也可以看到WifiAP相關的類主要有這麼幾個

  1. WifiApDialog.java:使用者互動介面,直觀呈現出wifiAp的配置資訊,提供使用者修改config的ui互動,繼承自AlertDialog,在構造該dialog物件時會傳入 DialogInterface.OnClickListener和WifiConfiguration,所以也可以看出按鈕點選事件的處理以及所顯示的config內容資訊都是在建立dialog時獲取的,所以總結下來,該類其實就做了兩件事
    • 把所獲取的config載入出來
    • 提供編輯框供user編輯

    其他對於config的read&write一律不進行處理。程式碼目錄為: /packages/apps/Settings/src/com/android/settings/wifi/

  2. TetherSettings.java:使用者互動介面,呈現手機所支援的行動式熱點的開關互動,程式碼目錄為: /packages/apps/Settings/src/com/android/settings/TetherSettings.java
  3. Tethering.java:邏輯實現類,該類中擁有很多業務處理邏輯來支援Android裝置作為BT\USB\WIFI作為閘道器,即裝置作為行動式熱點程式碼的業務邏輯實現。該類中包含網路共享和行動式熱點資訊,即
    • bluetooth_tethering:藍芽網路共享,涉及到BluetoothPan協議
    • usb_tethering:usb網路共享,涉及到裝置連線usb時狀態切換,即是否是充當大容量儲存裝置
    • wifiAp行動式熱點

    程式碼中對這三種模式的開關狀態進行了監聽以及更新。程式碼目錄為: /frameworks/base/services/core/java/com/android/server/connectivity/Tethering.java

  4. WifiManager.java :該類提供了管理wifi連線的主要的api介面,這裡所說的wifi連線包括WiFiAp和WiFi。三方應用開發者在對wifiap進行相關的操作時可以呼叫wifiManager類下的介面。developer需要注意的是在獲取wifiManager物件時必是要應用程式的context,以防止memory leaks記憶體洩漏。程式碼目錄為: /frameworks/base/wifi/java/android/net/wifi/WifiManager.java .wifimanager相關的有以下幾種情況
    • list of configured network:已經配置過的網路列表,即手機中以儲存的 WiFi列表,對已經配置過的wifi可以進行增刪改查的操作viewed、update、modify
    • current active wifi:當前正在執行的WiFi,即可用WiFi列表。列表中的wifi接入點access point 可以連線或者是斷開連線
    • result of access point scans:wifi接入點掃描結果,包含足夠的資訊來決定連線哪一個WiFi熱點
    • 定義了當wifi狀態發生改變時所要傳送的廣播
  5. WifiServiceImpl.java :作為一個binder代理形式的存在,銜接binder的client和server,主要是中間人的作用,該類不對三方應用開發者開放,不存在sdk中。程式碼目錄為: /frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java
  6. WifiStateMachine.java :顧名思義,狀態機,用來監測WiFi的各種連線狀態。程式碼目錄為: /frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java
  7. WifiApConfigStore.java :這個也很顯然,用於wifiAp的config資訊的存取即 reading&writing,大部分的程式碼在文中已經分析過,所以不再分析。程式碼目錄為: /frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiApConfigStore.java

wifiAp裝置連線

——————編輯於2017-08-16——————— 隔了這麼多天,終於有時間更新了,在csdn快兩年時間了,一直堅持著,不幸的是我不知道以後還會不會更新csdn,也許以後的文章會出現在別處…比如公眾號 wifi裝置連線有一個息息相關的類NativeDaemonConnector.java(wifiAp連線原始碼分析會更新在wifiAp開啟原始碼分析之下)

——————編輯於2017-08-18———————

wifiAp開啟原始碼流程

先大致說一下追的流程:如下,

  1. WifiManager.java中setWifiApEnabled呼叫service方法
  2. WifiServiceImpl.java中setWifiApEnabled藉助controller傳送message,msg.what=com.android.server.wifi.WifiController.CMD_SET_AP;
  3. WifiController.java中ApStaDisabledState的processMessage去處理CMD_SET_AP的msg,並觸發mWifiStateMachine.setHostApRunning
  4. WifiStateMachine.java.java中傳送msg,msg.what=CMD_START_AP,並在該類中的SoftApState的enter的方法中處理msg:呼叫 mSoftApManager.start(config);
  5. SoftApManager.java中IdleState的processmessage處理,呼叫startSoftAp、緊接著呼叫 mNmService.startAccessPoint
  6. NetworkManagementService.java中 executeOrLogWithMessage執行開啟wifiap的命令

WifiManager

由上文可知,WifiManager是Android原始碼提供給應用開發者使用的,提供API介面。如果上層應用想要開啟wifiAp,那麼就需要呼叫wifiManager的api—–>setWifiApEnabled(),那麼該方法具體做了什麼呢??

程式碼語言:javascript
複製
 /**
    *利用傳入的config開啟接入點即WiFiap.如果無線已經處於ap模式,那麼就更新         
    *該config,開啟ap模式,禁用sta模式 
    *該方法對於三方應用時hide的,屬於系統api
     */
    @SystemApi
    public boolean setWifiApEnabled(WifiConfiguration wifiConfig, 
       boolean enabled) {
        try {
            mService.setWifiApEnabled(wifiConfig, enabled);
            return true;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

既然是走的service,那就找到service

WifiServiceImpl

程式碼語言:javascript
複製
 IWifiManager mService;

可以看到這裡用到了binder機制,service中方法實際實現是在繼承字WifiManager.Stub的類中,所以找到所需要的類:

程式碼語言:javascript
複製
public class WifiServiceImpl extends IWifiManager.Stub 

也就是說service所對應的代理類為WifiServiceImpl,所以去看該類中的具體方法實現

程式碼語言:javascript
複製
public void setWifiApEnabled(WifiConfiguration wifiConfig,  
   boolean enabled) {
     //檢查是否有android.Manifest.permission.CHANGE_WIFI_STATE許可權
        enforceChangePermission();
     //檢查是否有android.Manifest.permission.TETHER_PRIVILEGED許可權     
       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
        //當config為null時,wifiAp會使用上一次ordefault的config,所以
        //config為null是有意義的,isValid是根據config中傳入的wifiAp的加
        //密方式來進行判斷是否有效的。即如果要配置config的加密方式,那麼一
        //定要配置有效,否則無法開啟ap
        if (wifiConfig == null || isValid(wifiConfig)) {
        //傳送message給mWifiController
            mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
        } else {
            Slog.e(TAG, "Invalid WifiConfiguration");
        }
    }

先是進行一系列的許可權判斷,在允許的條件下傳送msg,可以看到,所傳送的message攜帶的資訊有:

  • msg.what : CMD_SET_AP
  • msg.arg1:如果是開啟ap則為1,如果是關閉ap則為0
  • msg.arg2:傳入為0
  • msg.obj:WifiConfiguration物件

到這裡,WifiServiceImpl的任務就完成了,接下里就是WifiController來處理了

WifiController

WifiController繼承與StateMachine狀態機,用來管理各種操作(airplane,WiFi hotspot)在wifiStateMachine中的on/off狀態。 既然是狀態機,那麼會有一個特點,一旦註冊了狀態處理,那麼就會按照所新增的狀態類去順序執行。 在StateMachine中有一個方法,叫做addState,用於新增狀態:

程式碼語言:javascript
複製
//新增一個state
 public void addState(State state) {}
 ........
 //新增一個state,並且是從fromState執行過後,再執行toState
public void addState(State fromState, State toState) {}

狀態機預設的是線性模型,即按照add(State)的順序執行,但如果使用了 addState(fromState, toState),那麼就相當與指明瞭狀態機的執行順序。 關於狀態機的介紹就是後話了,接下來看接受到msg後的wifiController的處理:wifiController總結起來就做了兩件事

  • 儲存wifiAp的開關狀態:mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED); 該程式碼用於向settingsdb中儲存wifiAp的狀態,所儲存的欄位為: Settings.Global.WIFI_SAVED_STATE
  • 並執行wifiAp的開關操作:mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj, true); 該程式碼用於進行wifiAp的開關操作,呼叫的是WifiStateMachine的setHostApRunning方法,並將wifiConfiguration傳給WifiStateMachine,當然這裡如果是要關閉ap傳入的boolean值為false

所以可以看到wifiController只是起一個當狀態改變時傳遞msg的作用,接下來進入到WifiStateMachine中:

WifiStateMachine

WifiStateMachine繼承自StateMachine,該類用於跟蹤WiFi的連線狀態,所有的事件處理都在這裡,所有連線狀態的改變也是在這裡進行的初始化。Android7.1.1所支援的WiFi操作包括三種:

  1. Clients:裝置作為客戶端連線其他wifi
  2. p2p:wifi直連
  3. softAp: wifi熱點

目前WiFiStateMachine用於處理wifi作為Clients以及WiFi作為softAp,而p2p則交由WifiP2pService進行處理。 接下來直接進去到setHostApRunning方法:

程式碼語言:javascript
複製
public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {
    if (enable) {
         sendMessage(CMD_START_AP, wifiConfig);
    } else {
        sendMessage(CMD_STOP_AP);
    }
 }

很明顯,該方法也是sendmsg,只不過這個msg是在WifiStateMachine這個類中自己處理的,可以看到從此時開始,start/stop wifiAp的msg.what開始不同,而不是僅僅依靠boolean值來區分,因為如果是start的話,需要進行兩步的處理,包括

  1. 載入softAp的hal:setupDriverForSoftAp
  2. 在保證hal載入完成的情況下將要進行的操作以及config傳遞給softAp,讓其開始wifiAp: mSoftApManager.start(config)

而如果是stop的話,則只需要將wifiAp關閉即可,即呼叫 mSoftApManager.stop()。接下來就是SoftApManager中的start和stop了

SoftApManager

start和stop對比分析

程式碼語言:javascript
複製
    /**
     * 利用傳入的config物件開啟ap
     * @param config AP configuration
    */
 public void start(WifiConfiguration config) {
     mStateMachine.sendMessage(SoftApStateMachine.CMD_START, config);
  }

    /**
     * 停止ap
     */
  public void stop() {
     mStateMachine.sendMessage(SoftApStateMachine.CMD_STOP);
  }

可以看到SoftApManager的start和stop只是send了msg

startAp

start時send的msg為

  1. msg.what : SoftApStateMachine.CMD_START
  2. msg.arg1:config物件,這個值就是從wifiApDialog傳過來的,在傳遞過程中如果有為null的情況就載入預設的或者檔案中儲存的,具體可參見上文。但是從傳到這裡開始,config就不會再做任何的修改,所以如果config有為null的情況,則返回

在接收到CMD_START這個msg之後,SoftApManager最終會在startSoftAp方法中進行處理:

程式碼語言:javascript
複製
 private int startSoftAp(WifiConfiguration config) {
    if (config == null) {
       Log.e(TAG, "Unable to start soft AP without configuration");
       return ERROR_GENERIC;
     }
     WifiConfiguration localConfig = new WifiConfiguration(config);
     int result = ApConfigUtil.updateApChannelConfig(
           mWifiNative, mCountryCode, mAllowed2GChannels, localConfig);
     if (result != SUCCESS) {
        Log.e(TAG, "Failed to update AP band and channel");
        return result;
     }
        /* 建立國家程式碼 */
     if (mCountryCode != null) {
        /* 當ap的頻段被設定成5G時,必須設定contry code*/
        if (!mWifiNative.setCountryCodeHal(mCountryCode.toUpperCase(Locale.ROOT))&& config.apBand == WifiConfiguration.AP_BAND_5GHZ) {
           Log.e(TAG, "Failed to set country code, required for setting up " + "soft ap in 5GHz");
           return ERROR_GENERIC;
          }
       }
        /* 當wifiAp的驅動層配置好之後就可以建立wifiAp了*/
        try {
            mNmService.startAccessPoint(localConfig, mInterfaceName);
       } catch (Exception e) {
            Log.e(TAG, "Exception in starting soft AP: " + e);
            return ERROR_GENERIC;
        }

       Log.d(TAG, "Soft AP is started");

        return SUCCESS;
    }

開啟wifiAp接著會去呼叫 mNmService.startAccessPoint:方法的實現在NetworkManagementService.java中,內容如下

程式碼語言:javascript
複製
@Override
public void startAccessPoint(WifiConfiguration wifiConfig, String wlanIface) {        
   mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
   Object[] args;
   String logMsg = "startAccessPoint Error setting up softap";
   try {
       if (wifiConfig == null) {
          args = new Object[] {"set", wlanIface};
       } else {
          args = new Object[] {"set", wlanIface, wifiConfig.SSID,
                   "broadcast",Integer.toString(wifiConfig.apChannel),getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey)};
       }
       //設定wifiConfig
       executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,SOFT_AP_COMMAND_SUCCESS, logMsg);
       logMsg = "startAccessPoint Error starting softap";
       args = new Object[] {"startap"};
       //startap開啟ap
       executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,SOFT_AP_COMMAND_SUCCESS, logMsg);
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        }
    }

該方法首先是拼接command字串,並呼叫方法去執行命令,executeOrLogWithMessage方法是NetworkManagementService的private方法,其實就是利用NativeDaemonConnector這個runnable物件來執行command命令

程式碼語言:javascript
複製
private void executeOrLogWithMessage(String command, Object[] args,int expectedResponseCode, String expectedResponseMessage, String logMsg) throws NativeDaemonConnectorException {
 //返回執行結果
   NativeDaemonEvent event = mConnector.execute(command, args);
     if (event.getCode() != expectedResponseCode || !event.getMessage().equals(expectedResponseMessage)) {
        //當執行失敗時
        Log.e(TAG, logMsg + ": event = " + event);
     }
}

可以看到,構造了個NativeDaemonConnector–mConnector用於執行命令,先看命令執行的傳入引數arguments:

  1. String command : SOFT_AP_COMMAND = “softap” :要執行的command
  2. Object[] args:command的附加引數。這個命令稍微有一點複雜,會根據所傳入的config是否為null而有所不同: if (wifiConfig == null) { args = new Object[] {"set", wlanIface}; } else { args = new Object[] {"set", wlanIface, wifiConfig.SSID,"broadcast", Integer.toString(wifiConfig.apChannel), getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey)}; } 第一個問題,wlanIface的取值: 首先這裡的wlanIface是在構建softApManager物件時藉助WifiNative物件獲取的,WifiNative中獲取wlanIface的地方位於: private static WifiNative wlanNativeInterface = new WifiNative(SystemProperties.get("wifi.interface", "wlan0"), true); 很顯然,wlanIface的值取決於屬性欄位wifi.interface的對應值,可以看到,如果未定義即預設取值為wlan0,原始碼中設定的也是wlan0. 第二個問題,preSharedKey:指的是wifiAp的密碼,之所以列出來是因為原始碼用一層類SensitiveArg將他包裝了起來,該類的作用就是告訴開發者:該欄位屬於敏感內容,禁止使用log列印出來,該類所重寫的toString方法也是將構造時傳入的obj物件轉換成string輸出。 第三個問題,apChannel:定義如下 /** * The channel which AP resides on,currently, US only * 2G 1-11 * 5G 36,40,44,48,149,153,157,161,165 * 0 - find a random available channel according to the apBand * @hide */ public int apChannel = 0; channel是根據wifiConfig所配置的頻段(2.4g或者是5.0,預設是2.4g)來決定的 第四個問題,securitytype:wifiAp的安全保密型別:
WiFiAp探究實錄--功能實現與原始碼分析

經過對以上問題的分析,可以看出args的取值如下: if(config == null){ args = new Object[] {"set","wlan0"}; }else{ //eg:args = new Object[] {"set","wlan0","MyWifiAp","broadcast","0", //"open","12345678"} args = new Object[] {"set","wlan0","YourNetwork name","broadcast","your network apchannel","your network security type","your network password"} }

  1. int expectedResponseCode:執行結果期望值(int),即執行成功時的響應。NetdResponseCode.SoftapStatusResult = 214:
  2. String expectedResponseMessage :執行結果期望值(string型別) SOFT_AP_COMMAND_SUCCESS = “Ok”
  3. String logMsg:如果命令執行失敗,會列印該log:String logMsg = "startAccessPoint Error setting up softap";

接下來看一下execute命令的物件—–mConnector物件: 在NetworkManagementService的構造時會構造mConnector物件

程式碼語言:javascript
複製
mConnector = new NativeDaemonConnector(new NetdCallbackReceiver(), socket, 10, NETD_TAG,160,wl,FgThread.get().getLooper());

傳入引數有7個

  • INativeDaemonConnectorCallbacks callbacks執行結果回撥
  • String socket:關於命令的執行都是藉助socket的輸出流進行處理的,在建立 networkManagerService時會宣告,值為:String NETD_SERVICE_NAME = "netd";
  • int responseQueueSize:響應佇列的大小(message queue,looper)
  • String logTag
  • int maxLogSize
  • PowerManager.WakeLock wl:在這裡,傳入的值為null,因為不再需要喚醒鎖。
  • Looper looper:使訊息藉助handler迴圈處理

接下里就是execute方法,最終會是去呼叫NativeDeamonConnector中的 executeForList(long timeoutMs, String cmd, Object… args)方法進行處理,如下,可以看到executeForList方法會返回一個event的列表,而execute方法只返回列表的第一個event元素

程式碼語言:javascript
複製
public NativeDaemonEvent[] executeForList(long timeout, String cmd, Object... args) throws NativeDaemonConnectorException {
   //記錄下開始execute的時間
    final long startTime = SystemClock.elapsedRealtime();
    //初始化一個NativeDeamonEvent列表物件
    final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
    //初始化兩個sb
    final StringBuilder rawBuilder = new StringBuilder();
    final StringBuilder logBuilder = new StringBuilder();
    //序列號,因為訊息佇列最大允許有10個,該序列號是在當前序列號的基礎上加1
    final int sequenceNumber = mSequenceNumber.incrementAndGet();
    //makeCommand用於將sequenceNumber、cmd
    //args拼接到rawBuilder和logBuilder(如果arg是sensitivearg則用別的
    //字串代替),首先會判斷command是否符合要求,第一command不能
    //有"\0",第二command必須要與argument分開處理即避開args,即cmd不能有
    //" "
    makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
    final String rawCmd = rawBuilder.toString();
    final String logCmd = logBuilder.toString();
    log("SND -> {" + logCmd + "}");
    synchronized (mDaemonLock) {
      //根據上文中所述,wifiap上層與底層基本上是命令或者是檔案儲存的形式進行互動
      //所以在這裡藉助傳入的socket獲取到os
      if (mOutputStream == null) {
      //os為null時丟擲異常
     throw new NativeDaemonConnectorException("missing output stream");
       } else {
           try {                   
           //開始往輸出流中寫cmd,編碼格式為UTF_8
          mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
           } catch (IOException e) {
           //在寫cmd時發生io異常
               throw new NativeDaemonConnectorException("problem sending command", e);
            }
        }
      }
      NativeDaemonEvent event = null;
     do {
        event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
        if (event == null) {
         //在經過了DEFAULT_TIMEOUT:1分鐘之後,處理仍未成功,則丟擲timeout異常
            loge("timed-out waiting for response to " + logCmd);
            throw new NativeDaemonTimeoutException(logCmd, event);
        }
        log("RMV <- {" + event + "}");
        //將處理成功的event新增到arraylist中
        events.add(event);
        //while的判斷條件是event處理之後的返回碼處於[100,200)之間
     } while (event.isClassContinue());
     //記錄下cmd處理結束的時間
     final long endTime = SystemClock.elapsedRealtime();
     //WARN_EXECUTE_DELAY_MS為5秒,如果cmd的處理時間超過5秒則發出處理時間過長
     //的log警告
     if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
        loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
      }
      //如果event的返回碼取值返回為[500,600),則丟擲引數請求異常,即客戶端異常
     if (event.isClassClientError()) {
        throw new NativeDaemonArgumentException(logCmd, event);
     }
     //如果event的返回碼取值為[400,500),則失敗,伺服器處理異常
     if (event.isClassServerError()) {
        throw new NativeDaemonFailureException(logCmd, event);
     }
     //只有event返回碼在[200,300)之間,才表示請求成功
     return events.toArray(new NativeDaemonEvent[events.size()]);
 }
stopAp

而stop時send的msg的資訊為

  • msg.what : SoftApStateMachine.CMD_STOP

對於msg的處理也是在SoftApManager中,

程式碼語言:javascript
複製
private void stopSoftAp() {
     try {
          mNmService.stopAccessPoint(mInterfaceName);
  } catch (Exception e) {
            Log.e(TAG, "Exception in stopping soft AP: " + e);
            return;
 }
        Log.d(TAG, "Soft AP is stopped");
  }

同樣,也是呼叫NetworkManagerMentService中的方法進行處理,分析基本類似startAccessPonint,傳入的cmd與start一致,只不過arguments不同,stop時傳入的args為:

程式碼語言:javascript
複製
Object[] args = {"stopap"};

請求錯誤時的logmsg為:

程式碼語言:javascript
複製
 String logMsg = "stopAccessPoint Error stopping softap";

執行命令後要去重新載入wififirmware,即切換了wifi的模式到sta.(wifi總共有三種模式ap,sta,p2p)

程式碼語言:js
複製
             $(function () {                 $('pre.prettyprint code').each(function () {                     var lines = $(this).text().split('\n').length;                     var $numbering = $('<ul/>').addClass('pre-numbering').hide();                     $(this).addClass('has-numbering').parent().append($numbering);                     for (i = 1; i <= lines; i++) {                         $numbering.append($('<li/>').text(i));                     };                     $numbering.fadeIn(1700);                 });             });         

相關文章