setMobileDataEnabled方法在Android L及更高版本中不再可调用

我已经通过Googlelogging了78084号问题 ,关于setMobileDataEnabled()方法不再可以通过reflection来调用。 它是从Android 2.1(API 7)到Android 4.4(API 19)通过reflection来调用的,但是对于Android L和更高版本,即使使用root, setMobileDataEnabled()方法也是不可调用的。

官方的回应是这个问题是“closures”,并且状态设置为“WorkingAsIntended”。 Google的简单解释是:

私有API是私有的,因为它们不稳定,可能会在没有通知的情况下消失。

是的,谷歌,我们意识到使用reflection来调用隐藏的方法的风险 – 甚至在Android出现之前 – 但是你需要提供一个更可靠的答案,如果有的话,可以实现与setMobileDataEnabled() 。 (如果您不满意Google的决定,请login问题78084 ,并尽可能多地将其显示出来,让Google知道他们错误的方式。)

所以,我的问题是:在Android设备上以编程方式启用或禁用移动networkingfunction时,我们处于死路一条吗? 这种来自Google的粗暴的方式不能适应我的生活。 如果你有解决Android 5.0(棒棒糖)及更高版本的问题,我很乐意在这个主题中听到你的回答/讨论。

我已经使用下面的代码来查看setMobileDataEnabled()方法是否可用:

 final Class<?> conmanClass = Class.forName(context.getSystemService(Context.CONNECTIVITY_SERVICE).getClass().getName()); final Field iConnectivityManagerField = conmanClass.getDeclaredField("mService"); iConnectivityManagerField.setAccessible(true); final Object iConnectivityManager = iConnectivityManagerField.get(context.getSystemService(Context.CONNECTIVITY_SERVICE)); final Class<?> iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName()); final Method[] methods = iConnectivityManagerClass.getDeclaredMethods(); for (final Method method : methods) { if (method.toGenericString().contains("set")) { Log.i("TESTING", "Method: " + method.getName()); } } 

但事实并非如此。

更新 :目前,如果设备是固定的,则可以切换移动networking。 但是,对于没有根深蒂固的设备来说,这仍然是一个调查过程,因为没有通用的方法来切换移动networking。

为了扩展Muzikant的解决scheme#2,有人可以尝试下面的解决scheme在Android 5.0扎根设备(因为我目前不拥有一个),让我知道,如果它的工作或不工作。

要启用或禁用移动数据,请尝试:

 // 1: Enable; 0: Disable su -c settings put global mobile_data 1 su -c am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1 

注意: mobile_datavariables可以在Android API 21源代码中/android-sdk/sources/android-21/android/provider/Settings.java ,声明为:

 /** * Whether mobile data connections are allowed by the user. See * ConnectivityManager for more info. * @hide */ public static final String MOBILE_DATA = "mobile_data"; 

/android-sdk/sources/android-21/com/android/internal/telephony/TelephonyIntents.java Intent可以在Android API 21源代码中find,位于/android-sdk/sources/android-21/com/android/internal/telephony/TelephonyIntents.java ,声明为:

 /** * Broadcast Action: The data connection state has changed for any one of the * phone's mobile data connections (eg, default, MMS or GPS specific connection). * * <p class="note"> * Requires the READ_PHONE_STATE permission. * <p class="note">This is a protected intent that can only be sent by the system. * */ public static final String ACTION_ANY_DATA_CONNECTION_STATE_CHANGED = "android.intent.action.ANY_DATA_STATE"; 

更新1 :如果您不想在您的Android应用程序中实现上述Java代码,那么您可以通过shell(Linux)或命令提示符(Windows)运行su命令,如下所示:

 adb shell "su -c 'settings put global mobile_data 1; am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1'" 

注意: adb位于/android-sdk/platform-tools/目录下。 settings命令仅在Android 4.2或更高版本上受支持。 较旧的Android版本会报告"sh: settings: not found"错误。

更新2 :另一种方式来切换移动networking在一个固定的Android 5 +设备将是使用未logging的service shell命令。 以下命令可以通过ADB执行以切换移动networking:

 // 1: Enable; 0: Disable adb shell "su -c 'service call phone 83 i32 1'" 

要不就:

 // 1: Enable; 0: Disable adb shell service call phone 83 i32 1 

