概述
公司開發(fā)的產(chǎn)品增加了一個(gè)新的需求,需要把本應(yīng)用所消耗的流量分類上傳到服務(wù)端醋奠,進(jìn)行數(shù)據(jù)統(tǒng)計(jì)贱纠,方便以后大數(shù)據(jù)處理莫换。
該需求有個(gè)硬性條件,必須把流量的統(tǒng)計(jì)細(xì)分成 net 和 wlan 骤铃。
核心
- TrafficStats 系統(tǒng)封裝的流量統(tǒng)計(jì)類拉岁,適配版本 Android 2.2 以上,主要用于流量統(tǒng)計(jì)的方法有:
- getMobileRxBytes -- 獲得 mobile 的接受流量
- getMobileTxBytes -- 獲得 mobile 的發(fā)送流量
- getTotalRxBytes -- 獲得總共的接受流量( mobile + wifi )
- getTotalTxBytes -- 獲得總共的發(fā)送流量( mobile + wifi )
- getUidRxBytes -- 獲得指定 uid 的接受流量( mobile + wifi )
- getUidTxBytes -- 獲得指定 uid 的發(fā)送流量( mobile + wifi )
- NetworkStatsManager 一個(gè)強(qiáng)大的流量統(tǒng)計(jì)工具惰爬,適配版本 android 6.0喊暖,需要系統(tǒng)權(quán)限才能使用,不實(shí)用
問題
需求是統(tǒng)計(jì)本應(yīng)用的流量撕瞧,且細(xì)分成 net 和 wlan
但是 TrafficStats 提供的方法僅僅只有 getUidRxBytes 和 getUidTxBytes 陵叽,都是獲取全部的流量,無法細(xì)分
這個(gè)問題怎么解決呢丛版?只有去看 TrafficStats 源碼是怎么工作的
查看源碼
public static long getUidRxBytes(int uid) {
// This isn't actually enforcing any security; it just returns the
// unsupported value. The real filtering is done at the kernel level.
final int callingUid = android.os.Process.myUid();
if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
return nativeGetUidStat(uid, TYPE_RX_BYTES);
} else {
return UNSUPPORTED;
}
}
可以看到在獲取流量時(shí)巩掺,其實(shí)是在調(diào)用 nativeGetUidStat(uid, TYPE_RX_BYTES),這個(gè)方法是系統(tǒng) Native 方法页畦,其實(shí) TrafficStats 這個(gè)類所有的方法都是在調(diào)用三個(gè) Native 方法:
private static native long nativeGetTotalStat(int type);
private static native long nativeGetIfaceStat(String iface, int type);
private static native long nativeGetUidStat(int uid, int type);
Native 方法就需要去下載 Android 源碼進(jìn)行查看了胖替,在源碼中找到
![image](https://github.com/lwcye/Demo/blob/master/image/2.png?raw=true)
在 android_net_TrafficStats.cpp 查看 gMethods 內(nèi) nativeGetUidStat 在源碼中對(duì)應(yīng)的方法為 getUidStat
static const JNINativeMethod gMethods[] = {
{"nativeGetTotalStat", "(I)J", (void*) getTotalStat},
{"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*) getIfaceStat},
{"nativeGetUidStat", "(II)J", (void*) getUidStat},
};
查看 getUidStat 的源碼,其實(shí)通過 parseUidStats(uid, &stats) 解析數(shù)據(jù)豫缨,通過 getStatsType(&stats, (StatsType) type) 讀取數(shù)據(jù)
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;
}
}
parseUidStats(uid, &stats)独令,解析數(shù)據(jù)源碼:
static const char* QTAGUID_UID_STATS = "/proc/net/xt_qtaguid/stats";
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 (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;
}
可以看到源碼其實(shí)就是打開 "/proc/net/xt_qtaguid/stats" 按照你需要的字段進(jìn)行讀取數(shù)據(jù),看來最重要的就是這個(gè) "/proc/net/xt_qtaguid/stats" 文件了好芭,那就在讀取一下這個(gè)文件看一下內(nèi)容是什么燃箭,直接讀取文件內(nèi)容如下:
idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
2 rmnet_data0 0x0 10224 0 98175 208 33887 266 98175 208 0 0 0 0 33887 266 0 0 0 0
3 rmnet_data0 0x0 10224 1 78165 148 29214 143 78165 148 0 0 0 0 29214 143 0 0 0 0
4 wlan0 0x0 10224 0 0 0 1560 26 0 0 0 0 0 0 1560 26 0 0 0 0,
5 wlan0 0x0 10224 1 573000 629 56692 499 573000 629 0 0 0 0 56692 499 0 0 0 0
可以看到,第一行數(shù)據(jù)是表頭數(shù)據(jù)舍败,剩下的就是對(duì)應(yīng)的流量數(shù)據(jù)招狸,還可以通過 iface 進(jìn)行區(qū)分 net 還是 wlan 敬拓,這正是我們需要的數(shù)據(jù)。
總結(jié)
通過上面查看源碼后瓢颅,總結(jié)形成工具類:
轉(zhuǎn)載注明出處: http://www.reibang.com/p/ffe35b73be45
package com.cqmc.mulitactivity;
import android.content.Context;
import android.net.TrafficStats;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
/**
* <p>流量統(tǒng)計(jì)</p><br>
*
* @author - lwc
* @date - 2017/5/27
* @note -
* -------------------------------------------------------------------------------------------------
* @modified -
* @date -
* @note -
*/
public class TrafficStatsUtil {
/**
* 根據(jù)應(yīng)用id,統(tǒng)計(jì)的接受字節(jié)數(shù)恩尾,包含Mobile和WiFi等
*
* @return 上傳及下載流量,單位Kb
*/
public static long getBytesWithUid(int uid) {
//下載流量
long rxBytes = TrafficStats.getUidRxBytes(uid);
//上傳流量
long txBytes = TrafficStats.getUidTxBytes(uid);
long result = rxBytes + txBytes;
return result > 0 ? result / 1024 : 0;
}
/**
* 獲取本應(yīng)用的接受字節(jié)數(shù)挽懦,包含Mobile和WiFi等
*
* @return 上傳及下載流量翰意,單位Kb
*/
public static long getLocalBytes(Context context) {
//本應(yīng)用的進(jìn)程id
int uid = context.getApplicationInfo().uid;
return getBytesWithUid(uid);
}
/**
* 獲取本應(yīng)用通過Mobile的流量
*
* @return 上傳及下載流量,單位Kb
*/
public static long getLocalBytesWithNet(Context context) {
//本應(yīng)用的進(jìn)程id
int uid = context.getApplicationInfo().uid;
File file = new File("/proc/net/xt_qtaguid/stats");
List<String> list = readFile2List(file, "utf-8");
long rxBytesNet = 0L;
long txBytesNet = 0L;
for (String stat : list) {
String[] split = stat.split(" ");
try {
int idx = Integer.parseInt(split[3]);
if (uid == idx) {
long rx_bytes = Long.parseLong(split[5]);
long tx_bytes = Long.parseLong(split[7]);
if (split[1].startsWith("rmnet_data")) {
rxBytesNet += rx_bytes;
txBytesNet += tx_bytes;
}
}
} catch (NumberFormatException ignored) {
}
}
long result = rxBytesNet + txBytesNet;
return result > 0 ? result / 1024 : 0;
}
/**
* 獲取本應(yīng)用通過Wlan的流量
*
* @return 上傳及下載流量信柿,單位Kb
*/
public static long getLocalBytesWithWlan(Context context) {
//本應(yīng)用的進(jìn)程id
int uid = context.getApplicationInfo().uid;
File file = new File("/proc/net/xt_qtaguid/stats");
List<String> list = readFile2List(file, "utf-8");
long rxBytesWlan = 0L;
long txBytesWlan = 0L;
for (String stat : list) {
String[] split = stat.split(" ");
try {
int idx = Integer.parseInt(split[3]);
if (uid == idx) {
long rx_bytes = Long.parseLong(split[5]);
long tx_bytes = Long.parseLong(split[7]);
if (split[1].startsWith("wlan")) {
rxBytesWlan += rx_bytes;
txBytesWlan += tx_bytes;
}
}
} catch (NumberFormatException ignored) {
}
}
long result = rxBytesWlan + txBytesWlan;
return result > 0 ? result / 1024 : 0;
}
/**
* 獲取總的接受字節(jié)數(shù)冀偶,包含Mobile和WiFi等
*
* @return 上傳及下載流量,單位Kb
*/
public static long getTotalBytes() {
//總的下載流量
long totalRxBytes = TrafficStats.getTotalRxBytes();
//總的上傳你流量
long totalTxBytes = TrafficStats.getTotalTxBytes();
long result = totalRxBytes + totalTxBytes;
return result > 0 ? result / 1024 : 0;
}
/**
* 獲取通過Mobile連接收到的字節(jié)總數(shù)渔嚷,不包含WiFi
*
* @return 上傳及下載流量进鸠,單位Kb
*/
public static long getMobileBytes() {
//數(shù)據(jù)的下載流量
long mobileRxBytes = TrafficStats.getMobileRxBytes();
//數(shù)據(jù)的上傳你流量
long mobileTxBytes = TrafficStats.getMobileTxBytes();
long result = mobileRxBytes + mobileTxBytes;
return result > 0 ? result / 1024 : 0;
}
/**
* 指定編碼按行讀取文件到鏈表中
*
* @param file 文件
* @param charsetName 編碼格式
* @return 包含從start行到end行的list
*/
public static List<String> readFile2List(File file, String charsetName) {
if (file == null) {
return null;
}
BufferedReader reader = null;
try {
String line;
int curLine = 1;
List<String> list = new ArrayList<>();
if ((charsetName == null || charsetName.trim().length() == 0)) {
reader = new BufferedReader(new FileReader(file));
} else {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), charsetName));
}
while ((line = reader.readLine()) != null) {
if (0 <= curLine) {
list.add(line);
}
++curLine;
}
return list;
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}