1.背景
客户开发的第三方APP统计各个应用的数据流量,发现使用TraffficStats.getUidRxBytes(uid)和TraffficStats.getUidRxBytes(uid)结果值一直是0.
2.分析
首先看一下源码TraffficStats.java,位置/frameworks/base/core/java/android/net/
/**
* Return number of bytes received by the given UID since device boot.
* Counts packets across all network interfaces, and always increases
* monotonically since device boot. Statistics are measured at the network
* layer, so they include both TCP and UDP usage.
* <p>
* Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
* {@link #UNSUPPORTED} on devices where statistics aren't available.
* <p>
* Starting in {@link android.os.Build.VERSION_CODES#N} this will only
* report traffic statistics for the calling UID. It will return
* {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access
* historical network statistics belonging to other UIDs, use
* {@link NetworkStatsManager}.
*
* @see android.os.Process#myUid()
* @see android.content.pm.ApplicationInfo#uid
*/
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;
}
}
- 1
先看注释:To access historical network statistics belonging to other UIDs, use NetworkStatsManager
意思是如果要获取其他的UID的statistics,请时使用NetworkStatsManager(注意这是一个大坑,跟着注释就走到了NetworkStatsManager里面去了,于是给客户回应TraffficStats.getUidRxBytes只能获取自己应用本身的流量数据,结果。。。。)
- 2
在分析代码,代码很简单。
a.先获取当前进程(UID),不是系统进程或者当前进程和要查询的UID不同的话就直接返回 UNSUPPORTED(-1)了,否则进进入nativeGetUidStat()方法.
b. nativeGetUidStat方法在android_net_TrafficStats.cpp中,此文件在frameworks\base\core\jni\下 ,为方便分析,我直接在代码里面加注释
static int parseUidStats(const uint32_t uid, struct Stats* stats) {
FILE *fp = fopen(QTAGUID_UID_STATS, "r");
//QTAGUID_UID_STATS="/proc/net/xt_qtaguid/stats"
//这里直接打开/proc/net/xt_qtaguid/stats文件
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) {
//通过UID、tag过滤读的结果值
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文件,然后根据条件过滤输出结果,那么这个文件到底有什么了?
我们看到第4、6、8列数据分别是uid,rx和tx,这里面的数据正是我们想要的
- 3
代码分析完了,整个代码很清晰明了(除了那个注释),但这么简单的功能客户为什么说有问题了,起初怀疑客户应用不是系统应用,在TrafficStats.java中被直接拦截了,返回了UNSUPPORTED,后面看UNSUPPORTED结果值是-1而不是0,不信邪的我又写了一个系统app(什么是系统APP自行度娘)亲自测试,结果真的和客户说的一样,全部为0。难道真的如google所说需要NetworkStatsManager获取结果。
- 4
不到长城非好汉,不撞南墙誓不回,我辈程序员的精神所在,在被这个问题折磨了半天,猛然间想起Android/linux下对文件的权限管理很严格,stats是一个文件,肯定存在权限管理的,于是 ls -ll
stats文件是属于net_bw_stats组,那只有添加net_bw_stats组的应用才能读取此文件。事情终于有一点转机了,那么我们怎么使我们的应用有net_bw_stats组的权限了,请看这篇博客这里写链接内容,写很详细,具体就是在/frameworks/base/data/etc/platform.xml 找到net_bw_stats组的权限
<!-- Group that can read detailed network usage statistics -->
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY">
<group gid="net_bw_stats" />
</permission>
然后在manifest.xml文件中添加对应的permission权限,验证ok。
总结:这个问题分析完了,感觉很简单,就是缺个权限,但是google API上都没有说明,就需要自己去发现了,也说明API文档不是万能的(又在黑google了)。