注1 :在service call phone命令中使用的交易码83可能在Android版本之间改变。 请检查com.android.internal.telephony.ITelephony以获取您的Android版本的TRANSACTION_setDataEnabled字段的值。 此外,而不是硬编码83 ,你会更好使用reflection来获得TRANSACTION_setDataEnabled字段的值。 通过这种方式,它可以在所有运行Android 5+的移动品牌上运行(如果您不知道如何使用Reflection来获取TRANSACTION_setDataEnabled字段的值,请参阅下面的PhongLe解决scheme – 请不要将其复制到此处) 重要 :请注意事务代码TRANSACTION_setDataEnabled仅在Android 5.0及更高版本中引入。 在早期版本的Android上运行此事务代码将不会执行任何操作,因为事务代码TRANSACTION_setDataEnabled不存在。

注2adb位于/android-sdk/platform-tools/目录下。 如果您不想使用ADB,请在您的应用程序中通过su执行该方法。

注3 :请参阅下面的更新3。

更新3 :许多Android开发者已经通过电子邮件发送了关于为Android 5+开启/closures移动networking的问题,但是我不会回复个别的电子邮件,而是在这里发布我的答案,以便每个人都可以使用它并适应Android应用程序。

首先,让我们澄清一些关于以下的误解和误解:

 svc data enable svc data disable 

以上方法只会打开/closures背景数据, 而不是订阅服务,所以电池会耗费一点点,因为订购服务 – 一个Android系统服务 – 仍然会在后台运行。 对于支持多个SIM卡的Android设备,由于订阅服务不断扫描可用移动networking以使用Android设备中可用的有效SIM卡,因此这种情况更糟糕。 使用此方法需要您自担风险。

