如何判断“移动网络数据”是否启用或禁用(即使连接的是WiFi)?

79
我有一个应用程序,希望能够从远程查询中获取连接状态报告。
我想知道是否连接了WiFi,并且移动网络上启用了数据访问权限。
如果WiFi超出范围,我想知道是否可以依赖移动网络。
问题在于,当我通过WiFi连接时,数据启用始终返回为true,只有在未连接WiFi时才能正确查询移动网络。
我看到的所有答案都建议轮询以查看当前连接状态,但我想知道是否需要移动网络可用,即使我目前可能已连接WiFi。
是否有任何方法可以在不轮询连接状态的情况下判断移动网络数据是否已启用?
编辑:
因此,当通过WiFi连接时,如果我进入设置并取消选择“启用数据”,然后在我的应用程序中执行以下操作:
 boolean mob_avail = 
 conMan.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).isAvailable();

虽然mob_avail的返回值为'true',但是我已经禁用了移动网络数据,所以我希望它是'false'

如果关闭WiFi,由于我已经禁用了移动网络数据,因此理所当然没有连接。

那么当我通过WiFi连接时,如何检查移动网络数据是否已启用?

更新

如ss1271在评论中建议的那样,我查看了getAllNetworkInfo()返回的有关移动网络的信息。

我在以下3种情况下输出了有关移动网络的信息:

WiFi关闭 - 移动数据开启

WiFi开启 - 移动数据关闭

WiFi开启 - 移动数据开启

并得到以下结果:

WiFi关闭:

mobile[HSUPA],状态:CONNECTED/CONNECTED,原因:unknown,额外信息: internet,漫游:false,故障转移:false,可用:true, featureId:-1,用户默认设置:false

WiFi开启 / 移动关闭

NetworkInfo:类型:mobile[HSUPA],状态:DISCONNECTED/DISCONNECTED, 原因:connectionDisabled,额外信息:(none),漫游:false, 故障转移:false,可用:true,featureId:-1,用户默认设置: false

WiFi开启 / 移动开启

NetworkInfo:类型:mobile[HSPA],状态:DISCONNECTED/DISCONNECTED, 原因:connectionDisabled,额外信息:(none),漫游:false, 故障转移:false,可用:true,featureId:-1,用户默认设置: false

正如你所看到的,每次都返回了true和当WiFi生效时仅显示为断开连接的状态。

澄清

不是要查看我的手机当前是否通过移动网络连接。我正在尝试确定用户是否已启用/禁用移动网络上的数据访问。他们可以通过转到“设置”->“无线和网络设置”->“移动网络设置”->“启用数据”来打开或关闭此功能。


1
测试后台数据和数据包数据是否启用,同样的事情。 - Kevin Bradshaw
1
你尝试过使用 getAllNetworkInfo() 吗? - dumbfingers
1
我一时想不起来,但我会尽快回家试一试并更新。我猜那会返回一个可用连接的数组?而且我需要遍历这个数组吗? - Kevin Bradshaw
1
是的,它会返回一个数组。老实说,我以前没有使用过它,所以你可能需要打印一些日志来查看它为你获取了什么。请让我知道结果,祝好运。 - dumbfingers
17个回答

132
以下代码将告诉您“移动数据”是否已启用,而不管此时是否存在移动数据连接或Wi-Fi是否已启用/活动。此代码仅适用于Android 2.3(姜饼)及更高版本。实际上,此代码也适用于早期版本的Android ;-)

    boolean mobileDataEnabled = false; // Assume disabled
    ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    try {
        Class cmClass = Class.forName(cm.getClass().getName());
        Method method = cmClass.getDeclaredMethod("getMobileDataEnabled");
        method.setAccessible(true); // Make the method callable
        // get the setting for "mobile data"
        mobileDataEnabled = (Boolean)method.invoke(cm);
    } catch (Exception e) {
        // Some problem accessible private API
        // TODO do whatever error handling you want here
    }

注意:您需要获得权限android.permission.ACCESS_NETWORK_STATE才能使用此代码。


