【问题标题】:How to get network usage of apps on Android Q?如何在 Android Q 上获取应用程序的网络使用情况?
【发布时间】:2019-10-29 20:36:30
【问题描述】:

背景

我知道我们可以通过使用类似的东西(过去问过,here)来获取指定应用程序的网络使用情况(到目前为止,从某个特定时间开始,移动和 Wifi 使用的总带宽):

    private final static int[] NETWORKS_TYPES = new int[]{ConnectivityManager.TYPE_WIFI, ConnectivityManager.TYPE_MOBILE};

    long rxBytes=0L, txBytes=0L;
    final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    final String subscriberId = telephonyManager.getSubscriberId();
    final ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
    final int uid = applicationInfo.uid;
    for (int networkType : NETWORKS_TYPES) {
        final NetworkStats networkStats = networkStatsManager.queryDetailsForUid(networkType, subscriberId, 0, System.currentTimeMillis(), uid); 
        final Bucket bucketOut = new Bucket();
        while (true) {
                networkStats.getNextBucket(bucketOut);
                final long rxBytes = bucketOut.getRxBytes();
                if (rxBytes >= 0)
                    totalRx += rxBytes;
                final long txBytes = bucketOut.getTxBytes();
                if (txBytes >= 0)
                    totalTx += txBytes;
                if (!networkStats.hasNextBucket())
                    break;
            }
        }
    }

文档:

https://developer.android.com/reference/android/app/usage/NetworkStatsManager.html#queryDetailsForUid(int,%20java.lang.String,%20long,%20long,%20int)

也可以获取全球网络使用情况(使用 TrafficStats.getUidRxBytes(applicationInfo.uid)TrafficStats.getUidTxBytes(applicationInfo.uid) ),但这不是这个线程的全部内容。

问题

Android Q 似乎计划导致许多设备身份功能停止工作,getSubscriberId 就是其中之一。

我尝试过的

我尝试将 targetSdk 设置为 29 (Q),看看当我尝试使用它时会发生什么。

正如预期的那样,我遇到了一个异常,表明我不能再这样做了。它说:

019-06-11 02:08:01.871 13558-13558/com.android.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.android.myapplication, PID: 13558
    java.lang.SecurityException: getSubscriberId: The user 10872 does not meet the requirements to access device identifiers.
        at android.os.Parcel.createException(Parcel.java:2069)
        at android.os.Parcel.readException(Parcel.java:2037)
        at android.os.Parcel.readException(Parcel.java:1986)
        at com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getSubscriberIdForSubscriber(IPhoneSubInfo.java:984)
        at android.telephony.TelephonyManager.getSubscriberId(TelephonyManager.java:3498)
        at android.telephony.TelephonyManager.getSubscriberId(TelephonyManager.java:3473)

搜索互联网和这里,我没有看到提到这一点,但我发现了类似的问题,获取 IMEI 和其他标识符:

所以现在我只是在这里做了一个关于它的错误报告(包括一个示例项目):

https://issuetracker.google.com/issues/134919382

问题

是否可以在 Android Q 上获取指定应用的网络使用情况(针对它时)?也许没有subscriberId?

如果有,怎么做?

如果没有,是否可以通过root或adb?


编辑:

好的,我不知道如何正式使用它,但至少对于 root 访问来说,可以使用从 here 找到的 this solution 获取subscriberId。

意思是这样的:

@SuppressLint("MissingPermission", "HardwareIds")
fun getSubscriberId(telephonyManager: TelephonyManager): String? {
    try {
        return telephonyManager.subscriberId
    } catch (e: Exception) {
    }
    val commandResult = Root.runCommands("service call iphonesubinfo 1 | grep -o \"[0-9a-f]\\{8\\} \" | tail -n+3 | while read a; do echo -n \"\\u\${a:4:4}\\u\${a:0:4}\"; done")
    val subscriberId = commandResult?.getOrNull(0)
    return if (subscriberId.isNullOrBlank()) null else subscriberId
}

这当然不是官方的解决方案,但总比没有好...

编辑:通过 root 获取它的部分是错误的。没有任何帮助。

【问题讨论】:

  • 从 Android Q 开始,应用程序必须具有 READ_PRIVILEGED_PHONE_STATE 特权权限才能访问设备的不可重置标识符,包括 IMEI 和序列号。您是否尝试添加此权限?
  • 是的,但它不适用于普通应用程序。它仅适用于系统应用程序。其他人已经问过了。示例:stackoverflow.com/a/51987160/878126。也见这里:developer.android.com/preview/privacy/…

标签: android networking android-10.0


【解决方案1】:

谷歌团队在你提到的线程的comment 中说: " 状态:无法修复(预期行为) 这是按预期工作的。 IMEI 是个人标识符,作为政策问题,它不会提供给应用程序。没有解决方法。”。所以我猜想 NetworkStatsManager 类中需要 IMSI(也被视为个人标识符)才能工作的方法(如 queryDetailsForUid(int, String, long, long, int))现在在 Android Q 中被破坏了. 您可以使用这些方法来获取应用程序的 Wifi 使用详细信息(通过为订阅者 ID 传递空字符串),但要获取移动使用详细信息,您现在必须依靠良好的旧 TrafficStats 类,直到问题被注意到并修复。

