主入口为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)