2
太棒了!完美无缺。非常感谢,虽然花费了我1/3的声望,但它恰好满足我的需求,所以这些分数花得非常值得。我能否请您对代码进行一些解释?它与我尝试的内容和被建议作为解决方案的东西有所不同。如果我说你正在使用反射,我会是正确的吗?我对反射有点模糊,如果那就是反射的话。这是一个后门技巧还是Android的正式支持功能?无论如何,再次感谢。你是个明星! - Kevin Bradshaw
1
哇,非常感谢您的评论!很高兴能够帮到您。是的,这段代码使用了Java反射。由于某些愚蠢的原因,Android核心开发人员决定不在公共API中公开获取和设置“移动数据启用”标志的方法。因此,这些方法是存在的,但它们被标记为“私有”。使用反射,我们可以获取有关类的大量信息,甚至是被认为是“私有”的信息。由于这是一个“不受支持的API”,它可能无法在所有设备上运行,并且在Android的未来版本中可能会更改。但是,它似乎现在在大多数设备上都可以工作。 - David Wasser
2
@DavidWasser 太棒了。但我需要提出一个问题,如果使用proguard,“getMobileDataEnabled”字符串将被转换为其他内容,导致错误的结果。Proguard与反射不兼容。 - Murtaza Khursheed Hussain
3
这样做虽然可行,但存在很大风险,因为API开发人员可以随时删除私有方法。 - Peter Chaula
1
只要您在使用无法混淆的事物上进行反射,例如框架或库,我不知道那一点。谢谢。 - Murtaza Khursheed Hussain
显示剩余16条评论

37

我已经升级了Allesio的答案。Settings.Secure的mobile_data int自4.2.2版本以来已移至Settings.Global。

当您想知道移动网络是否启用时,即使WiFi已启用和连接,可以尝试此代码。

更新以检查SIM卡是否可用。感谢murat指出。

boolean mobileYN = false;

TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (tm.getSimState() == TelephonyManager.SIM_STATE_READY) {
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1)
    {
        mobileYN = Settings.Global.getInt(context.getContentResolver(), "mobile_data", 1) == 1;
    }
    else{
        mobileYN = Settings.Secure.getInt(context.getContentResolver(), "mobile_data", 1) == 1;
    }
}

2
@LoveForDroid 这是Settings.Global类的隐藏成员。你可以在aosp源代码中找到它。 - sNash
1
@Murat,我添加了一些代码来检查SIM卡是否可用。 - sNash
1
@sNash和SIM_STATE_UNKNOWN也应该被处理。在我的设备上,它没有SIM卡,而且SIM状态也是未知的。 - Murat
1
@Murat看起来SIM状态有9个项目,只有SIM_STATE_READY是有效的。所以我改变了代码去检查它。 - sNash
3
我曾见过至少一台设备,其中“禁用移动数据”选项在设置中不会改变此设置(但会导致TelephonyManager.isDataEnabled()返回false)。这可能是设置中的一个错误,因为下拉菜单中的移动数据图标也没有显示为禁用,但它确实禁用了移动数据。 - Ben Kuhn
显示剩余4条评论

23

一种方法是检查用户是否在设置中激活了移动数据,如果wifi关闭,那么很可能会使用移动数据。

这个方法已经测试过,而且不使用反射技术,尽管它使用了API中的一个隐藏值:

boolean mobileDataAllowed = Settings.Secure.getInt(getContentResolver(), "mobile_data", 1) == 1;

根据@user1444325指出的,根据API不同,您需要检查Settings.Global而不是Settings.Secure。

来源: Android API call to determine user setting "Data Enabled"


1
哇,这比反射方法要整洁得多,显然反射方法在棒棒糖上不起作用。这个版本是针对哪些安卓版本进行测试的? - Kevin Bradshaw
3
测试过 4.4.4 和 5.0.1 版本。 - Alessio
1
成功测试通过4.2.2版本。 - Jibran Khan
3
如果没有SIM卡,将返回true,你还应该添加SIM卡验证。 - Joze
1
@LoveForDroid,正如我所写的“它使用API中的隐藏值”:该值是隐藏的,因此您在文档化的API中看不到它(因为那只适用于公共方法);您需要去阅读源代码才能看到它。 - Alessio
显示剩余5条评论

6
@sNash的函数很好用。但是在一些设备上,我发现即使数据被禁用,它也会返回true。因此,我找到了一个Android API中的替代解决方案。
TelephonyManager的getDataState()方法将非常有用。
我使用上述函数更新了@sNash的函数。当蜂窝数据被禁用时,下面的函数返回false,否则返回true。
private boolean checkMobileDataIsEnabled(Context context){
        boolean mobileYN = false;

        TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        if (tm.getSimState() == TelephonyManager.SIM_STATE_READY) {
//          if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1)
//          {
//              mobileYN = Settings.Global.getInt(context.getContentResolver(), "mobile_data", 0) == 1;
//          }
//          else{
//              mobileYN = Settings.Secure.getInt(context.getContentResolver(), "mobile_data", 0) == 1;
//          }
            int dataState = tm.getDataState();
            Log.v(TAG,"tm.getDataState() : "+ dataState);
            if(dataState != TelephonyManager.DATA_DISCONNECTED){
                mobileYN = true;
            }

        }

        return mobileYN;
    }

