• 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 中。