當(dāng)前位置:首頁 > 嵌入式 > 嵌入式軟件
[導(dǎo)讀] 最 近研究Wifi模塊,查了不少的相關(guān)資料,但發(fā)現(xiàn)基本上是基于android2.0版本的的分析,而現(xiàn)在研發(fā)的android移動平臺基本上都是2.3的 版本,跟2.0版本的差別,在Wifi模塊上

 最 近研究Wifi模塊,查了不少的相關(guān)資料,但發(fā)現(xiàn)基本上是基于android2.0版本的的分析,而現(xiàn)在研發(fā)的android移動平臺基本上都是2.3的 版本,跟2.0版本的差別,在Wifi模塊上也是顯而易見的。2.3版本W(wǎng)ifi模塊沒有了WifiLayer,之前的WifiLayer主要負責(zé)一些復(fù) 雜的Wifi功能,如AP選擇等以提供給用戶自定義,而新的版本里面的這塊內(nèi)容基本上被WifiSettings所代替。

本文就是基于android2.3版本的Wifi分析,主要分為兩部分來分別說明:

(1) Wifi模塊相關(guān)文件的解析

(2) Wpa_supplicant解析

(3) Wifi的啟動流程(有代碼供參考分析)

一,Wifi模塊相關(guān)文件解析

1) wifisettings.java

packages/apps/Settings/src/com/android/settings/wifiwifisettings.java

該類數(shù)據(jù)部分主要定義了下面幾個類的變量:

{

private final IntentFilter mFilter;

//廣播接收器,用來接收消息并做響應(yīng)的處理工作

privatefinal BroadcastReceiver mReceiver;

//這是一個掃描類,會在用戶手動掃描 AP時被調(diào)用

privatefinal Scanner mScanner;

private WifiInfo mLastInfo;

//服務(wù)代理端,作為WifiService對外的接口類呈現(xiàn)

privateWifiManager mWifiManager;

//這個類主要實現(xiàn)Wifi的開閉工作

privateWifiEnabler mWifiEnabler;

//AP

private AccessPoint mSelected;

private WifiDialog mDialog;

……

}

wifiSettings類的構(gòu)造函數(shù)的主要工作:定義了一個IntentFilter(Intent過濾器)變量,并添加了六個動作,(了解 Android的intent機制的同學(xué)都知道什么意思,不明白的同學(xué)參考Intent機制的資料)接著定義一個廣播接收器,并有相應(yīng)的消息處理函數(shù),下 面是該構(gòu)造函數(shù)的定義:

public WifiSettings() {

mFilter = new IntentFilter();

//intent機制中的intent消息過濾器,下面添加可以處理的動作

mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);

mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);

mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);

mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);

mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);

mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);

//注冊了廣播接收器,用來處理接收到的消息事件

mReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context,Intent intent) {

handleEvent(intent); //事件處理函數(shù)

}

};

mScanner= new Scanner(); //手動掃描類

}

在廣播接收器中的相應(yīng)函數(shù)onReceive函數(shù)中有個handleEvent函數(shù),它就是用來處理廣播接收器接受到的intent消息的,它的功能是根 據(jù)intent消息中的動作類型,來執(zhí)行相應(yīng)的操作,每一種動作對應(yīng)了activity的一項消息處理能力。

在oncreate函數(shù)中實例化了mWifiManager和mWifiEnabler兩個類,這兩個類對wifiSettings來說至關(guān)重要,它后面的定義的一系列函數(shù)都是通過調(diào)用這兩個類的相應(yīng)接口來實現(xiàn)的。

……

mWifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);

mWifiEnabler = new WifiEnabler(this,

(CheckBoxPreference) findPreference("enable_wifi"));

……

WifiSettings中還定義了顯示菜單和響應(yīng)菜單鍵的函數(shù),即onCreateOptionsMenu()和 onOptionsItemSelected();還有響應(yīng)配置對話框中按鍵的onClick()函數(shù);最后定義了Scanner類,它是一個 handler的繼承類,實現(xiàn)了消息處理函數(shù),用于處理手動掃描的動作。

2) WifiEnabler.java:

packages/apps/Settings/src/com/android/settings/wifi/WifiEnabler.java

private final Context mContext;

private final CheckBoxPreference mCheckBox;

//兩個重要成員

private final WifiManager mWifiManager;

private final IntentFilter mIntentFilter;

wifienabler類中定義了四個成員變量很重要,mContext,mCheckBox,mWifiManager和mReceiver,其中 mContext用于獲取mwifiManager實例,mReceiver用來接收底層發(fā)來的消息,mCheckBox用來改變UI的狀態(tài)。

該 類中定義了幾個重要的函數(shù)onPreferenceChange,handleWifiStateChanged和 handleStateChanged,onPreferenceChange用來處理按下的Enbler鍵,它會調(diào)用 mWifiManager.setWifiEnabled(enable),另外兩個用來處理接受的消息事件。

在類的構(gòu)造函數(shù)中,主要做了一下工作:初始化了mContext,mCheckBox,mWifimanager,并且初始化了一個 mIntentFilter變量,添加了三個動作,在構(gòu)造函數(shù)的上面定義了一個廣播接收器,用來接收下層傳來的消息,并根據(jù)intent動作的類型調(diào)用相 應(yīng)的處理函數(shù),這個廣播接收器在onResum函數(shù)中被注冊。

public WifiEnabler(Context context, CheckBoxPreferencecheckBox) {

mContext= context;

mCheckBox = checkBox;

mOriginalSummary = checkBox.getSummary();

checkBox.setPersistent(false);

mWifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);

