Android 8.0 Settings流程分析与变动
一,相比Android Settings 7.0
如下图,在7.0的基础上,去掉了7.0新加的侧滑菜单(可能是觉得有点鸡肋吧)。多加了一级页面,把原来类别标题变成的第一级菜单的子项。在代码架构也稍加变动,并引入架构组件之LifeCycle(生命周期感知,本文不作介绍)。
二,第一级菜单的加载
浏览源码,大多数我们从程序的AndroidManifest.xml入手,这次也不列外。
packages\apps\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 android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
- </activity-alias>
找到<category android:name="android.intent.category.LAUNCHER" />所属的类,Settings.java。但打开Settings.java类看,除了大量静态类继承SettingsActivity,就没什么东西了。那再去父类SettingsActivity.java找找。
packages\apps\Settings\src\com\android\settings\SettingsActivity.java:
首先当然是onCreate()->
- @Override
- protected void onCreate(Bundle savedState) {
- super.onCreate(savedState);
- long startTime = System.currentTimeMillis();
- //工厂类实现方法com.android.settings.overlay.FeatureFactoryImpl.java
- final FeatureFactory factory = FeatureFactory.getFactory(this);
- //获取菜单信息的工厂类,实现类为DashboardFeatureProviderImpl.java
- mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);
- mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
- // 从intent信息中获取<meta-data/>标签名为"com.android.settings.FRAGMENT_CLASS"的值(下文用于加载Fragment的类名)
- getMetaData();
- ... ...
- //获取上面getMetaData()得到的类名
- final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
- //是否为快捷进入方式(如从其它的应用进入Settings的某个设置项)
- mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
- intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);
- ... ...
- //当前类是否为Settings.class,即进入方式为点击launcher上的图标进入的主设置界面
- mIsShowingDashboard = className.equals(Settings.class.getName());
- ... ...
- setContentView(mIsShowingDashboard ?
- R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
- mContent = findViewById(R.id.main_content);
- getFragmentManager().addOnBackStackChangedListener(this);
- if (savedState != null) {
- ... ...
- } else {
- //加载布局
- launchSettingFragment(initialFragmentName, isSubSettings, intent);
- }
- ... ...
- }
launchSettingFragment()->
- @VisibleForTesting
- void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) {
- if (!mIsShowingDashboard && initialFragmentName != null) {
- ... ...
- switchToFragment(initialFragmentName, initialArguments, true, false,
- mInitialTitleResId, mInitialTitle, false);
- } else {
- // Show search icon as up affordance if we are displaying the main Dashboard
- mDisplayHomeAsUpEnabled = true;
- mInitialTitleResId = R.string.dashboard_title;
- //进入主页走的这里,替换目标<span style="font-size:12px;">Fragment</span>
- switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false,
- mInitialTitleResId, mInitialTitle, false);
- }
- }
继续,我们看看DashboardSummary.java,对于它我们主要是想知道它的数据加载,它是怎么加载自己的子项的。
packages\apps\Settings\src\com\android\settings\dashboard\DashboardSummary.java:
对子项的数据获取在updateCategoryAndSuggestion()中得到实现。
- @VisibleForTesting
- void updateCategoryAndSuggestion(List<Tile> suggestions) {
- final Activity activity = getActivity();
- if (activity == null) {
- return;
- }
- /*根据"com.android.settings.category"的值查询子项数据,这里的值为"com.android.settings.category.ia.homepage"。
- 具体获取办法追踪到frameworks\base\packages\SettingsLib\src\com\android\settingslib\drawer\TileUtils.java中。
- 通过PackageManager查询所有在AndroidManifest.xml中定义<meta-data/>中含有该值的类。注意:会过滤掉非系统级应用的数据!
- 有兴趣的自行研究,这里不深究。*/
- final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(
- CategoryKey.CATEGORY_HOMEPAGE);
- if (category == null) {
- return;
- }
- mSummaryLoader.updateSummaryToCache(category);
- if (suggestions != null) {
- mAdapter.setCategoriesAndSuggestions(category, suggestions);
- } else {
- //数据的绑定在适配器中,->packages\apps\Settings\src\com\android\settings\dashboard\DashboardAdapter.java
- mAdapter.setCategory(category);
- }
- }
对于第一级菜单的加载。在AndroidManifest.xml中的配置如下列图:
三,第二级菜单的加载
以上我们知道第一级菜单是完全动态的加载,但二级菜单则是动态加载和静态xml布局文件,就拿“系统”这项为例。
packages\apps\Settings\AndroidManifest.xml:
- <activity android:name=".Settings$SystemDashboardActivity"
- android:label="@string/header_category_system"
- android:icon="@drawable/ic_settings_about">
- <intent-filter android:priority="-1">
- <action android:name="com.android.settings.action.SETTINGS"/>
- </intent-filter>
- <meta-data android:name="com.android.settings.category"
- android:value="com.android.settings.category.ia.homepage"/>
- <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
- android:value="com.android.settings.system.SystemDashboardFragment"/>
- <meta-data android:name="com.android.settings.summary"
- android:resource="@string/system_dashboard_summary"/>
- </activity>
SystemDashboardFragment.java继承DashboardFragment.java。我们主要观察这个类。
packages\apps\Settings\src\com\android\settings\dashboard\DashboardFragment.java:
1,静态加载部分:
静态加载部分的实现方法为displayResourceTiles()->
- /**
- * Displays resource based tiles.
- */
- private void displayResourceTiles() {
- //获取xml布局文件的id(DashboardFragment.java实现该方法)
- final int resId = getPreferenceScreenResId();
- if (resId <= 0) {
- return;
- }
- addPreferencesFromResource(resId);
- final PreferenceScreen screen = getPreferenceScreen();
- /** 实现布局文件中的子项控件的业务逻辑
- * DashboardFragment.java的子类实现getPreferenceControllers()方法,该方法加载继承于AbstractPreferenceController.java的实现业务逻辑类
- */
- Collection<AbstractPreferenceController> controllers = mPreferenceControllers.values();
- for (AbstractPreferenceController controller : controllers) {
- controller.displayPreference(screen);
- }
- }
2,动态加载部分:
动态加载部分的实现方法refreshDashboardTiles()->
- /**
- * Refresh preference items backed by DashboardCategory.
- */
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- void refreshDashboardTiles(final String TAG) {
- final PreferenceScreen screen = getPreferenceScreen();
- /* 获取子项
- * getCategoryKey()从DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP中获取Category值。
- * 该值通过类名获取
- * 存:PARENT_TO_CATEGORY_KEY_MAP.put(SystemDashboardFragment.class.getName(), CategoryKey.CATEGORY_SYSTEM);
- * CATEGORY_SYSTEM = "com.android.settings.category.ia.system";
- */
- final DashboardCategory category =
- mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
- ... ...
- // Install dashboard tiles.
- for (Tile tile : tiles) {
- ... ...
- if (mDashboardTilePrefKeys.contains(key)) {
- ... ...
- } else {
- // Don't have this key, add it.
- final Preference pref = new Preference(getPrefContext());
- /*在这里进行绑定,加载
- *packages\apps\Settings\src\com\android\settings\dashboard\DashboardFeatureProviderImpl.java->bindPreferenceToTile()
- */
- mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), getMetricsCategory(),
- pref, tile, key, mPlaceholderPreferenceController.getOrder());
- mProgressiveDisclosureMixin.addPreference(screen, pref);
- mDashboardTilePrefKeys.add(key);
- }
- remove.remove(key);
- }
- // Finally remove tiles that are gone.
- for (String key : remove) {
- mDashboardTilePrefKeys.remove(key);
- mProgressiveDisclosureMixin.removePreference(screen, key);
- }
- mSummaryLoader.setListening(true);
- }
该文的Settings加载流程就差不多到这里了。
四,顺便说说
下拉菜单栏时长按设置图标进入设置,在系统项里面会多一个《系统界面调节工具》。那么这是怎么显示和隐藏的了?
frameworks\base\packages\SystemUI\src\com\android\systemui\tuner\TunerService.java
->setTunerEnabled():
- public static final void setTunerEnabled(Context context, boolean enabled) {
- //隐藏应用图标,隐藏某个组件启动也可以使用该方法
- userContext(context).getPackageManager().setComponentEnabledSetting(
- new ComponentName(context, TunerActivity.class),
- enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
- : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP);
- }