现在,closures移动networking的正确方法,包括通过API 22中引入的SubscriptionManager类的相应订阅服务,是:

 public static void setMobileNetworkfromLollipop(Context context) throws Exception { String command = null; int state = 0; try { // Get the current state of the mobile network. state = isMobileDataEnabledFromLollipop(context) ? 0 : 1; // Get the value of the "TRANSACTION_setDataEnabled" field. String transactionCode = getTransactionCode(context); // Android 5.1+ (API 22) and later. if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { SubscriptionManager mSubscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); // Loop through the subscription list ie SIM list. for (int i = 0; i < mSubscriptionManager.getActiveSubscriptionInfoCountMax(); i++) { if (transactionCode != null && transactionCode.length() > 0) { // Get the active subscription ID for a given SIM card. int subscriptionId = mSubscriptionManager.getActiveSubscriptionInfoList().get(i).getSubscriptionId(); // Execute the command via `su` to turn off // mobile network for a subscription service. command = "service call phone " + transactionCode + " i32 " + subscriptionId + " i32 " + state; executeCommandViaSu(context, "-c", command); } } } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { // Android 5.0 (API 21) only. if (transactionCode != null && transactionCode.length() > 0) { // Execute the command via `su` to turn off mobile network. command = "service call phone " + transactionCode + " i32 " + state; executeCommandViaSu(context, "-c", command); } } } catch(Exception e) { // Oops! Something went wrong, so we throw the exception here. throw e; } } 

要检查移动networking是否启用:

 private static boolean isMobileDataEnabledFromLollipop(Context context) { boolean state = false; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { state = Settings.Global.getInt(context.getContentResolver(), "mobile_data", 0) == 1; } return state; } 

要获得TRANSACTION_setDataEnabled字段的值(从PhongLe的解决scheme中借用):

 private static String getTransactionCode(Context context) throws Exception { try { final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); final Class<?> mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName()); final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony"); mTelephonyMethod.setAccessible(true); final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager); final Class<?> mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName()); final Class<?> mClass = mTelephonyStubClass.getDeclaringClass(); final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled"); field.setAccessible(true); return String.valueOf(field.getInt(null)); } catch (Exception e) { // The "TRANSACTION_setDataEnabled" field is not available, // or named differently in the current API level, so we throw // an exception and inform users that the method is not available. throw e; } } 

通过su执行命令:

 private static void executeCommandViaSu(Context context, String option, String command) { boolean success = false; String su = "su"; for (int i=0; i < 3; i++) { // Default "su" command executed successfully, then quit. if (success) { break; } // Else, execute other "su" commands. if (i == 1) { su = "/system/xbin/su"; } else if (i == 2) { su = "/system/bin/su"; } try { // Execute command as "su". Runtime.getRuntime().exec(new String[]{su, option, command}); } catch (IOException e) { success = false; // Oops! Cannot execute `su` for some reason. // Log error here. } finally { success = true; } } } 

希望本次更新能够消除您在Android 5+设备上打开/closures移动networking的任何误解,误解或疑问。

我注意到由ChuongPham发布的服务调用方法在所有设备上都不能一致地工作。

我发现以下解决scheme,我认为,将工作没有任何问题的所有ROOTED设备。

通过su执行以下操作

启用移动数据

 svc data enable 

禁用移动数据

 svc data disable 

我认为这是最简单和最好的方法。

编辑:2 downvotes是我相信是商业原因。 这个人现在已经删除了他的评论。 试试吧,它的作品! 也证实了家伙在评论中的工作。

只是分享一些更深入的见解和可能的解决scheme(对于根植设备和系统应用程序)。

解决scheme#1

似乎在ConnectivityManager不再存在setMobileDataEnabled方法,并且使用两个方法getDataEnabledsetDataEnabled将此function移至TelephonyManager 。 我尝试用reflection来调用这些方法,如下面代码所示:

 public void setMobileDataState(boolean mobileDataEnabled) { try { TelephonyManager telephonyService = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); Method setMobileDataEnabledMethod = telephonyService.getClass().getDeclaredMethod("setDataEnabled", boolean.class); if (null != setMobileDataEnabledMethod) { setMobileDataEnabledMethod.invoke(telephonyService, mobileDataEnabled); } } catch (Exception ex) { Log.e(TAG, "Error setting mobile data state", ex); } } public boolean getMobileDataState() { try { TelephonyManager telephonyService = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); Method getMobileDataEnabledMethod = telephonyService.getClass().getDeclaredMethod("getDataEnabled"); if (null != getMobileDataEnabledMethod) { boolean mobileDataEnabled = (Boolean) getMobileDataEnabledMethod.invoke(telephonyService); return mobileDataEnabled; } } catch (Exception ex) { Log.e(TAG, "Error getting mobile data state", ex); } return false; } 

当执行代码时,你会得到一个SecurityException声明Neither user 10089 nor current process has android.permission.MODIFY_PHONE_STATE.

所以,是的,这是对内部API的改变,不再适用于在以前版本中使用这种黑客的应用程序。

(开始咆哮:那可怕的android.permission.MODIFY_PHONE_STATE权限…结束咆哮)。

好消息是,如果您正在构build可获取MODIFY_PHONE_STATE权限的应用程序(只有系统应用程序可以使用该权限),则可以使用上述代码切换移动数据状态。

解决scheme#2

要检查移动数据的当前状态,您可以使用Settings.Globalmobile_data字段(未在官方文档中logging)。

 Settings.Global.getInt(contentResolver, "mobile_data"); 

而要启用/禁用移动数据,您可以在根设备上使用shell命令(只需进行基本testing,以便在评论中提供任何反馈意见)。 您可以以root身份运行以下命令(1 =启用,0 =禁用):

 settings put global mobile_data 1 settings put global mobile_data 0 

我发现su -c 'service call phone 83 i32 1'解决scheme对于固定设备是最可靠的。 感谢Phong Le参考,我通过使用reflection获取供应商/操作系统特定事务代码来改进它。 也许这对其他人有用。 所以,这里是源代码:

  public void changeConnection(boolean enable) { try{ StringBuilder command = new StringBuilder(); command.append("su -c "); command.append("service call phone "); command.append(getTransactionCode() + " "); if (Build.VERSION.SDK_INT >= 22) { SubscriptionManager manager = SubscriptionManager.from(context); int id = 0; if (manager.getActiveSubscriptionInfoCount() > 0) id = manager.getActiveSubscriptionInfoList().get(0).getSubscriptionId(); command.append("i32 "); command.append(String.valueOf(id) + " "); } command.append("i32 "); command.append(enable?"1":"0"); command.append("\n"); Runtime.getRuntime().exec(command.toString()); }catch(IOException e){ ... } } private String getTransactionCode() { try { TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); Class telephonyManagerClass = Class.forName(telephonyManager.getClass().getName()); Method getITelephonyMethod = telephonyManagerClass.getDeclaredMethod("getITelephony"); getITelephonyMethod.setAccessible(true); Object ITelephonyStub = getITelephonyMethod.invoke(telephonyManager); Class ITelephonyClass = Class.forName(ITelephonyStub.getClass().getName()); Class stub = ITelephonyClass.getDeclaringClass(); Field field = stub.getDeclaredField("TRANSACTION_setDataEnabled"); field.setAccessible(true); return String.valueOf(field.getInt(null)); } catch (Exception e) { if (Build.VERSION.SDK_INT >= 22) return "86"; else if (Build.VERSION.SDK_INT == 21) return "83"; } return ""; } 