mIntentFilter= new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);

// Theorder matters! We really should not depend on this. :(

mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);

mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);

}

這 里可以總結(jié)為:如果上層需要監(jiān)聽或收到下層的消息,那么就要通過定義一個BroadcastReciever,并將它注冊,當(dāng)然在接受到消息后應(yīng)該有處理 消息的函數(shù),然后在onReciever函數(shù)中根據(jù)消息調(diào)用相應(yīng)的處理函數(shù),這里的消息通知機制是Intent,在BroadcastReciever類 的onReciever函數(shù)的參數(shù)中可以看出。[!--empirenews.page--]

該類成員函數(shù)的也是通過調(diào)用mWifimanager的接口來實現(xiàn)的。

3) WifiManager:

frameworks/base/wifi/java/android/net/wifi/WifiManager.java

兩個重要的數(shù)據(jù)成員:

//WifiService

IWifiManager mService;

HandlermHandler;

IWifiManager mService和HandlermHandler,這個類擁有了一個WifiService實例,就可以通過它進行一系列的調(diào) 用;WifiManager中定義了的wifi和ap的狀態(tài),這些狀態(tài)會在其他很多類中有使用;然后定義了大量的函數(shù),這些函數(shù)幾乎都是對 WifiService接口函數(shù)的封裝,直接調(diào)用WifiService的函數(shù)。

該類的構(gòu)造函數(shù)很簡單:

public WifiManager(IWifiManager service,Handler handler) {

mService = service;

mHandler = handler;

}

該 類中還定義了一個WifiLock類,這個類用來保證在有應(yīng)用程序使用Wifi無線電傳輸數(shù)據(jù)時,wifiradio可用,即當(dāng)一個應(yīng)用程序使用wifi 的radio進行無線電數(shù)據(jù)傳輸時,就要先獲得這個鎖,如果該鎖已被其他程序占有,就要等到該鎖被釋放后才能獲得,只用當(dāng)所有持有該鎖的程序都釋放該鎖 后,才能關(guān)閉radio功能。

4)WifiService:

frameworks/base/services/java/com/android/server/WifiService.java

private final WifiStateTrackermWifiStateTracker;

private Context mContext;

private WifiWatchdogServicemWifiWatchdogService = null;

private final WifiHandler mWifiHandler;

這是WifiService中的幾個重要的數(shù)據(jù)成員。

在 接下來的構(gòu)造函數(shù)中初始化了mWifiStateTracker,mContext,然后動態(tài)生成mWifiThread子線程并啟動,在主線程里用 mWifiThread調(diào)用getLooper()函數(shù)獲得線程的looper,來初始化創(chuàng)建一個mWifiHandler對象,這個 WifiHandler在WifiService類的后面有定義,并重載了Handler類的handlermessage()函數(shù),這樣消息就可以在主 線程里被處理了,這是android的handlerthread消息處理機制,可參考相關(guān)資料,這里不予詳述。在構(gòu)造函數(shù)的最后,注冊了兩個廣播接收 器,分別用來ACTION_AIRPLANE_MODE_CHANGED和ACTION_TETHER_STATE_CHANGED這兩個動作,這里是 android的intent消息通知機制,請參考相關(guān)資料,代碼如下:

mContext = context;

mWifiStateTracker = tracker;

mWifiStateTracker.enableRssiPolling(true);

……

HandlerThread wifiThread = newHandlerThread("WifiService");

wifiThread.start();

mWifiHandler = newWifiHandler(wifiThread.getLooper());

……

隨 后定義了一系列的函數(shù),其中有服務(wù)器要發(fā)送的命令的系列函數(shù),它通過mWifiStateTracker成員類調(diào)用自己的的發(fā)送命令的接口(其實就是對本 地接口的一個封裝),最后通過適配層發(fā)送命令給wpa_supplicant,而事件處理只到WifiStateTracker層被處理。

要 注意的是,在WifiService中,定義了一些函數(shù)來創(chuàng)建消息,并通過mWifiHandler將消息發(fā)送到消息隊列上,然后在 mHandlerThread線程體run()分發(fā)\處理消息,在主線程中被mWifiHandler的handlerMessage()函數(shù)處理,最后 調(diào)用mWifiStateTracker的對應(yīng)函數(shù)來實現(xiàn)的。這里我也不明白為什么WifiService不直接調(diào)用mWifiStateTracker 對應(yīng)的函數(shù),還要通過消息處理機制,繞了一圈在調(diào)用,當(dāng)然Google這么做肯定是有它道理的,忘高手指點。

5) WifiStateTracker類:

frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java

NetworkStateTracker繼承了handler類,而WifiStateTracker繼承了NetworkStateTracker類,就是說WifiStateTracker間接繼承了handler類,屬于一個事件處理類。

WifiStateTracker類首先定義了事件日志和事件碼(這里包含了所有可能的事件類型),還定義了如下的重要成員數(shù)據(jù):

//幾個重要的數(shù)據(jù)成員

private WifiMonitor mWifiMonitor; //被啟動用來監(jiān)聽supplicant傳來的消息

private WifiInfo mWifiInfo;

private WifiManager mWM; //服務(wù)代理

private DhcpHandler mDhcpTarget; //IP地址獲取線程

private DhcpInfo mDhcpInfo; //Dhcp的相關(guān)信息都在這里

類的構(gòu)造函數(shù)中,初始化了系列成員變量,包括生成了WifiMonitor的實例,在構(gòu)造函數(shù)中,因為WifiStateTracker是一個handler間接子類,所以他會自動調(diào)用handler的無參構(gòu)造函數(shù),獲得looper和Queue消息隊列。

