【问题标题】:Getting mobile data usage history using NetworkStatsManager使用 NetworkStatsManager 获取移动数据使用历史记录
【发布时间】:2016-04-18 19:22:14
【问题描述】:

我想知道数据使用历史,并注意到“新”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.permission.READ_NETWORK_USAGE_HISTORY 我删除了清单上的警告,该应用程序最终安装在我的设备上。但是当我尝试获取 TYPE_MOBILE 时,它总是返回 0。WIFI 工作正常。你解决了这个问题吗?请告诉我:)
  • 我也总是 0
  • @aj0822ArpitJoshi 你能解决这个问题吗?我在 android 8 设备(不是所有设备,而是一些设备)中的移动数据有同样的问题
  • querySummaryForDevice 的第二个参数不能为空;改用telephonyManager.getSubscriberId()

标签: android permissions android-6.0-marshmallow networkstatsmanager


【解决方案1】:

有一种方法可以在不访问私有 API 的情况下获得对 NetworkStateManager 的访问权限。步骤如下:

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

    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission
        android:name="android.permission.PACKAGE_USAGE_STATS"
        tools:ignore="ProtectedPermissions"/>
    
    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);
    

    还需要另一个权限:Manifest.permission.READ_PHONE_STATE。仅当您需要获取mobile data statistics 时才需要它。但是,这是正常的权限,所以可以是requested as any other permission

    1. 使用NetworkStatsManager:

制作了一个示例Github repo 演示用法。

【讨论】:

  • 我被 READ_NETWORK_USAGE_HISTORY 困住了。在 6.0.1 上尝试了 repo,并从 NetworkStatsManager 端得到了奇怪的数字。你确定这个黑客仍然有效吗?非常感谢!
  • getSubscriberId() 不幸因 java.lang.SecurityException 崩溃:getSubscriberId:用户 10435 不符合在 Android 11 中访问设备标识符的要求
【解决方案2】:
/*
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 "";
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多