流量统计
Android目前提供了两种流量计算方案
TrafficStats
NetworkStatsManager
这两种方案有着各自的优缺点与限制,下面简单的记录一下
TrafficStats
Android API8后提供的该类,可以获取设备重启以来的流量信息。
1 2 3 4 5 6 7 8
| public class TrafficStats { public static long getMobileRxBytes() public static long getMobileTxBytes() public static long getTotalTxBytes() public static long getTotalRxBytes() public static long getUidRxBytes(int uid) public static long getUidTxBytes(int uid) }
|
以上为TrafficStats
提供的基本方法
根据上述方法可以大致得到当前设备使用的流量数据,getMobileXX()
获取移动网络数据,getTotalXX()
获取总流量数据,所以getTotalXX()-getMobileXX()
大致可以得到Wifi的使用数据。还可以通过指定getUidXX()
获取指定应用的流量数据。
优点:
缺点:
简单原理介绍
上述方法内部实现都是通过getStatsService()
进行调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private synchronized static INetworkStatsService getStatsService() { if (sStatsService == null) { sStatsService = INetworkStatsService.Stub.asInterface( ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); } return sStatsService; }
public static long getTotalTxPackets() { try { return getStatsService().getTotalStats(TYPE_TX_PACKETS); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
|
通过AIDL调用到NetWorkStatsService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class NetworkStatsService extends INetworkStatsService.Stub { ... @Override public long getTotalStats(int type) { long nativeTotalStats = nativeGetTotalStat(type, checkBpfStatsEnable()); if (nativeTotalStats == -1) { return nativeTotalStats; } else { return nativeTotalStats + getTetherStats(IFACE_ALL, type); } } } mUseBpfTrafficStats = new File("/sys/fs/bpf/traffic_uid_stats_map").exists();
private boolean checkBpfStatsEnable() { return mUseBpfTrafficStats; }
|
bpf流量监控
是在Android 9之后提供的,需要在Android P上的设备才可以使用。老的监控方式逐渐被废弃。
通过JNI调用com_android_server_net_NetworkStatsService.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| static const char* QTAGUID_IFACE_STATS = "/proc/net/xt_qtaguid/iface_stat_fmt"; static const char* QTAGUID_UID_STATS = "/proc/net/xt_qtaguid/stats";
static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type, jboolean useBpfStats) { Stats stats = {};
if (useBpfStats) { if (bpfGetIfaceStats(NULL, &stats) == 0) { return getStatsType(&stats, (StatsType) type); } else { return UNKNOWN; } }
if (parseIfaceStats(NULL, &stats) == 0) { return getStatsType(&stats, (StatsType) type); } else { return UNKNOWN; } }
|
在Android 9之前,通过读取/proc/net/xt_qtaguid/stats
文件内容进行解析获取对应流量数据。
Android9 之后,通过读取/sys/fs/bpf/traffic_uid_stats_map
获取数据
使用实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class TrafficStatsHelper {
public static long getAllRxBytes() { return TrafficStats.getTotalRxBytes(); }
public static long getAllTxBytes() { return TrafficStats.getTotalTxBytes(); }
public static long getAllRxBytesMobile() { return TrafficStats.getMobileRxBytes(); }
public static long getAllTxBytesMobile() { return TrafficStats.getMobileTxBytes(); }
public static long getAllRxBytesWifi() { return TrafficStats.getTotalRxBytes() - TrafficStats.getMobileRxBytes(); }
public static long getAllTxBytesWifi() { return TrafficStats.getTotalTxBytes() - TrafficStats.getMobileTxBytes(); }
public static long getPackageRxBytes(int uid) { return TrafficStats.getUidRxBytes(uid); }
public static long getPackageTxBytes(int uid) { return TrafficStats.getUidTxBytes(uid); } }
调用实例: TrafficStats.getUidRxBytes(Process.myUid())
|
NetworkStatsManager
Android 6.0之后新增加的类,可以获取历史的流量信息,并且支持查询时间段的流量数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public Bucket querySummaryForDevice(int networkType, String subscriberId, long startTime, long endTime)
public NetworkStats queryDetailsForUid(int networkType, String subscriberId, long startTime, long endTime, int uid) public static class Bucket { ... public long getRxBytes() { return mRxBytes; }
public long getTxBytes() { return mTxBytes; } }
|
以上为NetworkStatsManager
的主要调用方法
根据上述提供的方法,可以得到设备一直的流量数据,并且支持按照networkType
区分和startTime~endTime
获取指定时间段的流量数据。
优点:
缺点:
简单原理介绍
INetworkStatsSession.aidl -> getDeviceSummaryForNetwork
NetworkStatsService.java
NetworkStatsCollection.java -> getHistory()
使用实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| AndroidManifest.xml 配置权限 <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions"/> 申请权限 private void requestPermissions() { if (!hasPermissionToReadNetworkHistory()) { return; } if (!hasPermissionToReadPhoneStats()) { requestPhoneStateStats(); } }
private boolean hasPermissionToReadNetworkHistory() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return true; } final AppOpsManager appOps = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE); int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(), getPackageName()); if (mode == AppOpsManager.MODE_ALLOWED) { return true; } appOps.startWatchingMode(AppOpsManager.OPSTR_GET_USAGE_STATS, getApplicationContext().getPackageName(), new AppOpsManager.OnOpChangedListener() { @Override @TargetApi(Build.VERSION_CODES.M) public void onOpChanged(String op, String packageName) { int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(), getPackageName()); if (mode != AppOpsManager.MODE_ALLOWED) { return; } appOps.stopWatchingMode(this); Intent intent = new Intent(StatsActivity.this, StatsActivity.class); if (getIntent().getExtras() != null) { intent.putExtras(getIntent().getExtras()); } intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); getApplicationContext().startActivity(intent); } }); requestReadNetworkHistoryAccess(); return false; }
private void requestReadNetworkHistoryAccess() { Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); startActivity(intent); }
public long getPackageBytesWithNetTypeAndFlow(Context context, boolean isRx, int networkType) { NetworkStats networkStats = null; try { networkStats = networkStatsManager.queryDetailsForUid(networkType, getSubscriberId(context, networkType), 0, System.currentTimeMillis(), packageUid); } catch (RemoteException e) { return -1; } long bytes = 0L; NetworkStats.Bucket bucket = new NetworkStats.Bucket(); while (networkStats.hasNextBucket()) { networkStats.getNextBucket(bucket); bytes += isRx ? bucket.getRxBytes() : bucket.getTxBytes(); } networkStats.close(); return bytes; }
private String getSubscriberId(Context context, int networkType) { if (ConnectivityManager.TYPE_MOBILE == networkType) { TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); return tm.getSubscriberId(); } return ""; }
|
参考链接
TrafficStats流程分析
ePBF流量监控
NetStads Demo