16

我想知道数据使用历史,并注意到“新的”android-6NetworkStatsManager似乎是积极的(我已经使用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 做同样的事情,那么,我该怎么做呢?

4

2 回答 2

22

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

  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. 仅当您需要获取移动数据统计信息时才需要它。但是,这是正常权限,因此可以像任何其他权限一样请求

    1. 使用NetworkStatsManager

制作了一个示例Github 存储库来演示用法。

于 2016-09-09T12:58:15.300 回答
1
/*
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 "";
}
于 2017-01-17T11:21:39.400 回答