关于 setMobileDataEnabled()
方法不再可通过反射调用,我已向 Google 记录了 Issue 78084。从 Android 2.1 (API 7) 到 Android 4.4 (API 19) 可以通过反射调用它,但从 Android L 及更高版本开始,即使使用 root,setMobileDataEnabled()
方法也是不可调用的。
官方的回应是问题是“已关闭”,状态设置为“WorkingAsIntended”。谷歌的简单解释是:
Private APIs are private because they are not stable and might disappear without notice.
是的,谷歌,我们已经意识到使用反射来调用隐藏方法的风险——甚至在 Android 出现之前——但你需要提供一个更可靠的答案,以实现与
setMobileDataEnabled()
相同的结果。 (如果你和我一样对谷歌的决定不满意,那就登录 Issue 78084 并尽可能多地给它加星标,让谷歌知道他们的方式错误。)所以,我的问题是:在以编程方式启用或禁用 Android 设备上的移动网络功能时,我们是否处于死胡同?谷歌这种强硬的做法不知何故不适合我。如果您有适用于 Android 5.0 (Lollipop) 及更高版本的解决方法,我很乐意在此线程中听到您的回答/讨论。
我已经使用下面的代码来查看
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());
}
}
但事实并非如此。
更新 :目前,如果设备已 Root ,则可以切换移动网络。但是,对于非 root 设备,这仍然是一个调查过程,因为没有通用的方法来切换移动网络。
最佳答案
为了扩展 Muzikant 的解决方案 #2,有人可以在 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_data
变量可以在 /android-sdk/sources/android-21/android/provider/Settings.java
的 Android API 21 源代码中找到并声明为:/**
* Whether mobile data connections are allowed by the user. See
* ConnectivityManager for more info.
* @hide
*/
public static final String MOBILE_DATA = "mobile_data";
而
android.intent.action.ANY_DATA_STATE
Intent 可以在 /android-sdk/sources/android-21/com/android/internal/telephony/TelephonyIntents.java
的 Android API 21 源代码中找到。并声明为:/**
* 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代码,那么你可以运行
su
通过 shell (Linux) 或命令提示符 (Windows) 的命令如下: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 : 在 上切换移动网络的另一种方法 Root Android 5+ 设备将使用未记录的
service
外壳命令。可以通过 ADB 执行以下命令来切换移动网络:// 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 :交易代码 83 用于
service call phone
命令可能会在 Android 版本之间发生变化。请查看com.android.internal.telephony.ITelephony
对于 TRANSACTION_setDataEnabled
的值您的 Android 版本的字段。此外,而不是硬编码 83 ,您最好使用反射来获取 TRANSACTION_setDataEnabled
的值 field 。这样,它将适用于所有运行 Android 5+ 的移动品牌(如果您不知道如何使用反射来获取 TRANSACTION_setDataEnabled
字段的值,请参阅下面 PhongLe 的解决方案 - 让我不要在这里复制它。) 重要 : 请注意交易代码TRANSACTION_setDataEnabled
仅在 Android 5.0 及更高版本中引入。在早期版本的 Android 上运行此事务代码将不会执行任何操作,因为事务代码 TRANSACTION_setDataEnabled
不存在。注2 :
adb
位于 /android-sdk/platform-tools/
目录。如果您不想使用 ADB,请通过 su
执行该方法在您的应用程序中。注3 :请参阅下面的更新 3。
更新 3 :许多 Android 开发人员通过电子邮件向我发送了有关为 Android 5+ 开启/关闭移动网络的问题,但我不会回复个别电子邮件,而是将我的答案发布在这里,以便每个人都可以使用它并使其适应他们的 Android 应用程序。
首先,让我们澄清一些误解和误解:
svc data enable
svc data disable
上述方法只会打开/关闭后台数据,不是 订阅服务,因此电池会消耗相当多的电量,因为订阅服务(Android 系统服务)仍将在后台运行。对于支持多张 SIM 卡的 Android 设备,这种情况更糟,因为订阅服务会不断扫描可用的移动网络以与 Android 设备中可用的 Activity SIM 卡一起使用。使用此方法需要您自担风险。
现在,关闭移动网络的正确方法,包括通过
SubscriptionManager
的相应订阅服务API 22 中引入的类是: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 i.e. 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;
}
}
检查移动网络是否启用:
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 的解决方案中借用):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;
}
}
}
希望此更新能够消除您对在已 Root 的 Android 5+ 设备上打开/关闭移动网络的任何误解、误解或疑问。
关于android - 从 Android L 及更高版本开始,setMobileDataEnabled 方法不再可调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26539445/