使用NetworkStatsManager获取移动数据使用历史记录

我想知道数据使用历史,并注意到“新”android-6 NetworkStatsManager似乎是积极的(我已经使用了TrafficStats一段时间,但不会涵盖任何重启之前的任何事情)。

从API文档:

注意:此API需要权限PACKAGE_USAGE_STATS,这是系统级权限,不会授予第三方应用程序。 但是,声明权限意味着使用API​​的意图,并且设备的用户可以通过“设置”应用程序授予权限。 配置文件所有者应用程序自动被授予查询其管理的配置文件上的数据的权限(即,除querySummaryForDevice(int,String,long,long)之外的任何查询)。 设备所有者应用程序同样可以访问主要用户的使用数据。

我想知道聚合级别的数据使用情况,而不是使用数据的应用程序,所以我试着像这样使用它:

 NetworkStatsManager service = context.getSystemService(NetworkStatsManager.class); NetworkStats.Bucket bucket = service.querySummaryForDevice(ConnectivityManager.TYPE_MOBILE, null, from, to); ... 

不幸的是,抛出SecurityException

 java.lang.SecurityException: NetworkStats: Neither user 10174 nor current process has android.permission.READ_NETWORK_USAGE_HISTORY. at android.os.Parcel.readException(Parcel.java:1620) at android.os.Parcel.readException(Parcel.java:1573) at android.net.INetworkStatsSession$Stub$Proxy.getDeviceSummaryForNetwork(INetworkStatsSession.java:259) at android.app.usage.NetworkStats.getDeviceSummaryForNetwork(NetworkStats.java:316) at android.app.usage.NetworkStatsManager.querySummaryForDevice(NetworkStatsManager.java:100) ... 

第三方应用程序不允许使用android.permission.READ_NETWORK_USAGE_HISTORY权限。 所以这似乎是一个死胡同。

但是,我深入研究了内部结构,发现您可以使用内部/隐藏API执行相同的操作而无需请求任何权限:

 INetworkStatsService service = INetworkStatsService.Stub.asInterface( ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); INetworkStatsSession session = service.openSession(); NetworkTemplate mobileTemplate = NetworkTemplate.buildTemplateMobileWildcard(); int fields = NetworkStatsHistory.FIELD_RX_BYTES | NetworkStatsHistory.FIELD_TX_BYTES; NetworkStatsHistory mobileHistory = session.getHistoryForNetwork(mobileTemplate, fields); for (int i = 0; i < mobileHistory.size(); i++) { NetworkStatsHistory.Entry entry = mobileHistory.getValues(i, null); ... } session.close(); 

我真的想用公共API做同样的事情,那么,我该怎么做呢?

  • Android 6 getAccountName()缺少android.permission.GET_ACCOUNTS
  • Android:adb:将文件复制到/ system(权限被拒绝)
  • Android应用可以在运行时请求附加权限吗?
  • 是否可以在Android中拥有“可选”权限?
  • 如何使用gradle构建Android应用时删除特定权限?
  • 什么样的Android应用程序需要android.permission.READ_PHONE_STATE权限?
  • Androidexception:java.io.IOException:打开失败:EACCES(权限被拒绝)
  • Android - 在应用项目中拥有提供商权限
  • 有办法获得对NetworkStateManager的访问权限而无法访问私有API。 以下是步骤:

    1. AndroidManifest.xml声明所需的权限:

         
      1. Activity请求许可

      android.permission.PACKAGE_USAGE_STATS不是正常的权限,不能简单地请求。 要检查是否已授予权限,请检查:

       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; } 

      要请求此权限,只需调用Intent

       Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); startActivity(intent); 

      还需要另一个permmission: Manifest.permission.READ_PHONE_STATE 。 只有在需要获取移动数据统计信息时才需要它。 但是,这是正常的权限,因此可以像任何其他权限一样请求

      1. 使用NetworkStatsManager

    制作了一个示例Github repo来展示其用法。

     /* getting youtube usage for both mobile and wifi. */ public long getYoutubeTotalusage(Context context) { String subId = getSubscriberId(context, ConnectivityManager.TYPE_MOBILE); return getYoutubeUsage(ConnectivityManager.TYPE_MOBILE, subId) + getYoutubeUsage(ConnectivityManager.TYPE_WIFI, ""); } private long getYoutubeUsage(int networkType, String subScriberId) { NetworkStats networkStatsByApp; long currentYoutubeUsage = 0L; try { networkStatsByApp = networkStatsManager.querySummary(networkType, subScriberId, 0, System.currentTimeMillis()); do { NetworkStats.Bucket bucket = new NetworkStats.Bucket(); networkStatsByApp.getNextBucket(bucket); if (bucket.getUid() == packageUid) { //rajeesh : in some devices this is immediately looping twice and the second iteration is returning correct value. So result returning is moved to the end. currentYoutubeUsage = (bucket.getRxBytes() + bucket.getTxBytes()); } } while (networkStatsByApp.hasNextBucket()); } catch (RemoteException e) { e.printStackTrace(); } return currentYoutubeUsage; } private String getSubscriberId(Context context, int networkType) { if (ConnectivityManager.TYPE_MOBILE == networkType) { TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); return tm.getSubscriberId(); } return ""; }