Chu*_*ham 47 reflection android android-5.0-lollipop
我已经向Google报告了问题78084,因为该setMobileDataEnabled()方法不再可以通过反射调用.它可以通过反射从Android 2.1(API 7)到Android 4.4(API 19)进行调用,但是从Android L及更高版本开始,即使使用root,该setMobileDataEnabled()方法也不可调用.
官方回应是问题是"已关闭",状态设置为"WorkingAsIntended".谷歌的简单解释是:
私有API是私有的,因为它们不稳定,可能会在没有通知的情况下消失.
是的,谷歌,我们意识到使用反射调用隐藏方法的风险 - 甚至在Android出现之前 - 但是你需要提供一个更加可靠的答案,如果有的话,可以实现相同的结果setMobileDataEnabled().(如果您对Google的决定不满意,请登录问题78084,并尽可能多地将其加注,以便让Google知道他们的错误.)
所以,我的问题是:在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());
}
}
Run Code Online (Sandbox Code Playgroud)
但事实并非如此.
更新:目前,如果设备已植根,则可以切换移动网络.但是,对于非根设备,它仍然是一个调查过程,因为没有通用的方法来切换移动网络.
Chu*_*ham 25
要扩展Muzikant的解决方案#2,有人可以在Android 5.0的root设备上尝试以下解决方案(因为我目前没有这个解决方案)并告诉我它是否有效.
要启用或停用移动数据,请尝试:
// 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
Run Code Online (Sandbox Code Playgroud)
注意:该mobile_data变量可以在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";
Run Code Online (Sandbox Code Playgroud)
虽然android.intent.action.ANY_DATA_STATEIntent可以在Android API 21源代码中找到/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";
Run Code Online (Sandbox Code Playgroud)
更新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'"
Run Code Online (Sandbox Code Playgroud)
注意:adb位于/android-sdk/platform-tools/目录中.该settings命令仅在Android 4.2或更高版本上受支持.较旧的Android版本将报告"sh: settings: not found"错误.
更新2:在有根的 Android 5+设备上切换移动网络的另一种方法是使用未记录的serviceshell命令.可以通过ADB执行以下命令来切换移动网络:
// 1: Enable; 0: Disable
adb shell "su -c 'service call phone 83 i32 1'"
Run Code Online (Sandbox Code Playgroud)
要不就:
// 1: Enable; 0: Disable
adb shell service call phone 83 i32 1
Run Code Online (Sandbox Code Playgroud)
注1:命令中使用的事务代码83service call phone可能会在Android版本之间发生变化.请检查您的Android版本字段com.android.internal.telephony.ITelephony的值 TRANSACTION_setDataEnabled.此外,您最好使用Reflection来获取字段的值,而不是硬编码83TRANSACTION_setDataEnabled.通过这种方式,它将适用于运行Android 5+的所有移动品牌(如果您不知道如何使用Reflection来获取该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
Run Code Online (Sandbox Code Playgroud)
上述方法只能打开/关闭后台数据,而不是订阅服务,因此电池将耗尽一点点,因为订阅服务 - 一个Android系统服务 - 仍将在后台运行.对于支持多个SIM卡的Android设备,这种情况更糟,因为订阅服务会不断扫描可用的移动网络,以便与Android设备中提供的有效SIM卡一起使用.使用此方法需要您自担风险.
现在,通过SubscriptionManagerAPI 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;
}
}
Run Code Online (Sandbox Code Playgroud)
要检查移动网络是否已启用:
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;
}
Run Code Online (Sandbox Code Playgroud)
为了获得该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;
}
}
Run Code Online (Sandbox Code Playgroud)
要执行命令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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
希望此更新能够解决您在有根据的Android 5+设备上开启/关闭移动网络时可能遇到的任何误解,误解或疑问.
我注意到ChuongPham发布的服务调用方法并不能在所有设备上保持一致.
我发现以下解决方案,我认为在所有ROOTED设备上都可以正常工作.
通过su执行以下操作
启用移动数据
svc data enable
Run Code Online (Sandbox Code Playgroud)
禁用移动数据
svc data disable
Run Code Online (Sandbox Code Playgroud)
我认为这是最简单和最好的方法.
编辑:2个downvotes是我认为是商业原因.这个人现在删除了他的评论.自己动手吧,它有效!在评论中也确认了由男性工作.
只是为了分享更多的见解和可能的解决方案(对于有根设备和系统应用程序).
解决方案#1
似乎该setMobileDataEnabled方法不再存在,ConnectivityManager并且此功能已移至TelephonyManager使用两种方法getDataEnabled和setDataEnabled.我尝试用反射调用这些方法,如下面的代码所示:
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;
}
Run Code Online (Sandbox Code Playgroud)
执行代码时,您会得到一个SecurityException说明Neither user 10089 nor current process has android.permission.MODIFY_PHONE_STATE.
所以,是的,这是对内部API的预期更改,并且不再适用于在先前版本中使用该hack的应用程序.
(开始咆哮:那个可怕的android.permission.MODIFY_PHONE_STATE权限......结束咆哮).
好消息是,如果您正在构建一个可以获得MODIFY_PHONE_STATE权限的应用程序(只有系统应用程序可以使用它),您可以使用上面的代码来切换移动数据状态.
解决方案#2
要检查移动数据的当前状态,您可以使用以下mobile_data字段Settings.Global(未在官方文档中记录).
Settings.Global.getInt(contentResolver, "mobile_data");
Run Code Online (Sandbox Code Playgroud)
要启用/禁用移动数据,您可以在root设备上使用shell命令(只需执行基本测试,以便评论中的任何反馈).您可以以root身份运行以下命令(1 =启用,0 =禁用):
settings put global mobile_data 1
settings put global mobile_data 0
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
36859 次 |
| 最近记录: |