然后定義了一些設(shè)置supplicant和更新網(wǎng)絡(luò)信息的輔助函數(shù)。

startEventLoop()函數(shù)很重要,用來啟動WifiMonitor線程,進入消息循環(huán)檢測。接著定義了系列通知函數(shù),被 WifiMonitor調(diào)用來向WifiStateTracker傳遞從wpa_supplicant接收到的消息,他會調(diào)用消息發(fā)送函數(shù)到消息隊列,并 被WifiStateTracker的handlermessage()函數(shù)處理。這個handlermessage()函數(shù)就是在隨后被定義的,它主要 是調(diào)用相應(yīng)的輔助函數(shù)完成動作,并可能會將消息封裝后,通過intent機制發(fā)送出去,被上層的UI活動接收處理。

這 里也定義了很多的WfiNative接口函數(shù),這是JNI的本地接口;類DhcpHandler extends Handler{}也是在該類中定義的,它也是一個handler的子類,用來處理DHCP相關(guān)的消息EVENT_DHCP_START,可以想到它和 WifiStateTracker不是共用一個looper。

注意:handleMessage是在該文件中定義的,用來處理經(jīng)WifiMonitor轉(zhuǎn)換過的消息。

6) WifiMonitor

frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java

聲明了一個重要的成員變量:mWifiStateTracker,并在構(gòu)造函數(shù)中由參數(shù)提供初始化,還定義了一系列的可能從wpa_supplicant層接收的事件類型及其名字,這些是消息處理機制的基礎(chǔ)。

startMonitoring()函數(shù),這是一個線程啟動的封裝函數(shù),WifiStateTracker就是通過這個函數(shù)啟動的WifiThread。

這 個重要的類classMonitorThreadextends Thread{};它是一個監(jiān)控進程類,里面有一系列的事件處理函數(shù)和一個重要的Run()函數(shù),run函數(shù)主要流 程:connectToSupplicant()連接精靈進程wpa_supplicant,這里有一個 mWifiStateTracker.notifySupplicantXXX()的調(diào)用,通知上層是否連接成功,然后就是一個輪詢過程,其中調(diào)用了 WifiNative.waitForEvent()本地輪詢函數(shù)接口,并從返回的事件字符串類型中提取事件的名稱,最后通過事件的名稱調(diào)用相應(yīng)的事件處 理函數(shù),并將事件轉(zhuǎn)換成mWifiStateTracker能識別的類型上報。[!--empirenews.page--]

注意:這里的每個事件的捕獲(由wifimonitor完成),最終都會調(diào)用相應(yīng)的mWifiStateTracker的消息通知函數(shù)上報消息;輪詢和事件處理都是在Monitor線程類中實現(xiàn)的。

7)WifiNative

frameworks/base/wifi/java/android/net/wifi/WifiNative.java

里面定義了一個類WifiNative:其中聲明了許多本地接口,可由native的標(biāo)志看出,這是Java代碼和本地庫之間的聯(lián)系接口;

8) android_net_wifi_Wifi.java

frameworks/base/core/jni/

里面定義了許多的JNI的本地代碼實現(xiàn),每個實現(xiàn)中都會調(diào)用wpa_supplicant適配層的接口,通過包含適配層的頭文件wifi.h獲取適配層定 義的接口;后面是一個JNINativeMethod數(shù)組,定義了每個本地接口和本地實現(xiàn)的對應(yīng)關(guān)系;最后有一個注冊函數(shù) regester_XXX_XX(),用以把上面的方法類數(shù)組注冊到系統(tǒng)中。

該類實現(xiàn)了本地接口的相關(guān)功能,并通過調(diào)用wpa的適配層的相關(guān)函數(shù)和wpa_supplicant通信,所以JNI是連接Java框架層和wpa適配層的橋梁.

9)wpa_supplicant適配層,wifi.c:目錄libhardware/wifi/

里面定義很多字符串變量和適配層的接口實現(xiàn),是對wpa_supplicant程序通信的接口封裝,用來完成上層和wpa_supplicant的通信, 頭文件在libhardware/include/hardware下,這里的函數(shù)用來向JNI的本地實現(xiàn)提供調(diào)用接口。

這里的函數(shù),我把它們分為三類函數(shù):

一 類是命令相關(guān)的(控制)函數(shù),就是在JNI層android_XXX_Command()函數(shù)所調(diào)用的::Wifi_Command()函數(shù),調(diào)用流 程:android_XXX_command()=>docommand()=>wifi_command()=> wifi_send_command()=>wpa_ctrl_require()。

二類是監(jiān)聽函數(shù),即Wifi_wait_for_event()函數(shù),調(diào)用流程:android_net_wifi_Waitforevent()=>wifi_wait_for_event()=>wpa_ctrl_recv()。

三類是剩下的函數(shù)。

10)wpa_supplicant與上層的接口,wpa_ctrl.c:external/wpa_supplicant

定義了三類套接字,并分別實現(xiàn)了和wpa_supplicant的通信,因此wpa_supplicant適配層和wpa_supplicant層是通過socket通訊的。

要 是從wifi.c中真的很難看出它和wpa_supplicant有什么關(guān)系,和它聯(lián)系密切的是wpa_ctrl.h文件,這里面定義了一個類 wpa_ctrl,這個類中聲明了兩個Socket套接口,一個是本地一個是要連接的套接口,wpa_ctrl與wpa_supplicant的通信就需 要socket來幫忙了,而wpa_supplicant就是通過調(diào)用wpa_ctrl.h中定義的函數(shù)和wpa_supplicant進行通訊 的,wpa_ctrl類(其實是其中的兩個socket)就是他們之間的橋梁。

