Android Lollipop (5.0) 原生代码 Settings 首页加载逻辑分析

本文详细剖析了Android系统的设置应用启动过程,从主入口类Settings开始,逐步深入到DashboardSummary的加载及其触发的UI重建过程。通过跟踪关键函数如buildDashboardCategories和rebuildUI,揭示了设置页面内容加载的核心机制。
主入口为com.android.settings.Settings. 这只是一个wrapper的类, 它继承于 SettingsActivity类,并且声明了一堆公有的继承于SettingsActivity的类作为内部类。 例如:
public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
public static class WirelessSettingsActivity extends SettingsActivity { /* empty */ }
public static class SimSettingsActivity extends SettingsActivity { /* empty */ }
所以,来看SettingsActivity。 这个类里面有很多的方法, 我们一开始并不知道哪些是在初始化时有用的。所以先全部打上断点。 然后点击Launcher里面的Settings开始断点调试。 命中的函数断点并不算太多,我们会注意到这个函数: buildDashboardCategories, 在这个函数被调用以后,界面差不多就加载好了。 事实上,这个函数会调用 loadCategoriesFromResource这个函数。而这个函数里面看起来像是在加载XML资源。 现在, 终点大概找到了。 下面再来理清楚它是怎么逐步调用的。 来看命中buildDashboardCategories时候的堆栈。 可以看到, 起源在于DashboardSummary里面的rebuildUI的函数。 而这个函数是由一个handler调用起来的。
 
 
查找这个handler如下:

private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REBUILD_UI: {
final Context context = getActivity();
rebuildUI(context);
} break;
}
}
};

这个私有的handler,在当前文件可以找到调用方: sendRebuildUI(),在这里打断点,察看堆栈,会发现它是由DashboardSummary的onResume()方法调用的。 再来看这个类的
定义: DashboardSummary extends Fragment。 是一个Fragment。 所以它是在这个Fragment加载时候触发的。 Fragment的加载,要么是在XML文件里面定义好了,要么是用
代码在运行时加载的。 所以应该去察看SetttingsActivity的UI加载部分,也就是要找setContentView函数。 我们找到了这段代码:

setContentView(mIsShowingDashboard ?
                R.layout.settings_main_dashboard : R.layout.settings_main_prefs);

经过断点调试,可以看到这里调用的是R.layout.settings_main_dashboard。 来看它的布局:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_content"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="@color/dashboard_background_color"
/>

很简单,只有一个frameLayout, id为main_content, 并不是我们期望的Fragment。 在onCreate里面继续往下找, 或者搜索DashboardSummary(因为在布局里面没有看到
DashboardSummary, 所以必然Activity用代码的方式调用了DashboardSummary), 很容易找到了switchToFragment函数:

调用:
switchToFragment(DashboardSummary.class.getName(), null, false, false,
                        mInitialTitleResId, mInitialTitle, false);

这个函数代码不多, 看起来容易, 里面有几句:

Fragment f = Fragment.instantiate(this, fragmentName, args);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.main_content, f);

看到这里就差不多连起来了。 在SettingsActivity里面的onCreate方法里面, 通过调用这个函数, 把名为main_content的对象替换成了DashboardSummary的实例。 继而激活
了这个fragment的onResume-> rebuildUI() -> SettingsActivity里面的buildDashboardCategories() -> 解析XML文件dashboard_categories.xml, 从而获得
Settings主页该显示哪些内容。

如果有兴趣的可以再仔细看一下这个xml和loadCategoriesFromResource函数,看加载了哪些内容。 我想这里可以分享的经验主要是如何面对一段未知的代码来理清楚它的
逻辑走向。 调用堆栈和Call hierarchy都是很好的辅助工具(Call Hierarchy在Android Studio里面的 Navigate-> Call Hierarchy)

欢迎转载,转载请注明出处。如果觉得有一丁点的帮助请点击一下推荐。多谢。


原文地址:http://www.cnblogs.com/zzpbuaa/p/5301965.html

