Android流量统计分析
本文只做为个人分析留档。
使用
NetworkStatsManager statsManager = (NetworkStatsManager) getSystemService(Context.NETWORK_STATS_SERVICE);
tatsManager.querySummary(ConnectivityManager.TYPE_MOBILE, "", startTime, endTime);
statsManager.querySummaryForDevice(ConnectivityManager.TYPE_WIFI, null, startTime, endTime);
权限分析
为什么需要权限?NetworkStatsAccess.java
@NetworkStatsAccess.Level int checkAccessLevel
这类生成查询的等级,按顺序来。
1.如果是系统签名,后面直接放行,可以查询全部。NetworkStatsAccess.Level.DEVICE;
2.如果是isDeviceOwner ,system uid同上。NetworkStatsAccess.Level.DEVICE;
3.如果拥有上述权限,level是:NetworkStatsAccess.Level.DEVICESUMMARY;
4.如果是isProfileOwner,level是:NetworkStatsAccess.Level.USER;
5.啥都没有就是默认:NetworkStatsAccess.Level.DEFAULT;
查询之前检测level
public static boolean isAccessibleToUser(int uid, int callerUid,
@NetworkStatsAccess.Level int accessLevel) {
switch (accessLevel) {
case NetworkStatsAccess.Level.DEVICE:
// Device-level access - can access usage for any uid.
return true;
case NetworkStatsAccess.Level.DEVICESUMMARY:
// Can access usage for any app running in the same user, along
// with some special uids (system, removed, or tethering) and
// anonymized uids
return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
|| uid == UID_TETHERING || uid == UID_ALL
|| UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
case NetworkStatsAccess.Level.USER:
// User-level access - can access usage for any app running in the same user, along
// with some special uids (system, removed, or tethering).
return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
|| uid == UID_TETHERING
|| UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
case NetworkStatsAccess.Level.DEFAULT:
default:
// Default access level - can only access one's own usage.
return uid == callerUid;
}
}
NetworkStatsAccess.Level.USER 和NetworkStatsAccess.Level.DEVICESUMMARY基本类似,就是多了一个uid == UID_ALL。
NetworkStatsAccess.Level.DEVICESUMMARY这里面根据系统不一样有点区别。如果系统不支持多用户,那么就能查询全部,跟NetworkStatsAccess.Level.DEVICE一样,否则看注释。
NetworkStatsService
这个类是核心类,所有的查询都是从这里开始。
1.构造
NetworkStatsService service = new NetworkStatsService(context, networkManager, alarmManager,
wakeLock, getDefaultClock(), TelephonyManager.getDefault(),
new DefaultNetworkStatsSettings(context), new NetworkStatsObservers(),
getDefaultSystemDir(), getDefaultBaseDir());
构造里面传入的路径是/data/system/netstats.记住这个路径。
2.systemReady
这个方法有几个重要的方法:
mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false);
mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false);
mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false);
mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true);
//对应上面路径下面的四个文件
bootstrapStatsLocked()//这个里面多了一个路径。
初次检查,上述路径没有数据,那么统计数据哪里来的?
bootstrapStatsLocked()搞定了。
这个部分回到Android之前的统计了。
public NetworkStatsFactory(File procRoot, boolean useBpfStats) {
mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
mUseBpfStats = useBpfStats;
}
以上所有的文件解析出来,就是Android所有记录的流量使用情况。
其中/data/system/netstats 里面保存了uid ,pkg,subscriberId。似乎卸载的app的包名也记录了,这部分没有仔细去看。大概是因为文件是生成的时候app还没卸载。但是因为有部分流量是使用之前的流量统计逻辑,这部分不会记录包名,如果生成文件是已经卸载似乎就不会记录。
查询匹配
查询之前,生成:
private static NetworkTemplate createTemplate(int networkType, String subscriberId) {
final NetworkTemplate template;
switch (networkType) {
case ConnectivityManager.TYPE_MOBILE:
template = subscriberId == null
? NetworkTemplate.buildTemplateMobileWildcard()
: NetworkTemplate.buildTemplateMobileAll(subscriberId);
break;
case ConnectivityManager.TYPE_WIFI:
template = NetworkTemplate.buildTemplateWifiWildcard();
break;
default:
throw new IllegalArgumentException("Cannot create template for network type "
+ networkType + ", subscriberId '"
+ NetworkIdentity.scrubSubscriberId(subscriberId) + "'.");
}
return template;
}
生成的template与上述NetworkStatsService读取的数据做匹配。
具体规则区间,可以去看NetworkStatsCollection。
说明一个,移动网络查询之前subscriberId,在Android9还是10以后,传入null,就是查询时间段类的所有卡信息,即使卡不存在。即生成的template是NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null);
然后正常查询,系统会去掉uid_tag文件的信息。这个部分暂时没细看是做什么的。后续有时间补充。