Android L Settings界面结构简单分析
Settings是Android系统很重要的模块,这部分也一直在看,很多时候都是在看某个具体的选项,比如WLAN,蓝牙这样具体的源码,但是对于主界面的布局以及结构并不清楚。
在使用Hierarchy Viewer工具可以看到Settings模块的主界面显示的是Settings,
com.android.settings/com.android.settings.Settings
在进入设置的子界面的时候,显示永远是SubSettings,
com.android.settings/com.android.settings.SubSettings
这样我就感觉有点奇怪,为什么所有的子界面显示的都是SubSettings?
这几天搜了相关资料和结合源码看了一下这部分的逻辑,简单分析如下。
- Settings主界面Activity使用的是Settings
- Settings子界面Activity基本上都是使用SubSettings
- Settings与SubSettings中都是空Activity,这里的空Activity指的是没有重写7大生命周期方法
- Settings与SubSettings都是继承于SettingsActivity
主界面使用的layout是:settings_main_dashboard,子界面使用的layout是:settings_main_prefs
在SettingsActivity的onCreate方法中,通过判断当前是Settings还是SubSettings来确定用什么布局来显示
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
.....
mIsShowingDashboard = className.equals(Settings.class.getName());
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
....
....
}
这里会有两个问题,Settings作为主界面,加载的是settings_main_dashboard.xml文件,下面是这个xml文件具体内容
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/dashboard_background_color" />
如果都是这个布局的话,那么主界面的不同item是如何显示出来的呢?
主界面settings_main_dashboard中是使用DashboardSummary(Fragment)进行填充。由于设置的主界面Settings和设置的子界面SubSettings都是继承于SettingsActivity的,Settings是和SubSettings里面是空的,所以会执行父类SettingsActivity的onCreate方法。
在SettingsActivity的onCreate方法后面,在这里会switchToFragment进行替换,
将其替换为DashboardSummary。DashboardSummary同样是一个Fragment,通过mIsShowingDashboard判断是否将DashboardSummary替换过来,而这里mIsShowingDashboard的值是通过判断当前是Settings还是SubSettings来获得的。
下面是switchToFragment方法的部分源码:
if (savedState != null) {
.....
.....
} else {
if (!mIsShowingDashboard) {
......
......
switchToFragment(initialFragmentName, initialArguments, true, false,mInitialTitleResId, mInitialTitle, false);
} else {
switchToFragment(DashboardSummary.class.getName(), null, false, false,mInitialTitleResId, mInitialTitle, false);
}
}
然后去看DashboardSummary这个类,它是一个Fragment。DashboardSummary继承了PreferenceFragment,并且在它的onCreateView方法中,加载的XML布局是dashboard.xml,dashboard.xml的布局如下,使用ScrollView嵌套了一个竖直的线性布局,这样,设置的主界面就是可以滚动的垂直的线性结构。
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dashboard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbarStyle="outsideOverlay"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/dashboard_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:paddingStart="@dimen/dashboard_padding_start"
android:paddingEnd="@dimen/dashboard_padding_end"
android:paddingTop="@dimen/dashboard_padding_top"
android:paddingBottom="@dimen/dashboard_padding_bottom"
android:clipToPadding="false"
android:orientation="vertical"
/>
</ScrollView>
这样,设置主页面Settings的整体结构就出来了,接下来看这界面的选项列表是如何加载出来的。
注意到在DashboardSummary的onResume方法中,有个方法为sendRebuildUI,这个方法通过Handler发送Message来通知界面更新UI,更新UI的方法为rebuildUI。
在rebuildUI这个方法中,通过调用SettingsActivity中的getDashboardCategories来获得主界面的选项列表,在SettingsActivity中的getDashboardCategories方法中,通过调用buildDashboardCategories来从布局文件中将Settings主界面的选项解析出来,这个布局为dashboard_categories.xml。下面截取的是dashboard_categories.xml部分布局文件,dashboard-category节点表示大的选项的分类,dashboard-tile则表示的是具体的选项比如wifi,蓝牙等。
上面红框圈住的为DashboardTitle,下面红框圈住的为DashboardTitleView,DashboardTitle对应的是xml布局中的dashboard-category这个节点,而DashboardTitleView则对应的是dashboard-title这个节点。
<?xml version="1.0" encoding="utf-8"?>
<dashboard-categories
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- WIRELESS and NETWORKS -->
<dashboard-category
android:id="@+id/wireless_section"
android:key="@string/category_key_wireless"
android:title="@string/header_category_wireless_networks" >
<!-- Wifi -->
<dashboard-tile
android:id="@+id/wifi_settings"
android:title="@string/wifi_settings_title"
android:fragment="com.android.settings.wifi.WifiSettings"
android:icon="@drawable/ic_settings_wireless"
/>
.....
.....
</dashboard-category>
</dashboard-categories>
通过以上一系列的操作,Settings模块的主界面的布局就加载出来了。
在Settings类中,定义了大量静态的内部类,但是都是空的,并未实现。
定义的这些静态内部类主要用于跳转用的,比如从SystemUI跳转至Settings的某一个页面,另外这些静态内部类在AndroidManifest.xml文件中通过meta-data将相应的Fragment绑定起来。
<activity android:name="Settings$WirelessSettingsActivity"
android:taskAffinity="com.android.settings"
android:label="@string/wireless_networks_settings_title"
android:parentActivityName="Settings">
<intent-filter android:priority="1">
<action android:name="android.settings.WIRELESS_SETTINGS" />
<action android:name="android.settings.AIRPLANE_MODE_SETTINGS" />
<action android:name="android.settings.NFC_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.VOICE_LAUNCH" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.WirelessSettings" />
<meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID"
android:resource="@id/wireless_settings" />
<!-- Note that this doesn't really show any Wireless settings. -->
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
这里是如何通过meta-data将相应的Fragment进行绑定的?
我们首先看一下Settings应用的Launcher类。查看package/app/Settings/AndroidManifest.xml文件:
<activity-alias android:name="Settings"
android:taskAffinity="com.android.settings"
android:label="@string/settings_label_launcher"
android:launchMode="singleTask"
android:targetActivity="Settings">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category