11)wpa_supplicant和driver_wext驅(qū)動接口的聯(lián)系:

driver.h:該文件定義了系列結(jié)構(gòu),首先是一個wpa_scan_result結(jié)構(gòu),這是一個掃描結(jié)果的通用格式,里面包含了掃描的所有信息(如 BSSID,SSID,信號質(zhì)量,噪音水平,支持的最大波特率等等信息),每個驅(qū)動接口實現(xiàn)負責(zé)將從驅(qū)動中上傳的掃描信息的格式轉(zhuǎn)換到該通用的掃描信息格 式;然后是一些宏定義和枚舉定義,最后也是最重要的是wpa_driver_ops結(jié)構(gòu),該結(jié)構(gòu)是wpa driver的操作函數(shù)集合,里面有驅(qū)動接口的名稱和很多的函數(shù)指針。

drviers.c:該文件很簡單,首先是一些外部變量的引用聲明,都是不同驅(qū)動操作接口的集合wpa_driver_XXX_ops變量;然后就是定義一個驅(qū)動操作接口集合的數(shù)組,根據(jù)宏定義添加對應(yīng)的驅(qū)動操作接口集合的變量。

drvier_XXX.h:這是不同驅(qū)動接口頭文件,主要聲明了操作接口

drvier_XXX.c:實現(xiàn)操作接口,定義一個操作集合變量,并用上面定義的函數(shù)初始化其中的函數(shù)指針

注意:下面要搞清楚wpa_supplicant守護進程是如何操作,最后調(diào)用驅(qū)動接口集合中的函數(shù)的;要知道wpa_supplicant是為不同驅(qū)動 和操作系統(tǒng)具有更好移植性而被設(shè)計的,以便在wpa_supplicant層不用實現(xiàn)驅(qū)動的具體接口就可以添加新的驅(qū)動程序;在 wpa_supplicant結(jié)構(gòu)中有一個wpa_drv_ops類型的drvier成員,在wpa_supplicant進程中,經(jīng)常通過 Wpa_supplicant_XXX函數(shù)傳遞wpa_supplicant實例指針wpa_s參數(shù)給wpa_drv_XXX函數(shù)來調(diào)用它,在 wpa_drv_XX中會通過wpa_s->driver->XXX()的流程來調(diào)用通用驅(qū)動接口,簡單才是生活的真相,可android始 終讓我感到真相還是遙不可及。

12)WifiWatchdogService:

首 先聲明了兩個主要變量mWifiStateTracker,mWifiManager,需要這兩個類對象來完成具體的控制工作,在 WifiWatchdogService的構(gòu)造函數(shù)中,創(chuàng)建了這兩個類,并通過regesterForWifiBroadcasts()注冊了 BroadcastReceiver,BroadcastReceiver是用來獲取網(wǎng)絡(luò)變化的,然后啟動了一個WatchdogTread線程,用來處 理從WifiStateTracker接收到的消息。

frameworks/base/services/java/com/android/server/WifiWatchdogService.java

WifiWatchdogService(Context context,WifiStateTracker wifiStateTracker) {

mContext = context;

mContentResolver = context.getContentResolver();

mWifiStateTracker =wifiStateTracker;

mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

createThread();

// The content observer to listen needs a handler, which createThreadcreates

registerForSettingsChanges();

if (isWatchdogEnabled()) {

registerForWifiBroadcasts();

}

if (V) {

myLogV("WifiWatchdogService: Created");

}

}

WifiWatchdogHandler繼承了handler類,成為一個消息處理類,定義handlemessage()函數(shù),處理消息隊列上的消息。

二,wpa_supplicant再解析

1)wpa_ctrl.h:

該文件中并沒有定義structwpa_ctrl結(jié)構(gòu),因為在其他包含該文件的.c文件中不能直接使用該結(jié)構(gòu)的成員,這里主要聲明了幾個用于使用 socket通信的函數(shù)接口,包 括:wpa_ctrl_open,wpa_ctrl_close,wpa_ctrl_attach,wpa_ctrl_detach,wpa_ctrl_cleanup,wpa_ctrl_recv,wpa_ctrl_request, wpa_ctrl_pending, wpa_ctrl_get_fd 等函數(shù)。[!--empirenews.page--]

這些函數(shù)的功能從名字上能看出,open函數(shù)就是創(chuàng)建一個socket接口,并綁定連接wpa_supplicant,attach函數(shù)用于定義一個控制 接口為監(jiān)聽接口,pending函數(shù)用于查詢有無消息可讀處理,request和recv分別用來發(fā)送和讀取消息。

其實,這里就是一個使用socket通信的封裝,具體內(nèi)容可以參考socket編程。

2)wpa_ctrl.c:

這里首先定義了一個wpa_ctrl結(jié)構(gòu),里面根據(jù)UDP,UNIX和命名管道三種domain類型來定義通信實體:

struct wpa_ctrl {

#ifdefCONFIG_CTRL_IFACE_UDP

int s;

struct sockaddr_in local;

struct sockaddr_in dest;

char *cookie;

#endif /*CONFIG_CTRL_IFACE_UDP */

#ifdefCONFIG_CTRL_IFACE_UNIX

int s;

struct sockaddr_un local;

struct sockaddr_un dest;

#endif /*CONFIG_CTRL_IFACE_UNIX */

#ifdef CONFIG_CTRL_IFACE_NAMED_PIPE

HANDLE pipe;

#endif /*CONFIG_CTRL_IFACE_NAMED_PIPE */

};

