概述
公司开发的产品增加了一个新的需求,需要把本应用所消耗的流量分类上传到服务端,进行数据统计,方便以后大数据处理。
该需求有个硬性条件,必须把流量的统计细分成 net 和 wlan 。
核心
- TrafficStats 系统封装的流量统计类,适配版本 Android 2.2 以上,主要用于流量统计的方法有:
- getMobileRxBytes -- 获得 mobile 的接受流量
- getMobileTxBytes -- 获得 mobile 的发送流量
- getTotalRxBytes -- 获得总共的接受流量( mobile + wifi )
- getTotalTxBytes -- 获得总共的发送流量( mobile + wifi )
- getUidRxBytes -- 获得指定 uid 的接受流量( mobile + wifi )
- getUidTxBytes -- 获得指定 uid 的发送流量( mobile + wifi )
- NetworkStatsManager 一个强大的流量统计工具,适配版本 android 6.0,需要系统权限才能使用,不实用
问题
需求是统计本应用的流量,且细分成 net 和 wlan
但是 TrafficStats 提供的方法仅仅只有 getUidRxBytes 和 getUidTxBytes ,都是获取全部的流量,无法细分
这个问题怎么解决呢?只有去看 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;
}
}
可以看到在获取流量时,其实是在调用 nativeGetUidStat(uid, TYPE_RX_BYTES),这个方法是系统 Native 方法,其实 TrafficStats 这个类所有的方法都是在调用三个 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 源码进行查看了,在源码中找到
在 android_net_TrafficStats.cpp 查看 gMethods 内 nativeGetUidStat 在源码中对应的方法为 getUidStat
static const JNINativeMethod gMethods[] = {
{"nativeGetTotalStat", "(I)J", (void*) getTotalStat},
{"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*) getIfaceStat},
{"nativeGetUidStat", "(II)J", (void*) getUidStat},
};
查看 getUidStat 的源码,其实通过 parseUidStats(uid, &stats) 解析数据,通过 getStatsType(&stats, (StatsType) type) 读取数据
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),解析数据源码:
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;
}
可以看到源码其实就是打开 "/proc/net/xt_qtaguid/stats" 按照你需要的字段进行读取数据,看来最重要的就是这个 "/proc/net/xt_qtaguid/stats" 文件了,那就在读取一下这个文件看一下内容是什么,直接读取文件内容如下:
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
可以看到,第一行数据是表头数据,剩下的就是对应的流量数据,还可以通过 iface 进行区分 net 还是 wlan ,这正是我们需要的数据。
总结
通过上面查看源码后,总结形成工具类:
转载注明出处: http://www.jianshu.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;
/**
* 流量统计
*
* @author - lwc
* @date - 2017/5/27
* @note -
* -------------------------------------------------------------------------------------------------
* @modified -
* @date -
* @note -
*/
public class TrafficStatsUtil {
/**
* 根据应用id,统计的接受字节数,包含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;
}
/**
* 获取本应用的接受字节数,包含Mobile和WiFi等
*
* @return 上传及下载流量,单位Kb
*/
public static long getLocalBytes(Context context) {
//本应用的进程id
int uid = context.getApplicationInfo().uid;
return getBytesWithUid(uid);
}
/**
* 获取本应用通过Mobile的流量
*
* @return 上传及下载流量,单位Kb
*/
public static long getLocalBytesWithNet(Context context) {
//本应用的进程id
int uid = context.getApplicationInfo().uid;
File file = new File("/proc/net/xt_qtaguid/stats");
List 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;
}
/**
* 获取本应用通过Wlan的流量
*
* @return 上传及下载流量,单位Kb
*/
public static long getLocalBytesWithWlan(Context context) {
//本应用的进程id
int uid = context.getApplicationInfo().uid;
File file = new File("/proc/net/xt_qtaguid/stats");
List 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;
}
/**
* 获取总的接受字节数,包含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连接收到的字节总数,不包含WiFi
*
* @return 上传及下载流量,单位Kb
*/
public static long getMobileBytes() {
//数据的下载流量
long mobileRxBytes = TrafficStats.getMobileRxBytes();
//数据的上传你流量
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 readFile2List(File file, String charsetName) {
if (file == null) {
return null;
}
BufferedReader reader = null;
try {
String line;
int curLine = 1;
List 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();
}
}
}
}