更新:

我的一些用户报告说,他们通过这种方法打开移动networking有问题(closures工作正确)。 有没有人有解决scheme?

UPDATE2:

经过一些挖掘Android 5.1代码,我发现他们改变了交易的签名。 Android 5.1带来了多SIM的官方支持。 所以,交易需要所谓的订阅ID作为第一个参数( 在这里阅读更多 )。 这种情况的结果是,命令su -c 'service call phone 83 i32 1'不能在Android 5.1上打开移动networking。 所以,Android 5.1上的完整命令应该是这样的su -c 'service call phone 83 i32 0 i32 1'i32 0是subId, i32 1是命令0 off和1 on)。 我已经使用此修复程序更新了上面的代码。

如果你通过将.apk移动到/system/priv-app/文件夹, 而不是 /system/app/ one来创build应用程序“系统”,那么Muzikant的解决scheme#1似乎能够工作(@jaumard:也许这就是为什么你的testing没有工作)。

当.apk位于/system/priv-app/文件夹中时,它可以成功请求Manifest中的可怕android.permission.MODIFY_PHONE_STATE权限,并调用TelephonyManager.setDataEnabledTelephonyManager.getDataEnabled

至less可以在Nexus 5 / Android 5.0上运行。 .apk烫发是0144 。 您需要重新启动设备才能考虑更改,也许这可以避免 – 请参阅此主题 。

我没有足够的评价,但我已经尝试了所有的答案,并发现以下内容:

ChuongPham:我使用了reflection来获取com.android.internal.telephony.ITelephonyvariablesTRANSACTION_setDataEnabled的值,所以它可以在所有Android 5+设备上运行,而不pipe品牌。

Muzikant:如果应用程序移动到/system/priv-app/目录(感谢rgruet ),就可以工作。否则,它也可以通过root进行工作! 您只需通知您的用户,该应用程序将需要重新启动之前,移动networking的变化将发生。

AJ:工作types。 不要closures订阅服务,所以我testing的设备耗尽了他们的电池一点点。 AJ的解决scheme等于Muzikant的解决scheme,尽pipe索赔。 我可以通过debugging不同的三星,索尼和LG股票ROM(我彻底)来证实这一点,并可以驳斥AJ的说法,他的解决scheme与Muzikant相同。 (注意:我无法使用Nexus和Motorola ROM,因此没有使用所提出的解决scheme对这些ROM进行testing。)

无论如何,希望对解决scheme有所怀疑。

快乐的编码! PL,德国

更新 :对于那些想知道如何通过reflection来获取TRANSACTION_setDataEnabled字段的值,可以执行以下操作:

 private static String getTransactionCodeFromApi20(Context context) throws Exception { try { final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); final Class<?> mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName()); final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony"); mTelephonyMethod.setAccessible(true); final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager); final Class<?> mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName()); final Class<?> mClass = mTelephonyStubClass.getDeclaringClass(); final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled"); field.setAccessible(true); return String.valueOf(field.getInt(null)); } catch (Exception e) { // The "TRANSACTION_setDataEnabled" field is not available, // or named differently in the current API level, so we throw // an exception and inform users that the method is not available. throw e; } } 

纠正Muzikant解决scheme#2

 settings put global mobile_data 1 

仅启用移动数据切换,但对连接无效。 只有切换已启用。 为了获得正在使用的数据

 su -c am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1 

给错误作为额外的

 android.intent.action.ANY_DATA_STATE 

需要string对象,而 – ez参数用于布尔值。 Ref:PhoneGlobals.java&PhoneConstants.java。 使用连接或连接后作为额外的使用命令

 su -c am broadcast -a android.intent.action.ANY_DATA_STATE --es state connecting 

仍然没有做任何事情来启用数据。