然后是根據(jù)上面三個類型分別實現(xiàn)了wpa_ctrl.h中聲明的接口函數(shù),這里就不做介紹了。

3)wpa_supplicant.h:

首先,定義了一個枚舉wpa_event_type,羅列了系列wpa的事件類型,然后就是wpa_event_data類型,隨后是兩個函數(shù):wpa_supplicant_event和wpa_supplicant_rx_eapol。

wpa_supplicant.c: 首先定義一個驅(qū)動操作數(shù)組externstruct wpa_driver_ops *wpa_supplicant_drivers[],然后是系列wpa_supplicant_XXX()函數(shù),很多函數(shù)里面調(diào)用 wpa_drv_XXX()函數(shù),這些函數(shù)是wpa_supplicant_i.h中實現(xiàn)的函數(shù)。幾乎每個函數(shù)都需要一個wpa_supplicant結(jié) 構(gòu),對其進行所有的控制和通信操作。

4)wpa_supplicant_i.h:

開頭定義了幾個結(jié)構(gòu), 如:wpa_blacklist,wpa_interface,wpa_params,wpa_global,wpa_client_mlme和 wpa_supplicant等結(jié)構(gòu),其中wpa_supplicant最為重要,wpa_supplicant結(jié)構(gòu)里有一個重要的driver成員,它 是wpa_driver_ops類型,可以被用來調(diào)用抽象層的接口。

接下來是系列函數(shù)聲明,這些函數(shù)聲明在wpa_supplicant.c中實現(xiàn),然后就是wpa_drv_XXX函數(shù),這些函數(shù)就是在 wpa_supplicant.c中被wpa_supplicant_xxx函數(shù)調(diào)用的,而這些wpa_drv_xxx函數(shù)也都有一個 wpa_supplicant結(jié)構(gòu)的變量指針,用來調(diào)用封裝的抽象接口。

這里要注意的是:在wpa_suppliant.c文件中定義的很多函數(shù)是在該頭文件中聲明的,而不是在wpa_supplicant.h中聲明的。

5)driver.h:

該文件中定義了一個重要的數(shù)據(jù)結(jié)構(gòu):wpa_scan_result,這是一個從驅(qū)動發(fā)來的數(shù)據(jù)被封裝成的通用的掃描結(jié)果數(shù)據(jù)結(jié)構(gòu),每個驅(qū)動結(jié)構(gòu)的實現(xiàn)都 要遵循的掃描結(jié)果格式,比如driver_wext.c中的實現(xiàn)。后面還有定義了很多的數(shù)據(jù)結(jié)構(gòu),這里不具表。

文件中最重要的一個數(shù)據(jù)結(jié)構(gòu)是:wpa_driver_ops,這是所有驅(qū)動接口層必須為之實現(xiàn)的API,是所有驅(qū)動類型的一個接口封裝包,wpa_supplicant就是通過該接口來和驅(qū)動交互的。

6)driver_wext.h:

該文件很簡單,就是聲明了該驅(qū)動的一些對應(yīng)驅(qū)動API接口的函數(shù)。

driver_wext.c:

此文件就是實現(xiàn)了上面的一些函數(shù),最后初始化了一個wpa_drv_ops變量。

三,Wifi模塊解析

1)框架分析

圖示1:Wifi框架

首先,用戶程序使用WifiManager類來管理Wifi模塊,它能夠獲得Wifi模塊的狀態(tài),配置和控制Wifi模塊,而所有這些操作都要依賴Wifiservice類來實現(xiàn)。

WifiService和WifiMonitor類是Wifi框架的核心,如圖所示。下面先來看看WifiService是什么時候,怎么被創(chuàng)建和初始化的。

在systemServer啟動之后,它會創(chuàng)建一個ConnectivityServer對象,這個對象的構(gòu)造函數(shù)會創(chuàng)建一個WifiService的實例,代碼如下所示:

framework/base/services/java/com/android/server/ConnectivityService.java

{

……

case ConnectivityManager.TYPE_WIFI:

if (DBG) Slog.v(TAG, "Starting Wifi Service.");

WifiStateTracker wst = new WifiStateTracker(context, mHandler); //創(chuàng)建WifiStateTracker實例

WifiService wifiService = newWifiService(context, wst);//創(chuàng)建WifiService實例

ServiceManager.addService(Context.WIFI_SERVICE, wifiService); //向服務(wù)管理系統(tǒng)添加Wifi服務(wù)

wifiService.startWifi(); //啟動Wifi

mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst;

wst.startMonitoring(); //啟動WifiMonitor中的WifiThread線程

……

}

WifiService的主要工作:WifiMonitor和Wpa_supplicant的啟動和關(guān)閉,向Wpa_supplicant發(fā)送命令。

WifiMonitor的主要工作:阻塞監(jiān)聽并接收來自Wpa_supplicant的消息,然后發(fā)送給WifiStateTracker。

上面兩個線程通過AF_UNIX套接字和Wpa_supplicant通信,在通信過程中有兩種連接方式:控制連接和監(jiān)聽連接。它們創(chuàng)建代碼如下:

ctrl_conn =wpa_ctrl_open(ifname);

.. .. ..

monitor_conn = wpa_ctrl_open(ifname);

2)Wifi啟動流程

(1)使能Wifi

要想使用Wifi模塊,必須首先使能Wifi,當(dāng)你第一次按下Wifi使能按鈕時,WirelessSettings會實例化一個WifiEnabler對象,實例化代碼如下:

