Android流量統計
阿新 • • 發佈:2018-06-14
進程 整體 logs 接受 發送 api alt net code
項目中需要對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獲取uidadb 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流量統計