【讨论】:

  • 链接是关于IMEI的。我谈到了 queryDetailsForUid 需要什么,即 getSubscriberId 。但是,即使它大致相同,如果我有根可能有解决方案吗?我尝试以任何一种方式授予此权限,但未授予该应用程序。似乎它真的只适用于系统应用程序。正如我所写,使用 TrafficStats 是全球性的。
  • 我对 IMEI 和 IMSI 感到困惑。我已经编辑了答案。目前,我认为除了系统应用程序之外,没有其他方法可以访问 Android Q 的 IMSI 或 IMEI 号码。虽然 TrafficStats 提供了全球移动使用情况,但我认为它是目前接近您想要在 Android Q 中实现的唯一方法。
  • 据此(developer.android.com/preview/privacy/…),设备的网络状态存储在/proc/net中。尝试使用 root 访问它。
  • 似乎有很多文件,但是在那里寻找什么?见:ibb.co/7QqBGfG
  • @androiddeveloper 能否在 android Q 中获取 IMSI?
【解决方案2】:

我们正在使用 NetworkStatsManager.querySummaryForDevice()。由于一个偶然的错误,我们在 Q 中将 null 作为 MOBILE 的subscriberId 传递。它似乎在我们的设备上工作。我不确定这是错误还是功能,但这些值符合我们预期的蜂窝使用情况。

总而言之,对于这个用例,我们可以只使用 TrafficStats,但在 Pie 之前它是不稳定的。

【讨论】:

  • Wifi 使用情况如何?那么只执行特定应用的查询呢?
【解决方案3】:

您可以为 API 29 及更高版本提供 null 值。它返回 WIFI 和移动数据的值。

Documentation:

如果适用,网络接口的订阅者 ID。 从 API 级别 29 开始,subscriberId 受到额外限制的保护。调用不符合新要求的应用程序访问subscriberId 可以在查询移动网络类型时提供空值以接收所有移动网络的使用情况。有关更多详细信息,请参阅 TelephonyManager.getSubscriberId()。

权限(别忘了获得用户的许可):

<uses-permission android:name="android.permission.READ_PHONE_STATE"
    android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
    tools:ignore="ProtectedPermissions" />

示例代码:

//network stats
NetworkStatsManager networkStatsManager = (NetworkStatsManager) activity.getSystemService(Context.NETWORK_STATS_SERVICE);
int[] networkTypes = new int[]{NetworkCapabilities.TRANSPORT_CELLULAR, NetworkCapabilities.TRANSPORT_WIFI};
String subscriberId;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
   TelephonyManager telephonyManager = (TelephonyManager) activity.getSystemService(Context.TELEPHONY_SERVICE);
   try {
     subscriberId = telephonyManager.getSubscriberId(); //MissingPermission
   } catch (SecurityException e) {
     subscriberId = null;
   }

} else {
   subscriberId = null;
}

获取应用的 NetworkStats:

long receivedBytes = 0;
long transmittedBytes = 0;

for (int networkType : networkTypes) {
    NetworkStats networkStats;
    try {
        networkStats = networkStatsManager
                .queryDetailsForUid(networkType,
                        subscriberId,
                        0,
                        System.currentTimeMillis(),
                        appUid);
    } catch (SecurityException e) {
        networkStats = null;
    }

    if(networkStats != null) {
        NetworkStats.Bucket bucketOut = new NetworkStats.Bucket();
        while (networkStats.hasNextBucket()) {
            networkStats.getNextBucket(bucketOut);
            long rxBytes = bucketOut.getRxBytes();
            long txBytes = bucketOut.getTxBytes();
            if (rxBytes >= 0) {
                receivedBytes += rxBytes;
            }
            if (txBytes >= 0) {
                transmittedBytes += txBytes;
            }
        }
        networkStats.close();
    }
}

【讨论】:

  • 似乎运作良好。此操作需要 PACKAGE_USAGE_STATS 从哪个 Android 版本开始?适用于所有版本?我不记得了......为什么你有 android:maxSdkVersion="28" ?不再需要了?它是否也适用于 Wifi,并且仅适用于指定的应用程序?
  • @androiddeveloper 1- 我不确定这个操作,但一些 NetworkStats 操作需要 PACKAGE_USAGE_STATS 权限。如果它在未经许可的情况下工作,您可以将其删除。 2-是的,android:maxSdkVersion="28" 因为我们在不使用 TelephonyManager 时不需要此权限。 (这对这段代码有效,如果你打算对其他功能使用READ_PHONE_STATE权限,你可以删除maxSdk标签)。 3- 我不太明白最后一个问题,但它适用于 Wifi 和移动数据。
  • 1.这里需要哪些操作呢? 2.好的。 3. 我的意思是你可以查询一个特定的应用程序,它使用了多少字节。
  • 您好,需要的权限请看这篇文章:developer.android.com/reference/android/app/usage/…。要检查应用使用情况,请将“appUid”替换为您要查询的应用 uid。
  • 哦,好的。谢谢。是不是和我写的一样?我不记得这个问题了,但似乎代码非常相似。我想知道为什么我没有成功...
猜你喜欢
  • 1970-01-01
  • 2012-06-15
  • 1970-01-01
  • 1970-01-01
  • 2019-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多