標籤:擷取 使用 現在 com log turn size 如何使用 adb shell
項目中需要對Android裝置進行流量統計來進行資費結算,所以對Android裝置流量統計進行了一些調研。發現流量統計主流上有兩種方式
- 使用系統統計類TrafficStats擷取
- 通過系統檔案解析讀取
TrafficStats
- static long getMobileRxBytes() //擷取通過Mobile串連收到的位元組總數,不包含WiFi
- static long getMobileRxPackets() //擷取Mobile串連收到的資料包總數
- static long getMobileTxBytes() //Mobile發送的總位元組數
- static long getMobileTxPackets() //Mobile發送的總資料包數
- static long getTotalRxBytes() //擷取總的接受位元組數,包含Mobile和WiFi等
- static long getTotalRxPackets() //總的接受資料包數,包含Mobile和WiFi等
- static long getTotalTxBytes() //總的發送位元組數,包含Mobile和WiFi等
- static long getTotalTxPackets() //發送的總資料包數,包含Mobile和WiFi等
- static long getUidRxBytes(int uid) //擷取某個網路UID的接受位元組數
- static long getUidTxBytes(int uid) //擷取某個網路UID的發送位元組數
這些方法 擷取到的流量資料都是從手機上次開機到當前的流量使用方式,這個沒關係,我們可以監聽手機的開機和關機。關機的時候(或者每間隔一段時間) 將資料都儲存起來,下次開機後通過以上方法擷取到的資料在疊加上.
但是這些方法裡面,只能我發現在這些方法裡,缺少我最需要的那個方法,我想擷取某個app接受和發送的2G/3G流量,不包含wifi,這些API無法滿足該需求。
於是乎,便從系統檔案的解析上著手吧
/proc/net/xt_qtaguid/stats格式
/proc/net/xt_qtaguid/stats內包含著各個app使用流量的情況,先來簡單看一下這個檔案的內容
這輸出內容整體是一個表結構,首先我們需要看一下表中各欄欄位代表什麼
- idx : 序號
- iface : 代表流量類型(rmnet表示2G/3G, wlan表示Wifi流量,lo表示本地流量)
- acct_tag_hex :線程標記(用於區分單個應用內不同模組/線程的流量)
- uid_tag_int : 應用uid,據此判斷是否是某應用統計的流量資料
- cnt_set : 應用前後標誌位:1:前台, 0:後台
- rx_btyes : receive bytes 接受到的位元組數
- rx_packets : 接收到的任務包數
- tx_bytes : transmit bytes 發送的總位元組數
- tx_packets : 發送的總包數
- rx_tcp_types : 接收到的tcp位元組數
- rx_tcp_packets : 接收到的tcp包數
- rx_udp_bytes : 接收到的udp位元組數
- rx_udp_packets : 接收到的udp包數
- rx_other_bytes : 接收到的其他類型位元組數
- rx_other_packets : 接收到的其他類型包數
- tx_tcp_bytes : 發送的tcp位元組數
- tx_tcp_packets : 發送的tcp包數
- tx_udp_bytes : 發送的udp位元組數
- tx_udp_packets : 發送的udp包數
- tx_other_bytes : 發送的其他類型位元組數
- tx_other_packets : 發送的其他類型包數
可以看到,用檔案解析的方式擷取流量可以獲得比直接使用acct_tag_hex要豐富的多的資訊,包括區分WIFI和移動網路,區分應用內不同模組功能流量,前後台流量等。
使用shell命令來查看進程流量
接下來說明下uid_tag_int這個欄位
pid:Processid(進程id)
擷取pid的shell命令:
grep com.car.tencent
uid:每個應用程式一個uid(如果一個app擁有兩個進程,則有兩個不同pid,但兩個pid對應的uid是相同的)
根據pid擷取uid
adb shell cat /proc/1643/status
根據uid擷取流量
adb shell cat /proc/net/xt_qtaguid/stats | grep uid
如何使用代碼解析/proc/net/xt_qtaguid/stats檔案
static const char *QTAGUID_UID_STATS = "/proc/net/xt_qtaguid/stats";struct Stats { uint64_t rxBytes; uint64_t rxPackets; uint64_t txBytes; uint64_t txPackets; uint64_t tcpRxPackets; uint64_t tcpTxPackets;};static int parseUidStats(const uint32_t uid, struct Stats *stats) { FILE *fp = fopen(QTAGUID_UID_STATS, "r"); if (fp == NULL) { return -1; } char buffer[384]; char iface[32]; uint32_t idx, cur_uid, set; uint64_t tag, rxBytes, rxPackets, txBytes, txPackets; while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (sscanf(buffer, "%" SCNu32 " %31s 0x%" SCNx64 " %u %u %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64 "", &idx, iface, &tag, &cur_uid, &set, &rxBytes, &rxPackets, &txBytes, &txPackets) == 9) { //繞過本地迴環地址 if (strcmp(iface, "lo") && uid == cur_uid && tag == 0L) { stats->rxBytes += rxBytes; stats->rxPackets += rxPackets; stats->txBytes += txBytes; stats->txPackets += txPackets; } } } if (fclose(fp) != 0) { return -1; } return 0;}static jlong getUidStat(JNIEnv *env, jclass clazz, jint uid, jint type) { struct Stats stats; memset(&stats, 0, sizeof(Stats)); if (parseUidStats(uid, &stats) == 0) { return getStatsType(&stats, (StatsType) type); } else { return UNKNOWN; }}
這段代碼裡面,需要注意的點是如何忽略本地迴環流量
if (strcmp(iface, "lo") && uid == cur_uid && tag == 0L) {
因為iface是網路類型,一共只有三個類型,rmnet表示2G/3G, wlan表示Wifi流量,lo表示本地流量。所以這裡直接使用了字串比對的方式。
擴充
- /proc/net/xt_qtaguid/iface_stat_fmt:該檔案中已經按照類型統計出了不同類型網路資料的總數,因此擷取當前裝置的總接受位元組數等裝置級統計資訊,直接分析該檔案,能最快擷取結果
- /proc/net/xt_qtaguid/stats:該檔案中則是按照uid進行了分類,適合於做更細力度(應用級/線程級)的分析
附錄:
- 部落格原地址:https://chchaooo.github.io/2018/06/13/Android%E6%B5%81%E9%87%8F%E7%BB%9F%E8%AE%A1/
- 如果有問題交流請移步:https://www.cnblogs.com/weilf/p/9180373.html
參考資料:
- Android流量最佳化(一):模組化流量統計
- Android效能測試之網路流量
Android流量統計