目录
1.应用统计效果
2.介绍Android官方6.0之后推出的API网络太类 NetworkStatsManager:
3.统计查询方法(UID单个应用,所有应用)
4.统计过程中的发现的坑以及解决方案
5.统计结果与系统统计对比



1.应用统计效果

废话不多说,先上菜:


在这里插入图片描述




2.介绍Android官方6.0之后推出的API网络太类 NetworkStatsManager:
先看一下 android官方的 NetworkStatsManager API文档说明:

Android Developer NetworkStatsManager

NetworkStatsManager类 提供对网络使用历史和统计数据的访问,分为Summary queries(摘要查询)和 History queries(历史查询)

Summary queries(摘要查询)
这些查询汇总了整个时间间隔内的网络使用情况。因此,对于特定的密钥、状态、计量和漫游组合,只有一个桶。如果是用户范围和设备范围的摘要,则返回一个包含总网络使用情况的存储桶。

History queries(历史查询)
这些查询不会随时间聚合,但会聚合状态、计量和漫游。因此,一个特定的键可以有多个桶。


查询方法:



	查询网络使用统计详情。结果过滤为仅包含属于呼叫用户的 uid(包含手机上每个应用的uid,统计所有应用总流量可用)
	queryDetails(int networkType, String subscriberId, long startTime, long endTime)
	
	查询指定 uid 的网络使用统计详细信息。(可查询单个应用流量)
	queryDetailsForUid(int networkType, String subscriberId, long startTime, long endTime, int uid)
	
	查询指定 uid 和标签的网络使用统计详细信息。
	queryDetailsForUidTag(int networkType, String subscriberId, long startTime, long endTime, int uid, int tag)
	
		查询给定 uid、标签和状态的网络使用统计详细信息。仅可用于属于主叫用户的 uid。结果不会随时间聚合。
	queryDetailsForUidTagState(int networkType, String subscriberId, long startTime, long endTime, int uid, int tag, int state)
	
	查询网络使用统计摘要。(查询多个应用流量统计)
	querySummary(int networkType, String subscriberId, long startTime, long endTime)
	
	查询网络使用统计摘要。结果是整个设备的汇总数据使用情况。结果是随着时间、状态、uid、标签、计量和漫游聚合的单个存储querySummaryForDevice(int networkType, String subscriberId, long startTime, long endTime)
	
	查询网络使用统计摘要。结果是属于调用用户的所有 uid 的汇总数据使用情况。结果是随着时间、状态和 uid 聚合的单个 Bucket。
	querySummaryForUser(int networkType, String subscriberId, long startTime, long endTime)






3.统计查询方法(UID单个应用,所有应用)

a. 需求:统计单个应用流量
可以从上面查询方法中选择 queryDetailsForUid(int networkType, String subscriberId, long startTime, long endTime, int uid)方法最符合需求,除了该方法之外还有querySummary()、queryDetails()也可以实现该需求。

queryDetailsForUid(int networkType, String subscriberId, long startTime, long endTime, int uid)

参数描述:

  • 1.networkType 查询网络类型 (ConnectivityManager.TYPE_WIFI,ConnectivityManager.TYPE_MOBILE)
    2.subscriberId 设备唯一id(android 10及以后设备 获取不了,可不传)
    3.startTime 查询指定时间段 开始时间戳
    4.endTime 查询指定时间段 结束时间
    5.uid 查询设备的Uid

    查询方法如下:

    queryDetailsForUid方法

	//统计工具类
    public static class Usage {
        long totalRxBytes;//总数据 下载字节
        long totalTxBytes;//总数据 上传字节
        long wifiTotalData;//wifi总流量数据
        long mobleTotalData;//移动总流量数据
        long mobleRxBytes;//移动 下载字节
        long mobleTxBytes;//移动 上传字节
        long wifiRxBytes;//wifi 下载字节
        long wifiTxBytes;//wifi 上传字节
        String uid;//包名
    }


 	/**
   * 获取uid (单个)应用流量
     * @param context   上下文
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @param uid       应用uid
     */
    public static Usage getUsageBytesByUid(Context context, long startTime, long endTime, int uid) {
        Usage usage = new Usage();
        usage.totalRxBytes = 0;
        usage.totalTxBytes = 0;
        usage.mobleRxBytes = 0;
        usage.mobleTxBytes = 0;
        usage.wifiTxBytes = 0;
        usage.wifiTxBytes = 0;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            NetworkStatsManager nsm = (NetworkStatsManager) context.getSystemService(NETWORK_STATS_SERVICE);
            assert nsm != null;
            NetworkStats wifi = nsm.queryDetailsForUid(ConnectivityManager.TYPE_WIFI, null, startTime, endTime, uid);
            NetworkStats moble = nsm.queryDetailsForUid(ConnectivityManager.TYPE_MOBILE, null, startTime, endTime, uid);
            do {
                NetworkStats.Bucket bucket = new NetworkStats.Bucket();
                wifi.getNextBucket(bucket);
                usage.wifiRxBytes += bucket.getRxBytes();
                usage.wifiTxBytes += bucket.getTxBytes();
            } while (wifi.hasNextBucket());
            do {
                NetworkStats.Bucket bucket = new NetworkStats.Bucket();
                moble.getNextBucket(bucket);
                usage.mobleRxBytes += bucket.getRxBytes();
                usage.mobleTxBytes += bucket.getTxBytes();
            } while (moble.hasNextBucket());
            usage.wifiTotalData = usage.wifiRxBytes + usage.wifiTxBytes;
            usage.mobleTotalData = usage.mobleRxBytes + usage.mobleTxBytes;
        }
        return usage;
    }
    