<think>在Android中,关闭移动数据需要应用程序具有系统级别的权限(如MODIFY_PHONE_STATE),该权限仅授予系统应用。普通应用无法直接关闭移动数据。 但是,对于有root权限的设备或系统应用,可以使用以下方法: 1. 使用ConnectivityManager(仅系统应用可用) 2. 使用反射(需要系统签名,且在高版本Android上可能失效) 3. 使用TelephonyManager(需要系统权限) 由于普通应用无法直接关闭移动数据,常见的做法是引导用户进入设置界面手动关闭。 以下给出几种方案: </think>以下是 Android 中关闭移动数据的完整实现方案。需要注意的是,直接修改移动数据状态需要系统级权限,普通应用通常无法直接实现此功能,除非是系统应用或具有特殊权限。 ### 解决方案 1:使用系统设置(需要引导用户手动操作) ```java import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.provider.Settings; public class MobileDataUtils { // 打开移动数据设置页面 public static void openMobileDataSettings(Context context) { Intent intent; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // Android 9.0+ intent = new Intent(Settings.ACTION_DATA_USAGE_SETTINGS); } else { // 旧版Android intent = new Intent(); intent.setClassName("com.android.settings", "com.android.settings.Settings$DataUsageSummaryActivity"); } context.startActivity(intent); } // 检查移动数据是否启用 public static boolean isMobileDataEnabled(Context context) { try { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (cm == null) return false; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return !cm.isActiveNetworkMetered(); } else { // 使用反射检查旧版设备 try { Class<?> cmClass = Class.forName(cm.getClass().getName()); Method method = cmClass.getDeclaredMethod("getMobileDataEnabled"); method.setAccessible(true); return (boolean) method.invoke(cm); } catch (Exception e) { e.printStackTrace(); return false; } } } catch (Exception e) { e.printStackTrace(); return false; } } } ``` ### 解决方案 2:使用系统权限(需要系统应用签名) ```java import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkPolicyManager; import android.provider.Settings; import java.lang.reflect.Method; public class MobileDataController { // 关闭移动数据(需要系统权限) public static boolean disableMobileData(Context context) { return setMobileDataEnabled(context, false); } // 启用移动数据(需要系统权限) public static boolean enableMobileData(Context context) { return setMobileDataEnabled(context, true); } // 设置移动数据状态 private static boolean setMobileDataEnabled(Context context, boolean enabled) { try { // 方法1: 使用ConnectivityManager (API 21+) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (cm == null) return false; try { Method setMobileDataEnabled = ConnectivityManager.class.getDeclaredMethod("setMobileDataEnabled", boolean.class); setMobileDataEnabled.setAccessible(true); setMobileDataEnabled.invoke(cm, enabled); return true; } catch (Exception e) { // 备选方法 } } // 方法2: 使用TelephonyManager (旧版本) try { TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); if (tm == null) return false; Method setDataEnabled = TelephonyManager.class.getDeclaredMethod("setDataEnabled", boolean.class); setDataEnabled.setAccessible(true); setDataEnabled.invoke(tm, enabled); return true; } catch (Exception e) { e.printStackTrace(); } // 方法3: 修改系统设置(需要WRITE_SECURE_SETTINGS权限) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { Settings.Secure.putInt(context.getContentResolver(), "mobile_data", enabled ? 1 : 0); return true; } } catch (Exception e) { e.printStackTrace(); } return false; } } ``` ### 解决方案 3:使用 DevicePolicyManager(设备管理应用) ```java import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; public class MobileDataAdmin { // 设备管理员接收器 public static class AdminReceiver extends DeviceAdminReceiver { // 空实现 } // 检查是否激活设备管理员 public static boolean isAdminActive(Context context) { DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); ComponentName admin = new ComponentName(context, AdminReceiver.class); return dpm.isAdminActive(admin); } // 请求设备管理员权限 public static void requestAdminPermission(Activity activity, int requestCode) { Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, new ComponentName(activity, AdminReceiver.class)); activity.startActivityForResult(intent, requestCode); } // 禁用移动数据(需要设备管理员权限) public static boolean disableMobileData(Context context) { if (!isAdminActive(context)) return false; try { DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); // 此方法需要自定义策略实现 // 实际上Android原生DevicePolicyManager不直接支持禁用移动数据 // 需要自定义设备策略控制器 // 替代方案:禁用所有网络 // dpm.setGlobalSetting(dpm, Settings.Global.AIRPLANE_MODE_ON, "1"); return true; } catch (Exception e) { e.printStackTrace(); return false; } } } ``` ### 解决方案 4:使用 ADB 命令(需要 root 权限) ```java import java.io.DataOutputStream; import java.io.OutputStream; public class RootUtils { // 使用su命令执行命令 public static boolean executeCommand(String command) { try { Process process = Runtime.getRuntime().exec("su"); OutputStream os = process.getOutputStream(); DataOutputStream dos = new DataOutputStream(os); dos.writeBytes(command + "\n"); dos.writeBytes("exit\n"); dos.flush(); int exitCode = process.waitFor(); return exitCode == 0; } catch (Exception e) { e.printStackTrace(); return false; } } // 关闭移动数据(需要root) public static boolean disableMobileData() { String command = ""; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Android 6.0+ command = "svc data disable"; } else { // 旧版Android command = "service call phone 115 i32 0"; } return executeCommand(command); } } ``` ### AndroidManifest.xml 权限配置 ```xml <!-- 基本权限 --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.INTERNET"/> <!-- 系统权限(需要系统应用签名) --> <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" tools:ignore="ProtectedPermissions" /> <!-- 设备管理员声明 --> <receiver android:name=".MobileDataAdmin$AdminReceiver" android:description="@string/admin_description" android:label="@string/admin_label" android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="android.app.device_admin" android:resource="@xml/device_admin" /> <intent-filter> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> </intent-filter> </receiver> ``` ### 设备管理员策略文件 (res/xml/device_admin.xml) ```xml <?xml version="1.0" encoding="utf-8"?> <device-admin xmlns:android="http://schemas.android.com/apk/res/android"> <uses-policies> <disable-network /> </uses-policies> </device-admin> ``` ### 代码解释 1. **系统设置方法**: - 引导用户到设置界面手动关闭移动数据 - 使用 `isMobileDataEnabled()` 检查状态 - 兼容新旧Android版本 2. **系统权限方法**: - 使用反射调用隐藏API - 需要应用使用系统证书签名 - 支持 Android 5.0+ 设备 3. **设备管理员方法**: - 需要用户授权设备管理员权限 - 可以通过 `DevicePolicyManager` 控制网络状态 - 需要声明设备管理员组件 4. **ADB/Root 方法**: - 使用 `su` 命令执行系统指令 - 需要设备已 root - 使用 `svc data disable` 命令关闭移动数据 ### 实际应用建议 对于大多数普通应用,推荐使用方案1(引导用户到设置界面)。以下是在 Activity 中的完整实现示例: ```java public class MainActivity extends AppCompatActivity { private static final int REQUEST_CODE_ENABLE_ADMIN = 100; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 检查移动数据状态 TextView statusText = findViewById(R.id.status_text); statusText.setText("移动数据状态: " + (MobileDataUtils.isMobileDataEnabled(this) ? "已启用" : "已禁用")); // 打开设置按钮 findViewById(R.id.open_settings_btn).setOnClickListener(v -> { MobileDataUtils.openMobileDataSettings(this); }); // 请求设备管理员权限 findViewById(R.id.request_admin_btn).setOnClickListener(v -> { MobileDataAdmin.requestAdminPermission(this, REQUEST_CODE_ENABLE_ADMIN); }); // 尝试关闭移动数据(需要root) findViewById(R.id.disable_data_btn).setOnClickListener(v -> { if (RootUtils.disableMobileData()) { Toast.makeText(this, "移动数据已关闭", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "操作失败,可能需要root权限", Toast.LENGTH_SHORT).show(); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_ENABLE_ADMIN) { if (MobileDataAdmin.isAdminActive(this)) { if (MobileDataAdmin.disableMobileData(this)) { Toast.makeText(this, "已使用设备管理员权限关闭移动数据", Toast.LENGTH_SHORT).show(); } } } } } ``` ### 重要注意事项 1. **权限限制**: - 普通应用无法直接修改移动数据状态 - `MODIFY_PHONE_STATE` 是系统级权限 - 设备需要 root 或系统签名 2. **Android 版本差异**: - Android 10+ 限制了反射调用 - Android 11+ 需要特殊权限才能使用 `svc` 命令 - 最新 Android 版本限制更多系统设置修改 3. **替代方案**: ```java // 禁用特定应用的移动数据使用 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); cm.setAppRestrictBackground(uid, true); } ``` 4. **最佳实践**: - 优先引导用户到设置界面 - 对于企业设备管理应用,使用设备管理员API - 对于系统应用,使用系统级权限方法
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值