Android系统源码中,Settings模块的功能多达二三十个,包括wifi、蓝牙、nfc、屏幕设置等等重要功能模块,可以说十分庞大。又因为各个子模块又能够通过桌面添加快捷方式等形式进入对应模块的设置界面,因此其实现机制有些独特。Android7.0由于在设置中加入了抽屉,因此界面加载部分代码有所变化。
先从Androidmanifest.xml看起,发现程序入口是Settings类,其继承自SettingsActivity,且没有重写任何生命周期函数,只是定义一些空实现的内部类外,作用是当创建快捷方式时启动独立的类。
下面看SettingsActivity,它集成自SettingsDrawerActivity,这是7.0增加的。从onCreate方法看起,
首先是getMetaData():
private void getMetaData() {
try {
ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
if (ai == null || ai.metaData == null) return;
mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS); } catch (NameNotFoundException nnfe) { // No recovery
Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
}
}
该方法在加载对应的设置项时会从Androidmanifest中声明的Activity的<meta-data>节点下获取到fragmentclass的值。
接着判断是否为四个Categories:
mIsShowingDashboard = className.equals(Settings.class.getName())
|| className.equals(Settings.WirelessSettings.class.getName())
|| className.equals(Settings.DeviceSettings.class.getName())
|| className.equals(Settings.PersonalSettings.class.getName())
|| className.equals(Settings.WirelessSettings.class.getName());
根据mIsShowingDashboard的布尔值,加载对应的布局
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
这里有两种布局,主面板对应settings_main_dashboard布局,子项对应settings_main_prefs,下面主要分析主面板的布局加载方式。
接着往下看:
switchToFragment(DashboardSummary.class.getName(), null, false, false,
mInitialTitleResId, mInitialTitle, false);
若加载项为主面板,则转换到DashboardSummary这个fragment,跟着转到该类,看到onViewCreated函数,有个rebuildUI():
List<DashboardCategory> categories =
((SettingsActivity)getActivity()).getDashboardCategories();
mAdapter.setCategories(categories);
发现它是通过getDashboardCategories()方法获取到categories(设置主面板上的条目)的。而getDashboardCategories()是在SettingsActivity 的父类SettingsDrawerActivity里实现的:
public List<DashboardCategory>getDashboardCategories() {
if (sDashboardCategories == null) {
sTileCache = new HashMap<>();
sConfigTracker = newInterestingConfigChanges();
// Apply initial current config.
sConfigTracker.applyNewConfig(getResources());
sDashboardCategories =TileUtils.getCategories(this, sTileCache);
}
return sDashboardCategories;
}
在这里又通过TileUtils.getCategories(this,sTileCache)方法加载sDashboardCategories,继续查看该方法,
又发现是通过getTilesForIntent方法获取:
public static void getTilesForIntent(Context context, UserHandle user, Intent intent,
Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,boolean usePriority, boolean checkCategory) {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
PackageManager.GET_META_DATA, user.getIdentifier());
for (ResolveInfo resolved : results) {
•••
ActivityInfo activityInfo = resolved.activityInfo;
Bundle metaData = activityInfo.metaData;
String categoryKey = defaultCategory;
•••
Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
activityInfo.name);
Tile tile = addedCache.get(key);
if (tile == null) {
tile = new Tile();
tile.intent = new Intent().setClassName(
activityInfo.packageName, activityInfo.name);
tile.category = categoryKey;
tile.priority = usePriority ? resolved.priority : 0;
tile.metaData = activityInfo.metaData;
updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
pm);
/// M: Drawer plugin support @{
•••
}
}
首先通过getPackageManager().queryIntentActivitiesAsUser(intent,
PackageManager.GET_META_DATA, user.getIdentifier())
(queryIntentActivitiesAsUser在frameworks.base.core.java.android.content.pm.PackageManager中定义)
获取到List<ResolveInfo> results,再通过resolved.activityInfo获取到activityInfo,最后通过new Tile对象,将activityInfo里的信息填充到tile中。(Tile对象对应主面板上其中一项,如wifi项,其icon和title都在metaData中,7.0之前获取这些数据的步骤是从xml文件中读取的)
看到这里,基本上已经明白主面板上的界面是如何加载的了,具体的布局加载在rebuildUI()中的
mAdapter 的onBindViewHolder里面实现的。