Android流量监控

Android流量监控

背景:客户反映公司的APP在几部手机上流量超支,需要自证清白,否则要帮客户垫付超出的流量费用。流量超支之后手机早已停机,距今已有半年多,手机的日志文件莫名奇妙丢失,客户一口咬定是程序问题,大家相互扯来扯去。为了避免这个问题就有了这个监控自身程序流量的需求。

流量监控Android2.2之后提供了TrafficStats,可以用于统计手机的流量以及程序的流量。具体参照android develop的介绍

TrafficStats常用方法

查看getUidRxBytes(int uid)的方法签名:

这里写图片描述
明显可以看出API 12之后,添加了此方法,此方法统计uid对应程序接收的流量(包括tcp+udp),在版本JELLY_BEAN_MR2之前可能会返回UNSUPPORTED(constant value -1)。版本N也可能会返回UNSUPPORTED。

关于版本的对应关系可以看下面这张图:
Android平台版本/API/版本名称 对应图

再来看看方法参数 see also:myUid() uid。
myUid():进程的uid,直接通过 android.os.Process.myUid();就可以获取
uid : 是ApplicationInfo的一个成员变量;
获取的方法:

public static int getUid(Context context) {
        try {
            PackageManager packageManager =context.getPackageManager();
            ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
            return applicationInfo.uid;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        return -1;
    }

实验证明myUid() 与 uid的值是一样的。

自此搞清楚了方法的含义与参数的意思之后,想要看自己程序花了多少流量? 看起来似乎很简单嘛。

TrafficStats.getUidRxBytes(uid)+TrafficStats.getUidTxBytes(uid);

方法返回UNSUPPORTED那就是不支持的机型。
但是实际却没有那么简单,getUidRxBytes(uid)统计的是device boot之后到现在的流量。也就是说如果你的手机在此期间开关机若干次,那统计的就是你最近一次开机到现在的流量,这样做很明显不科学。而且你的统计只给一个最终的流量统计,客户无法知道今天这个程序花费了多少流量,这个月这个程序花费了多少流量。

基于以上这些情况。
在cookie(sharedPerference)中存储每天的流量信息。
具体的存储字段 格式如下:
这里写图片描述
这样就把每天的流量按月统计到cookie中了。
具体实现就简单了,在登录之前统计当前的流量intiTraffic保存到cookie中,在退出系统的时候统计当前的流量与initTraffic的差值就是此次登录系统所耗费的流量。然后根据上一次退出系统的时间exitDate
与此时的时间判断,这次登录所花的流量是今天的还是前一天的流量。然后更新todayTraffic,与对应月份的流量。

具体实现代码:
在登录时候调用下面方法

/**
     * 
     * @Description: 流量监控 往cookie中写入登录前的流量
     * @author fengao
     */
    private void trafficMonitor() {
        // 流量监控
        Cookie appConfig = Cookie.getInstance(this, Cookie.APP_CFG);
        long totalTraffic = -1;
        int appUid = TrafficStatsUtils.getUid(this);
        if(appUid!=-1){
            totalTraffic = TrafficStatsUtils.getTotalTraffic(appUid);
        }
        //将初始流量保存到APP_CFG的cookie中 (没有取到默认-1)
        appConfig.putVal(CookieKeyBean.APP_NET_START, totalTraffic);
    }

在退出系统的时候调用

/**
     * 
     * @Description: 处理网络流量  
     * @author fengao
     * @param uid 程序的uid
     */
    public void trafficMonitor(int uid){
        Cookie appConfig = Cookie.getInstance(this, Cookie.APP_CFG);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        //退出程序时间
        String appEndDate = DateUtil.getDate(dateFormat, 0, 0, 0);
        //进入程序时间
//      String appStartDate = appConfig.getVal(CookieKeyBean.APP_DATE_START);
        //进入程序的流量
        long totalStartTraffic = appConfig.getLong(CookieKeyBean.APP_NET_START);
        if(totalStartTraffic==-1){
            return;
        }
        //退出程序的流量
        long totalEndTraffic = TrafficStatsUtils.getTotalTraffic(uid);
        long totalAddedTraffic = totalEndTraffic - totalStartTraffic;
        //cookie中記錄的总流量
        long totalTraffic = appConfig.getLong(CookieKeyBean.APP_NET_TOTAL,0); 
        //更新cookie中的总流量
        appConfig.putVal(CookieKeyBean.APP_NET_TOTAL, totalTraffic+totalAddedTraffic);
        //今日的流量
        long todayTraffic = appConfig.getLong(CookieKeyBean.APP_NET_TODAY,0);
        //上一次退出系统的日期
        String lastEndDate = appConfig.getVal(CookieKeyBean.APP_DATE_END,"").trim();
        if(lastEndDate.equals(appEndDate)){
            //更新今日的流量
            todayTraffic = todayTraffic+totalAddedTraffic;
        }else{
            if(lastEndDate.equals("")){//第一次进入流量监控
                todayTraffic = totalAddedTraffic;
            }else{
                String monthDate = lastEndDate.substring(0, 6);
                String monthTraffic = appConfig.getVal(monthDate,"").trim();
                if(monthTraffic.equals("")){//第一次记录月份
                    monthTraffic = monthTraffic + todayTraffic;
                }
                monthTraffic = monthTraffic+","+todayTraffic;
                //更新cookie中对应月份的流量  201704:120,130,140
                appConfig.putVal(monthDate,monthTraffic);
                todayTraffic = totalAddedTraffic;
            }
        }
        //更新cookie中的今日流量
        appConfig.putVal(CookieKeyBean.APP_NET_TODAY, todayTraffic);
        //更新cookie中退出系统的时间
        appConfig.putVal(CookieKeyBean.APP_DATE_END, appEndDate);
    }

以上2个方法中的cookie是sharedPreference的封装类
获取当前程序流量工具类:

/**
 * 
 * @ClassName: TrafficStatsUtils 
 * @Description: 流量统计的工具类
 * @author fengao
 * @date 2017年4月26日 上午9:44:37 
 *
 */
public class TrafficStatsUtils {

    /**
     * 
     * @Description: 获取uid上传的流量(wifi+3g/4g)
     * @author fengao
     * @param uid 程序的uid
     * @return 上传的流量(tcp+udp)  返回-1 表示不支持得机型
     */
    public static long getTxTraffic(int uid) {
        return  TrafficStats.getUidTxBytes(uid);
    }

    /**
     * 
     * @Description: 获取uid上传的流量(wifi+3g/4g)  通过读取/proc/uid_stat/uid/tcp_snd文件获取
     * @author fengao
     * @param uid 程序的uid
     * @return 上传的流量(tcp)  返回-1 表示出现异常
     */
    public static long getTxTcpTraffic(int uid){
        RandomAccessFile rafSnd = null;
        String sndPath = "/proc/uid_stat/" + uid + "/tcp_snd";
        long sndTcpTraffic;
        try {
            rafSnd = new RandomAccessFile(sndPath, "r");
            sndTcpTraffic = Long.parseLong(rafSnd.readLine());
        } catch (FileNotFoundException e) {
            sndTcpTraffic = -1;
        } catch (IOException e) {
            e.printStackTrace();
            sndTcpTraffic = -1;
        } finally {
            try {
                if (rafSnd != null){
                    rafSnd.close();
                }
            } catch (IOException e) {
                sndTcpTraffic = -1;
            }
        }
        return sndTcpTraffic;
    }

    /**
     * 
     * @Description: 获取uid下載的流量(wifi+3g/4g)
     * @author fengao
     * @param uid 程序的uid
     * @return 下載的流量(tcp+udp) 返回-1表示不支持的机型
     */
    public static long getRxTraffic(int uid){
        return  TrafficStats.getUidRxBytes(uid);
    }

    /**
     * 
     * @Description: 获取uid上传的流量(wifi+3g/4g) 通过读取/proc/uid_stat/uid/tcp_rcv文件获取
     * @author fengao
     * @param uid 程序的uid
     * @return 下载的流量(tcp)  返回-1 表示出现异常
     */
    public static long getRxTcpTraffic(int uid) {
        RandomAccessFile rafRcv = null; // 用于访问数据记录文件
        String rcvPath = "/proc/uid_stat/" + uid + "/tcp_rcv";
        long rcvTcpTraffic;
        try {
            rafRcv = new RandomAccessFile(rcvPath, "r");
            rcvTcpTraffic = Long.parseLong(rafRcv.readLine()); // 读取流量统计
        } catch (FileNotFoundException e) {
            rcvTcpTraffic = -1;
        } catch (IOException e) {
            rcvTcpTraffic = -1;
        } finally {
            try {
                if (rafRcv != null){
                    rafRcv.close();
                }
            } catch (IOException e) {
                rcvTcpTraffic = -1;
            }
        }
        return rcvTcpTraffic;
    }

    /**
     * 
     * @Description: 得到uid的总流量(上传+下载)
     * @author fengao
     * @param uid 程序的uid
     * @return uid的总流量   当设备不支持方法且没有权限访问/proc/uid_stat/uid时 返回-1
     */
    public static long getTotalTraffic(int uid){
        long txTraffic = (getTxTraffic(uid)==-1)?getTxTcpTraffic(uid):getTxTraffic(uid);
        if(txTraffic==-1){
            return -1;
        }
        long rxTraffic = (getRxTraffic(uid)==-1)?getRxTcpTraffic(uid):getRxTraffic(uid);
        if(rxTraffic==-1){
            return -1;
        }
        return txTraffic+rxTraffic;
    }

    /**
     * 
     * @Description: 取得程序的uid
     * @author fengao
     * @param context 上下文
     * @return 当前程序的uid  返回-1表示出现异常
     */
    public static int getUid(Context context) {
        try {
            PackageManager packageManager =context.getPackageManager();
            ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
            return applicationInfo.uid;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        return -1;
    }
}

最后给一个了解流量监控的相关知识总结的知识图。
这里写图片描述

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值