背景:客户反映公司的APP在几部手机上流量超支,需要自证清白,否则要帮客户垫付超出的流量费用。流量超支之后手机早已停机,距今已有半年多,手机的日志文件莫名奇妙丢失,客户一口咬定是程序问题,大家相互扯来扯去。为了避免这个问题就有了这个监控自身程序流量的需求。
流量监控Android2.2之后提供了TrafficStats,可以用于统计手机的流量以及程序的流量。具体参照android develop的介绍
查看getUidRxBytes(int uid)的方法签名:
明显可以看出API 12之后,添加了此方法,此方法统计uid对应程序接收的流量(包括tcp+udp),在版本JELLY_BEAN_MR2之前可能会返回UNSUPPORTED(constant value -1)。版本N也可能会返回UNSUPPORTED。
再来看看方法参数 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;
}
}