packages/apps/settings/src/com/android/settings/WirelessSettings.java

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

……

CheckBoxPreferencewifi = (CheckBoxPreference) findPreference(KEY_TOGGLE_WIFI);

mWifiEnabler= new WifiEnabler(this, wifi);

……

}

WifiEnabler類的定義大致如下,它實現(xiàn)了一個監(jiān)聽接口,當(dāng)WifiEnabler對象被初始化后,它監(jiān)聽到你按鍵的動作,會調(diào)用響應(yīng)函數(shù) onPreferenceChange(),這個函數(shù)會調(diào)用WifiManager的setWifiEnabled()函數(shù)。[!--empirenews.page--]

public class WifiEnabler implementsPreference.OnPreferenceChangeListener {

……

public boolean onPreferenceChange(Preference preference,Object value) {

booleanenable = (Boolean) value;

……

if (mWifiManager.setWifiEnabled(enable)) {

mCheckBox.setEnabled(false);

……

}

……

}

我們都知道Wifimanager只是個服務(wù)代理,所以它會調(diào)用WifiService的setWifiEnabled()函數(shù),而這個函數(shù)會調(diào)用 sendEnableMessage()函數(shù),了解android消息處理機制的都知道,這個函數(shù)最終會給自己發(fā)送一個 MESSAGE_ENABLE_WIFI的消息,被WifiService里面定義的handlermessage()函數(shù)處理,會調(diào)用 setWifiEnabledBlocking()函數(shù)。下面是調(diào)用流程:

mWifiEnabler.onpreferencechange()=>mWifiManage.setWifienabled()=>mWifiService.setWifiEnabled()=>mWifiService.sendEnableMessage()=>mWifiService.handleMessage()=>mWifiService.setWifiEnabledBlocking().

在setWifiEnabledBlocking()函數(shù)中主要做如下工作:加載Wifi驅(qū)動,啟動wpa_supplicant,注冊廣播接收器,啟動WifiThread監(jiān)聽線程。代碼如下:

……

if (enable) {

if (!mWifiStateTracker.loadDriver()) {

Slog.e(TAG, "Failed toload Wi-Fi driver.");

setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);

return false;

}

if (!mWifiStateTracker.startSupplicant()) {

mWifiStateTracker.unloadDriver();

Slog.e(TAG, "Failed tostart supplicant daemon.");

setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);

return false;

}

registerForBroadcasts();

mWifiStateTracker.startEventLoop();

……

至此,Wifi使能結(jié)束,自動進入掃描階段。

(2) 掃描AP

當(dāng)驅(qū)動加載成功后,如果配置文件的AP_SCAN = 1,掃描會自動開始,WifiMonitor將會從supplicant收到一個消息EVENT_DRIVER_STATE_CHANGED,調(diào)用 handleDriverEvent(),然后調(diào)用mWifiStateTracker.notifyDriverStarted(),該函數(shù)向消息隊列 添加EVENT_DRIVER_STATE_CHANGED,handlermessage()函數(shù)處理消息時調(diào)用scan()函數(shù),并通過 WifiNative將掃描命令發(fā)送到wpa_supplicant。

Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java

private void handleDriverEvent(Stringstate) {

if (state == null) {

return;

}

if (state.equals("STOPPED")) {

mWifiStateTracker.notifyDriverStopped();

} else if (state.equals("STARTED")) {

mWifiStateTracker.notifyDriverStarted();

} else if (state.equals("HANGED")) {

mWifiStateTracker.notifyDriverHung();

}

}

Frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java

case EVENT_DRIVER_STATE_CHANGED:

switch(msg.arg1) {

case DRIVER_STARTED:

/**

*Set the number of allowed radio channels according

*to the system setting, since it gets reset by the

*driver upon changing to the STARTED state.

*/

setNumAllowedChannels();

synchronized (this) {

if (mRunState == RUN_STATE_STARTING) {

mRunState = RUN_STATE_RUNNING;

if (!mIsScanOnly) {

reconnectCommand();

} else {

// In somesituations, supplicant needs to be kickstarted to

// start thebackground scanning

scan(true);

}

}

}

break;

上面是啟動Wifi時,自動進行的AP的掃描,用戶當(dāng)然也可以手動掃描AP,這部分實現(xiàn)在WifiService里面,WifiService通過startScan()接口函數(shù)發(fā)送掃描命令到supplicant。

Frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java

public boolean startScan(booleanforceActive) {

enforceChangePermission();

switch (mWifiStateTracker.getSupplicantState()) {

case DISCONNECTED:

case INACTIVE:

case SCANNING:

case DORMANT:

break;

default:

mWifiStateTracker.setScanResultHandling(

WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY);

break;

}

return mWifiStateTracker.scan(forceActive);

}

然后下面的流程同上面的自動掃描,我們來分析一下手動掃描從哪里開始的。我們應(yīng)該知道手動掃描是通過菜單鍵的掃描鍵來響應(yīng)的,而響應(yīng)該動作的應(yīng)該是 WifiSettings類中Scanner類的handlerMessage()函數(shù),它調(diào)用WifiManager的 startScanActive(),這才調(diào)用WifiService的startScan()。

packages/apps/Settings/src/com/android/settings/wifiwifisettings.java

public boolean onCreateOptionsMenu(Menu menu) {

menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan)

.setIcon(R.drawable.ic_menu_scan_network);

menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)

.setIcon(android.R.drawable.ic_menu_manage);

return super.onCreateOptionsMenu(menu);

}