querySummary方法查询统计


    /**
     * 获取单个应用流量
     * @param context    上下文
     * @param startTime  开始时间
     * @param endTime    结束时间
     * @param uid        统计应用uid
     */
    public static Usage getApplicationQuerySummary(Context context, long startTime, long endTime,int uid) {
        usage = new Usage();
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            NetworkStatsManager nsm = (NetworkStatsManager) context.getSystemService(NETWORK_STATS_SERVICE);
            assert nsm != null;
            try {
                NetworkStats wifi = nsm.querySummary(ConnectivityManager.TYPE_WIFI, null, startTime, endTime);
                NetworkStats mobile = nsm.querySummary(ConnectivityManager.TYPE_MOBILE, null, startTime, endTime);
                do {
                    NetworkStats.Bucket bucket = new NetworkStats.Bucket();
                    wifi.getNextBucket(bucket);
                    if(bucket.getUid() == uid){
                        usage.wifiRxBytes += bucket.getRxBytes();
                        usage.wifiTxBytes += bucket.getTxBytes();
                    }
                } while (wifi.hasNextBucket());

                do {
                    NetworkStats.Bucket bucket = new NetworkStats.Bucket();
                    mobile.getNextBucket(bucket);
                    if(bucket.getUid() == uid) {
                        usage.mobleRxBytes += bucket.getRxBytes();
                        usage.mobleTxBytes += bucket.getTxBytes();
                    }
                } while (mobile.hasNextBucket());
                usage.wifiTotalData = usage.wifiRxBytes + usage.wifiTxBytes;
                usage.mobleTotalData = usage.mobleRxBytes + usage.mobleTxBytes;
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        return usage;
    }
    



queryDetails 方法

 /**
     * 获取单个应用流量
     * @param context
     * @param startTime
     * @param endTime
     * @param uid
     */
    public static Usage getApplicationQueryDetails(Context context, long startTime, long endTime,int uid) {
        usage = new Usage();
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            NetworkStatsManager nsm = (NetworkStatsManager) context.getSystemService(NETWORK_STATS_SERVICE);
            assert nsm != null;
            try {
                NetworkStats wifi = nsm.queryDetails(ConnectivityManager.TYPE_WIFI, null, startTime, endTime);
                NetworkStats mobile = nsm.queryDetails(ConnectivityManager.TYPE_MOBILE, null, startTime, endTime);
                do {
                    NetworkStats.Bucket bucket = new NetworkStats.Bucket();
                    wifi.getNextBucket(bucket);
                    if(bucket.getUid() == uid) {
                        usage.wifiRxBytes += bucket.getRxBytes();
                        usage.wifiTxBytes += bucket.getTxBytes();
                    }
                } while (wifi.hasNextBucket());
                do {
                    NetworkStats.Bucket bucket = new NetworkStats.Bucket();
                    mobile.getNextBucket(bucket);
                    if(bucket.getUid() == uid) {
                        usage.mobleRxBytes += bucket.getRxBytes();
                        usage.mobleTxBytes += bucket.getTxBytes();
                    }
                } while (mobile.hasNextBucket());
                usage.wifiTotalData = usage.wifiRxBytes + usage.wifiTxBytes;
                usage.mobleTotalData = usage.mobleRxBytes + usage.mobleTxBytes;
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        return usage;
    }
    




b. 统计手机使用流量(所有应用)

可以从上面查询方法中可以看出 querySummaryForDevice(int networkType, String subscriberId, long startTime, long endTime, int uid)方法最符合需求,除了该方法之外还有querySummary()、queryDetails()querySummaryForUser()也可以实现该需求。

querySummaryForDevice(int networkType, String subscriberId, long startTime, long endTime)

参数描述:

  • 1.networkType 查询网络类型 (ConnectivityManager.TYPE_WIFI,ConnectivityManager.TYPE_MOBILE)
    2.subscriberId 设备唯一id(android 10及以后设备 获取不了,可不传)
    3.startTime 查询指定时间段 开始时间戳
    4.endTime 查询指定时间段 结束时间


querySummaryForUser 方法 统计


    /**
     * 获取所有应用流量
     *
     * @param context
     * @param startTime 开始时间
     * @param endTime   结束时间
     */
    public static Usage getUsageByUidFromSummaryTotal(Context context, long startTime, long endTime) {
        usage = new Usage();
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            NetworkStatsManager nsm = (NetworkStatsManager) context.getSystemService(NETWORK_STATS_SERVICE);
            assert nsm != null;
            try{
                //querySummaryForUser   整个设备的汇总数据使用情况。
                NetworkStats.Bucket mobileBucket = nsm.querySummaryForUser(ConnectivityManager.TYPE_MOBILE, null, startTime, endTime);
                usage.mobleTotalData = mobileBucket.getTxBytes() + mobileBucket.getRxBytes();
                usage.mobleTxBytes = mobileBucket.getTxBytes();
                usage.mobleRxBytes = mobileBucket.getRxBytes();

                NetworkStats.Bucket wifiBucket = nsm.querySummaryForUser(ConnectivityManager.TYPE_WIFI, "", startTime, endTime);
                usage.wifiTotalData = wifiBucket.getTxBytes() + wifiBucket.getRxBytes();
                usage.wifiTxBytes = wifiBucket.getTxBytes();
                usage.wifiRxBytes = wifiBucket.getRxBytes();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return usage;
    }
    



此处就不每个方法都展开来写了,querySummary、queryDetails、querySummaryForUser几个方法也可以实现总流量统计,具体方法可以参考单个流量统计。




4.统计过程中的发现的坑以及解决方案

BUG1: 进行querySummaryForUser(int networkType, String subscriberId, long startTime, long endTime) 查询总流量分别为移动和wifi:

调用该方法时传入参数如下:
第一个参数: 移动类型 / wifi类型
第二个参数:传空字符串
第三个参数:今日零点零分的时间戳 如:2022-3-10:00:00:00(毫秒值)
第四个参数:获取当前系统时间戳 System.currentTimeMillis()

此方法指定查询某段时间的总流量使用情况,得到的结果是wifi正常,移动流量却是为0。这就觉得很纳闷了。



解决方法:

思路1:方法问题,先是参数不变替换成其它方法queryDetails、querySummary查询,结果依旧是0

思路2:参数问题,根据之前的结果既然wifi查询没有问题,那就排除时间参数三和四。剩下类型跟subscriberId(设备id)继续查看ConnectivityManager里面是否还有其它类型,以及TYPE_MOBILE类型参数说明,那也排除掉了。
subscriberId (设备id)android 10之后获取不了,其它第三方应用可以统计流量,没理由不行。没折,先去查看源码

在源码找到createTemplate方法里面525行,对subscriberId进行判断是否为null,然后上面传入参数字符串为空代入,不就是查询buildTemplateMobileAll方法嘛。最后subscriberId传入null值查询,结果数据有了。。

在这里插入图片描述


BUG2: 需求是查询今天抖音使用了多少流量,使用queryDetailsForUid(int networkType, String subscriberId, long startTime, long endTim, int uide)方法调用两次 (移动和wifi)。

调用该方法时传入参数如下:
第一个参数:分别为 wifi和移动类型
第二个参数:null
第三个参数:今日零点零分的时间戳 如:2022-3-10:00:00:00(毫秒值)
第四个参数:获取当前系统时间戳 System.currentTimeMillis()
第五个参数:抖音的uid

返回结果居然是0,想了一下,好像今天还没打开过抖音。然后打开抖音 wifi和流量切换着刷了一会,然后在进行查询结果都是0


解决方法

思路1:替换方法,使用querySummary、queryDetails去查询,发现querySummary可以查询到即时流量,queryDetails还是查询结果为0。
思路2:换时间,是否是因为时间长度问题,把时间换成了昨天的就可以了。
在次尝试把结束时间拉长为次日,如:今天3月10号,第四个参数替换为:2022-3-11:00:00:00,结果也是可以正常测试出来。


总结:

bug1的坑刚开始着实没想到,subscriberId是String基础类型默认值一般都是空字符串,谁能想到需要传为值null。所以说还是应该多看源码,看看也不吃亏

bug2 的问题用得少,多试几次或者看下资料就知道了。queryDetails和queryDetailsForUid方法属于历史查询,该方法统计单位按小时统计,具体可以自行测试。而querySummary则是摘要查询,即在指定时间段内的流量(即时查询)。英文翻译成中文也不太友好啊



5.统计结果与系统统计对比

1.测试的手机有小米(Android 12)和vivo (Android 11)、魅族
小米系统自带原生统计基本一致,相差不超过1M
2.统计方法
这两种querySummary系列和queryDetails系列统计结果相差几十兆


下面是参考的几个博客地址:

https://blog.csdn.net/w7849516230/article/details/71705835
https://www.jianshu.com/p/52192441089b/

https://developer.android.com/reference/android/app/usage/NetworkStatsManager#queryDetails(int,%20java.lang.String,%20long,%20long)

应用统计效果Demo下载地址

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