- Settings应用的部分逻辑代码
frameworks/base/packages/SettingsLib/ - Settings数据库代码,独立应用
frameworks/base/packages/SettingsProvider/ - Settings主体代码
packages/apps/Settings - Settins搜索相关代码,独立应用
packages/apps/SettingsIntelligence/
首先查看 AndroidManifest 文件,找到 Settings 的启动页。
<activity-alias android:name="Settings"
android:label="@string/settings_label_launcher"
android:taskAffinity="com.android.settings.root"
android:launchMode="singleTask"
android:theme="@style/Theme.Settings.Main"
android:targetActivity=".homepage.SettingsHomepageActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
</activity-alias>
Settings 是 SettingsHomepageActivity 的别名,查看 Settings.java,其中定义了大量Activity继承 SettingsActivity,直接看 SettingsHomepageActivity.java。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//main activity
setContentView(R.layout.settings_homepage_container);
final View root = findViewById(R.id.settings_homepage_container);
getActionBar().setTitle(R.string.settings_label);
getActionBar().setDisplayHomeAsUpEnabled(false);
//使用 TopLevelSettings 填充 main_content
showFragment(new TopLevelSettings(), R.id.main_content);
((FrameLayout) findViewById(R.id.main_content))
.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
}
查看 settings_homepage_container 主要由两个 FrameLayout 以及一个搜索框构成,设置首页主要是由 main_content 这个帧布局呈现,并由 NestedScrollView 控件包裹,实现滚动效果。
由以上代码可知,首页使用 TopLevelSettings 填充 main_content,所以,主要分析 TopLevelSettings 就可知首页加载流程了。
public class TopLevelSettings extends DashboardFragment implements
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
......
}
TopLevelSettings 继承 DashboardFragment,并实现了 OnPreferenceStartFragmentCallback 接口。
DashboardFragment 是 Settings 模块中重要的基类,主要加载所有的 Preference 的控制类、静态加载 XML 中的 Preference,从 DashboardCategory 动态加载 Preference 等,相当一部分 Fragment 继承于他,后续会重点分析。
OnPreferenceStartFragmentCallback 接口主要在处理 Preference 的点击事件时回调 onPreferenceStartFragment() 方法,一般实现界面的跳转。
TopLevelSettings 构造方法,只是保存了一个状态,不多赘述,后面会按照 Fragment 的生命周期详细分析下加载流程。
onAttach()
public void onAttach(Context context) {
//调用父类方法
super.onAttach(context);
//use(SupportPreferenceController.class).setActivity(getActivity());
//Controller 添加生命周期感知
getSettingsLifecycle().addObserver(use(TopLevelSystemUpdatePreferenceController.class));
//*/
}
DashboardFragment.onAttach()
public void onAttach(Context context) {
super.onAttach(context);
......
// Load preference controllers from code
final List<AbstractPreferenceController> controllersFromCode =
createPreferenceControllers(context);
// Load preference controllers from xml definition
final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
.getPreferenceControllersFromXml(context, getPreferenceScreenResId());
// Filter xml-based controllers in case a similar controller is created from code already.
final List<BasePreferenceController> uniqueControllerFromXml =
PreferenceControllerListHelper.filterControllers(
controllersFromXml, controllersFromCode);
// Add unique controllers to list.
if (controllersFromCode != null) {
mControllers.addAll(controllersFromCode);
}
mControllers.addAll(uniqueControllerFromXml);
//为所有 XML 中获取的 Controller 添加生命周期感知
// And wire up with lifecycle.
final Lifecycle lifecycle = getSettingsLifecycle();
uniqueControllerFromXml.forEach(controller -> {
if (controller instanceof LifecycleObserver) {
lifecycle.addObserver((LifecycleObserver) controller);
}
});
......
for (AbstractPreferenceController controller : mControllers) {
addPreferenceController(controller);
}
}
该方法主要填充了三个 List,分别是
- 从代码中获取的 controllersFromCode
- 从 XMl 文件中获取 controllersFromXml
- XML 中独有的 uniqueControllerFromXml
下面着重分析下前两种方法的具体实现
从代码中获取的 Controllers
controllersFromCode 由 createPreferenceControllers() 方法获取,该方法为抽象方法,由 Fragement 具体实现,因首页未实现此方法,以 AccountDashboardFragment 为例
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
return buildPreferenceControllers(context, this /* parent */, authorities);
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
SettingsPreferenceFragment parent, String[] authorities) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
final AccountPreferenceController accountPrefController =
new AccountPreferenceController(context, parent, authorities,
ProfileSelectFragment.ProfileType.ALL);
if (parent != null) {
//添加生命周期感知
parent.getSettingsLifecycle().addObserver(accountPrefController);
}
controllers.add(accountPrefController);
controllers.add(new AutoSyncDataPreferenceController(context, parent));
controllers.add(new AutoSyncPersonalDataPreferenceController(context, parent));
controllers.add(new AutoSyncWorkDataPreferenceController(context, parent));
controllers.add(new UserPreferenceController(context));
return controllers;
}
设置中大部分Fragment都是如此实现,通过 buildPreferenceControllers() 方法创建并返回需要的所有 Controller 集合。需要注意的是,创建 Controller 时如果需要感知生命周期就需要在此添加。
从 XMl 文件中获取 Controllers
首先通过 getPreferenceScreenResId 获取资源 id,该方法也是抽象方法,由 Fragment 具体实现。TopLevelSettings 实现如下:
protected int getPreferenceScreenResId() {
return R.xml.top_level_settings;
}
很简单,返回 xml 文件索引。该文件列出了首页大部分配置,之所以是大部分,不是全部,是因为有一部分通过 DashboardCategory 加载,后续会讲到,此处以显示设置为例,配置如下:
<Preference
android:fragment="com.android.settings.DisplaySettings"
android:icon="@drawable/ic_homepage_display"
android:key="top_level_display"
android:order="-170"
android:summary="@string/summary_placeholder"
android:title="@string/display_settings"
android:widgetLayout="@layout/preference_widget_arrow"
settings:controller="com.android.settings.display.TopLevelDisplayPreferenceController" />
- fragment: 配置点击该 preference 后需要跳转的 fragement
- icon: 配置该 preference 图标
- key: 定义此 preference 唯一id
- order: 显示优先级,值越小,越靠前
- summary: 配置副标题,一般显示状态
- title: 配置标题,此处为显示与亮度
- widgetLayout: 控件布局,此处为自定义控件,一个小箭头
- controller: 配置控制管理类,一般控制 preference 的显示,点击状态,summary 显示等
获取到 res id 后通过 PreferenceControllerListHelper .getPreferenceControllersFromXml(context, getPreferenceScreenResId()) 来加载 XML 文件中的 Controller
public static List<BasePreferenceController> getPreferenceControllersFromXml(Context context,
@XmlRes int xmlResId) {
final List<BasePreferenceController> controllers = new ArrayList<>();
List<Bundle> preferenceMetadata;
try {
//使用工具类读取 XML 文件中的元素值
preferenceMetadata = PreferenceXmlParserUtils.extractMetadata(context, xmlResId,
MetadataFlag.FLAG_NEED_KEY | MetadataFlag.FLAG_NEED_PREF_CONTROLLER
| MetadataFlag.FLAG_INCLUDE_PREF_SCREEN | MetadataFlag.FLAG_FOR_WORK);
} catch (IOException | XmlPullParserException e) {
Log.e(TAG, "Failed to parse preference xml for getting controllers", e);
return controllers;
}
for (Bundle metadata : preferenceMetadata) {
final String controllerName = metadata.getString(METADATA_CONTROLLER);
if (TextUtils.isEmpty(controllerName)) {
continue;
}
BasePreferenceController controller;
try {
//调用仅有一个参数的构造方法创建实例,子类中无此构造方法抛出异常
controller = BasePreferenceController.createInstance(context, controllerName);
} catch (IllegalStateException e) {
Log.d(TAG, "Could not find Context-only controller for pref: " + controllerName);
final String key = metadata.getString(METADATA_KEY);
final boolean isWorkProfile = metadata.getBoolean(METADATA_FOR_WORK, false);
if (TextUtils.isEmpty(key)) {
Log.w(TAG, "Controller requires key but it's not defined in xml: "
+ controllerName);
continue;
}
try {
//调用有两个参数的构造方法创建实例,子类中无此构造方法抛出异常
controller = BasePreferenceController.createInstance(context, controllerName,
key, isWorkProfile);
} catch (IllegalStateException e2) {
Log.w(TAG, "Cannot instantiate controller from reflection: " + controllerName);
continue;
}
}
controllers.add(controller);
}
return controllers;
}
- PreferenceXmlParserUtils.extractMetadata() 中主要读取 xml 中配置的每个 preference 的 METADATA_CONTROLLER, KEY, FOR_WORK以上述显示与亮度菜单项为例,读取的即为 “com.android.settings.display.TopLevelDisplayPreferenceController”, “top_level_display”, false。
- 之后遍历 preferenceMetadata 去添加 Controller,先调用 BasePreferenceController.java 的 createInstance 方法去创建 Controller 实例,这里用到反射机制,即调用 TopLevelDisplayPreferenceController.java 的带一个参数的构造方法,看下 TopLevelDisplayPreferenceController 的构造函数:
//只有两个参数的构造方法
public TopLevelDisplayPreferenceController(Context context, String preferenceKey) {
//调用父类构造器
super(context, preferenceKey);
}
- 因为 TopLevelDisplayPreferenceController 只有两个参数的构造方法,所以此次反射创建实例会抛出异常,继续往下看,后去解析出来的 key 和 isWorkProfile ,然后以此再次通过 controllerName 反射创建实例,这次调用两个参数的构造器,分别是 context 和 key ,与 TopLevelDisplayPreferenceController 吻合。
public static BasePreferenceController createInstance(Context context, String controllerName,
String key, boolean isWorkProfile) {
......
final Constructor<?> preferenceConstructor =
clazz.getConstructor(Context.class, String.class);
final Object[] params = new Object[]{context, key};
final BasePreferenceController controller =
(BasePreferenceController) preferenceConstructor.newInstance(params);
controller.setForWork(isWorkProfile);
return controller;
......
}
至此,通过 XML 文件加载全部 Controller 完成,总结就是,工具类解析文件,获取 Controller 子串,通过这个子串去创建实例并添加到 List 中。
之后将所有 Controller 通过 addPreferenceController() 方法,传给 mPreferenceControllers;
private final Map<Class, List<AbstractPreferenceController>> mPreferenceControllers =
new ArrayMap<>();
protected void addPreferenceController(AbstractPreferenceController controller) {
if (mPreferenceControllers.get(controller.getClass()) == null) {
mPreferenceControllers.put(controller.getClass(), new ArrayList<>());
}
mPreferenceControllers.get(controller.getClass()).add(controller);
}
PreferenceControllers 是每个 Preference 的管理类,控制每个 Preference 的显示,状态,点击事件等。,Settings 中的所有 PreferenceControllers 按照 Class-Controller_list 的键值对方式存入 mPreferenceControllers 中。