當(dāng)按下菜單鍵時,WifiSettings就會調(diào)用這個函數(shù)繪制菜單。如果選擇掃描按鈕,WifiSettings會調(diào)用onOptionsItemSelected()。

packages/apps/Settings/src/com/android/settings/wifiwifisettings.java[!--empirenews.page--]

public booleanonOptionsItemSelected(MenuItem item) {

switch (item.getItemId()) {

case MENU_ID_SCAN:

if(mWifiManager.isWifiEnabled()) {

mScanner.resume();

}

return true;

case MENU_ID_ADVANCED:

startActivity(new Intent(this,AdvancedSettings.class));

return true;

}

return super.onOptionsItemSelected(item);

}

private class Scanner extends Handler {

private int mRetry = 0;

void resume() {

if (!hasMessages(0)) {

sendEmptyMessage(0);

}

}

void pause() {

mRetry = 0;

mAccessPoints.setProgress(false);

removeMessages(0);

}

@Override

public void handleMessage(Message message) {

if (mWifiManager.startScanActive()){

mRetry = 0;

} else if (++mRetry >= 3) {

mRetry = 0;

Toast.makeText(WifiSettings.this, R.string.wifi_fail_to_scan,

Toast.LENGTH_LONG).show();

return;

}

mAccessPoints.setProgress(mRetry != 0);

sendEmptyMessageDelayed(0, 6000);

}

}

這里的mWifiManager.startScanActive()就會調(diào)用WifiService里的startScan()函數(shù),下面的流程和上面的一樣,這里不贅述。

當(dāng)supplicant完成了這個掃描命令后,它會發(fā)送一個消息給上層,提醒他們掃描已經(jīng)完成,WifiMonitor會接收到這消息,然后再發(fā)送給WifiStateTracker。

Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java

void handleEvent(int event, String remainder) {

switch (event) {

caseDISCONNECTED:

handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED,remainder);

break;

case CONNECTED:

handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED,remainder);

break;

case SCAN_RESULTS:

mWifiStateTracker.notifyScanResultsAvailable();

break;

case UNKNOWN:

break;

}

}

WifiStateTracker將會廣播SCAN_RESULTS_AVAILABLE_ACTION消息:

Frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java

public voidhandleMessage(Message msg) {

Intent intent;

……

case EVENT_SCAN_RESULTS_AVAILABLE:

if(ActivityManagerNative.isSystemReady()) {

mContext.sendBroadcast(new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));

}

sendScanResultsAvailable();

/**

* On receiving the first scanresults after connecting to

* the supplicant, switch scanmode over to passive.

*/

setScanMode(false);

break;

……

由于WifiSettings類注冊了intent,能夠處理SCAN_RESULTS_AVAILABLE_ACTION消息,它會調(diào)用handleEvent(),調(diào)用流程如下所示。

WifiSettings.handleEvent() =>WifiSettings.updateAccessPoints() => mWifiManager.getScanResults() => mService.getScanResults()=> mWifiStateTracker.scanResults() => WifiNative.scanResultsCommand()……

將獲取AP列表的命令發(fā)送到supplicant,然后supplicant通過Socket發(fā)送掃描結(jié)果,由上層接收并顯示。這和前面的消息獲取流程基本相同。

(3)配置,連接AP

當(dāng)用戶選擇一個活躍的AP時,WifiSettings響應(yīng)打開一個對話框來配置AP,比如加密方法和連接AP的驗證模式。配置好AP后,WifiService添加或更新網(wǎng)絡(luò)連接到特定的AP。

packages/apps/settings/src/com/android/settings/wifi/WifiSetttings.java

public booleanonPreferenceTreeClick(PreferenceScreen screen, Preference preference) {

if (preference instanceof AccessPoint) {

mSelected = (AccessPoint) preference;

showDialog(mSelected, false);

} else if (preference == mAddNetwork) {

mSelected = null;

showDialog(null, true);

} else if (preference == mNotifyOpenNetworks) {

Secure.putInt(getContentResolver(),

Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,

mNotifyOpenNetworks.isChecked() ? 1 : 0);

} else {

return super.onPreferenceTreeClick(screen, preference);

}

return true;

}

配置好以后,當(dāng)按下“Connect Press”時,WifiSettings通過發(fā)送LIST_NETWORK命令到supplicant來檢查該網(wǎng)絡(luò)是否配置。如果沒有該網(wǎng)絡(luò)或沒有配置 它,WifiService調(diào)用addorUpdateNetwork()函數(shù)來添加或更新網(wǎng)絡(luò),然后發(fā)送命令給supplicant,連接到這個網(wǎng)絡(luò)。 下面是從響應(yīng)連接按鈕到WifiService發(fā)送連接命令的代碼:

packages/apps/settings/src/com/android/settings/wifi/WifiSetttings.java

public void onClick(DialogInterfacedialogInterface, int button) {

if (button == WifiDialog.BUTTON_FORGET && mSelected != null) {

forget(mSelected.networkId);

} else if (button == WifiDialog.BUTTON_SUBMIT && mDialog !=null) {

WifiConfiguration config = mDialog.getConfig();

if (config == null) {

if (mSelected != null&& !requireKeyStore(mSelected.getConfig())) {

connect(mSelected.networkId);

}

} else if (config.networkId != -1) {[!--empirenews.page--]

if (mSelected != null) {

mWifiManager.updateNetwork(config);

saveNetworks();

}

} else {

int networkId =mWifiManager.addNetwork(config);

if (networkId != -1) {

mWifiManager.enableNetwork(networkId, false);

config.networkId =networkId;

if (mDialog.edit || requireKeyStore(config)){

saveNetworks();

} else {

connect(networkId);

}

}

}

}

}