你为什么要两次调用 getSystemService(Context.TELEPHONY_SERVICE)? - Chandler
那是个错误。我更新了代码片段 @Chandler - MohK

5

由于ConnectivityManager.allNetworkInfo已经过时,Android建议使用getNetworkCapabilities

fun isOnMobileData(): Boolean {
    val connectivityManager =
        context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
    val all = connectivityManager.allNetworks

    return all.any {
        val capabilities = connectivityManager.getNetworkCapabilities(it)
        capabilities?.hasTransport(TRANSPORT_CELLULAR) == true
    }
}

1
这不适用于这种情况:用户已启用移动数据,但没有数据套餐;顺便说一下,allNetworks也已被弃用。 - undefined

4

使用TelephonyManager

TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);

tm.isDataEnabled()

根据安卓文档,isDataEnabled()函数是与手机数据连接相关的。

1
必须说明的是,该功能仅在API 26版本及以上可用。 - clemsciences

3
这是两个其他答案提供的简单解决方案:

        TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            return tm.isDataEnabled();

        } else {
            return tm.getSimState() == TelephonyManager.SIM_STATE_READY && tm.getDataState() != TelephonyManager.DATA_DISCONNECTED;
        }

3
你可以尝试类似这样的方法:
ConnectivityManager conMan = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

//mobile
State mobile = conMan.getNetworkInfo(0).getState();

//wifi
State wifi = conMan.getNetworkInfo(1).getState();


if (mobile == NetworkInfo.State.CONNECTED || mobile == NetworkInfo.State.CONNECTING) 
{
    //mobile
}
else if (wifi == NetworkInfo.State.CONNECTED || wifi == NetworkInfo.State.CONNECTING) 
{
    //wifi
}

如果您想确认是否真正连接,请使用

NetworkInfo.State.CONNECTED 

只需要使用,而非替换。
NetworkInfo.State.CONNECTED || NetworkInfo.State.CONNECTING

1
正如我向第一(和第二)位回答者指出的那样,这不会起作用。我不是要查看是否通过移动设备连接,而是要查看用户是否启用了移动数据。(他们可以通过转到“设置”->“无线和网络设置”->“移动网络设置”->“启用数据”来打开或关闭此功能) - Kevin Bradshaw
@KevinBradshaw,你可以使用这个来检查移动数据是否已启用。如果移动数据已启用,则会得到NetworkInfo.State.CONNECTED或NetworkInfo.State.CONNECTING == mobile。因为如果移动数据已启用,则系统将尝试连接到网络或已连接。 - Milos Cuculovic
我认为你需要尝试一下。在手机上禁用移动网络数据(取消选中设置->无线和网络设置->移动网络设置->启用数据)。运行你的代码,你会发现它会报告有一个移动连接。NetworkInfo状态不考虑用户是否启用了移动数据。请参见已接受的答案,这是我找到的唯一可行的方法。 - Kevin Bradshaw

1
这是一个解决该问题的 Xamarin 解决方案:

    public static bool IsMobileDataEnabled()
    {
        bool result = false;

        try
        {
            Context context = //get your context here or pass it as a param

            if (Build.VERSION.SdkInt >= BuildVersionCodes.JellyBeanMr1)
            {
                //Settings comes from the namespace Android.Provider
                result = Settings.Global.GetInt(context.ContentResolver, "mobile_data", 1) == 1;
            }
            else
            {
                result = Settings.Secure.GetInt(context.ContentResolver, "mobile_data", 1) == 1;
            }
        }
        catch (Exception ex)
        {
            //handle exception
        }

        return result;
    }

PS:确保您拥有此代码的所有权限。


1

我认为使用NetworkInfo class和isConnected应该可以解决问题:

ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);

return info != NULL || info.isConnected();

而且要检查移动数据是否已连接。在我测试之前,我不能确定。而我要到明天才能测试。

TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);

if(tm .getDataState() == tm .DATA_CONNECTED)
   return true;

正如我在第一次回复中指出的那样,这不会起作用。我不是在查看是否通过移动设备连接,而是要查看用户是否启用了移动数据。(他们可以通过转到“设置”->“无线和网络设置”->“移动网络设置”->“启用数据”来打开或关闭此功能) - Kevin Bradshaw
1
你尝试使用TelephonyManager了吗?它应该会显示数据是否已启用。 - tozka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接