Frameworks\base\wifi\java\android\net\wifi\WifiManager.java

public intupdateNetwork(WifiConfiguration config) {

if(config == null || config.networkId < 0) {

return -1;

}

return addOrUpdateNetwork(config);

}

private intaddOrUpdateNetwork(WifiConfiguration config) {

try {

return mService.addOrUpdateNetwork(config);

} catch (RemoteException e) {

return -1;

}

}

WifiService.addOrUpdateNetwork()通過調(diào)用mWifiStateTracker.setNetworkVariable()將連接命令發(fā)送到Wpa_supplicant。

(4)獲取IP地址

當(dāng)連接到supplicant后,WifiMonitor就會通知WifiStateTracker。

Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java

Public void Run(){

if (connectToSupplicant()) {

// Send a message indicatingthat it is now possible to send commands

// to the supplicant

mWifiStateTracker.notifySupplicantConnection();

} else {

mWifiStateTracker.notifySupplicantLost();

return;

}

……

}

WifiStateTracker發(fā)送EVENT_SUPPLICANT_CONNECTION消息到消息隊列,這個消息有自己的handlermessage()函數(shù)處理,它會啟動一個DHCP線程,而這個線程會一直等待一個消息事件,來啟動DHCP協(xié)議分配IP地址。

frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java

void notifySupplicantConnection() {

sendEmptyMessage(EVENT_SUPPLICANT_CONNECTION);

}

public void handleMessage(Message msg) {

Intent intent;

switch (msg.what) {

case EVENT_SUPPLICANT_CONNECTION:

……

HandlerThread dhcpThread = newHandlerThread("DHCP Handler Thread");

dhcpThread.start();

mDhcpTarget = newDhcpHandler(dhcpThread.getLooper(), this);

……

……

}

當(dāng)Wpa_supplicant連接到AP后,它會發(fā)送一個消息給上層來通知連接成功,WifiMonitor會接受到這個消息并上報給WifiStateTracker。

Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java

void handleEvent(int event, String remainder) {

switch (event) {

case DISCONNECTED:

handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED,remainder);

break;

case CONNECTED:

handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED,remainder);

break;

……

}

private voidhandleNetworkStateChange(NetworkInfo.DetailedState newState, String data) {

StringBSSID = null;

intnetworkId = -1;

if(newState == NetworkInfo.DetailedState.CONNECTED) {

Matcher match = mConnectedEventPattern.matcher(data);

if(!match.find()) {

if (Config.LOGD) Log.d(TAG, "Could not find BSSID in CONNECTEDevent string");

}else {

BSSID = match.group(1);

try {

networkId = Integer.parseInt(match.group(2));

} catch (NumberFormatException e) {

networkId = -1;

}

}

}

mWifiStateTracker.notifyStateChange(newState,BSSID, networkId);

}

void notifyStateChange(DetailedState newState, StringBSSID, int networkId) {

Messagemsg = Message.obtain(

this, EVENT_NETWORK_STATE_CHANGED,

newNetworkStateChangeResult(newState, BSSID, networkId));

msg.sendToTarget();

}

caseEVENT_NETWORK_STATE_CHANGED:

……

configureInterface();

……

private void configureInterface() {

checkPollTimer();

mLastSignalLevel = -1;

if(!mUseStaticIp) { //使用DHCP線程動態(tài)IP

if(!mHaveIpAddress && !mObtainingIpAddress) {

mObtainingIpAddress = true;

//發(fā)送啟動DHCP線程獲取IP

mDhcpTarget.sendEmptyMessage(EVENT_DHCP_START);

}

} else { //使用靜態(tài)IP,IP信息從mDhcpInfo中獲取

intevent;

if(NetworkUtils.configureInterface(mInterfaceName, mDhcpInfo)) {

mHaveIpAddress = true;

event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;

if (LOCAL_LOGD) Log.v(TAG, "Static IP configurationsucceeded");

}else {

mHaveIpAddress = false;

event = EVENT_INTERFACE_CONFIGURATION_FAILED;

if (LOCAL_LOGD) Log.v(TAG, "Static IP configuration failed");

}

sendEmptyMessage(event); //發(fā)送IP獲得成功消息事件

}

}

DhcpThread獲取EVENT_DHCP_START消息事件后,調(diào)用handleMessage()函數(shù),啟動DHCP獲取IP地址的服務(wù)。[!--empirenews.page--]

public void handleMessage(Message msg) {

intevent;

switch (msg.what) {

case EVENT_DHCP_START:

……

Log.d(TAG, "DhcpHandler: DHCP requeststarted");

//啟動一個DHCPclient的精靈進程,為mInterfaceName請求分配一個IP地//址

if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {

event= EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;

if(LOCAL_LOGD) Log.v(TAG, "DhcpHandler: DHCP request succeeded");

} else {

event= EVENT_INTERFACE_CONFIGURATION_FAILED;

Log.i(TAG,"DhcpHandler: DHCP request failed: " +

NetworkUtils.getDhcpError());

}

……

}

這 里調(diào)用了一個NetworkUtils.runDhcp()函數(shù),NetworkUtils類是一個網(wǎng)絡(luò)服務(wù)的輔助類,它主要定義了一些本地接口,這些接 口會通過他們的JNI層android_net_NetUtils.cpp文件和DHCP client通信,并獲取IP地址。

至此,IP地址獲取完畢,Wifi啟動流程結(jié)束。

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