在介绍Android O之前,先要介绍Android N的Settings模块,在N上面谷歌对架构做了调整,Settings增加了一个侧滑按钮。
android N 在Settings中作了一些调整,如上面的截图。
- 增加了侧滑菜单,采用v4下的DrawerLayout来实现;
- 在Settings主界面增加了Condition,能够在设置列表中显示状态;
- 在Settings主界面增加了Suggestion。
再来看看Android O与N的Setting界面对比:
(1)去掉7.0增加的侧滑菜单
(2)去掉了Settings最上面的Condition和Suggestion两个菜单
设置的启动界面: O/packages/apps/Settings/src/com/android/settings/Settings.java
Settings的父类是SettingsActivity,而且其中有很多继承SettingsActivity的内部类,这个地方是很有意思,后续再说,先看下SettingsActivity.java
O/packages/apps/Settings/src/com/android/settings/SettingsActivity.java
SettingsActivity的父类是SettingsDrawerActivity,而SettingsDrawerActivity是在SettingLib中定义
O/frameworks/base/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
SettingsDrawerActivity名称是沿用N版本的名字,但是侧滑栏功能在O版本上已经移除了,google在这个地方偷了下懒.
- SettingsDrawerActivity
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- //主要是布局设置
- super.setContentView(R.layout.settings_with_drawer);
- mContentHeaderContainer = (FrameLayout) findViewById(R.id.content_header_container);
- Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);
- if (theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
- toolbar.setVisibility(View.GONE);
- return;
- }
- setActionBar(toolbar);
- }
- SettingsActivity
- @Override
- protected void onCreate(Bundle savedState) {
- super.onCreate(savedState);
- ......
- //获取的类名,此处获取的可能是在Settings.java中的内部类,或者就是Settings.java
- // Should happen before any call to getIntent()
- getMetaData();
- // Getting Intent properties can only be done after the super.onCreate(...)
- final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
- ......
- final ComponentName cn = intent.getComponent();
- final String className = cn.getClassName();
- //mIsShowingDashboard会根据不同的类名布置不同的布局
- mIsShowingDashboard = className.equals(Settings.class.getName());
- // This is a "Sub Settings" when:
- // - this is a real SubSettings
- // - or :settings:show_fragment_as_subsetting is passed to the Intent
- final boolean isSubSettings = this instanceof SubSettings ||
- intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
- // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content
- // insets
- //设置二级界面的主题
- if (isSubSettings) {
- setTheme(R.style.Theme_SubSettings);
- }
- //布局不同的布置,主要是为了区分一级界面
- setContentView(mIsShowingDashboard ? R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
- mContent = (ViewGroup) findViewById(R.id.main_content);
- getFragmentManager().addOnBackStackChangedListener(this);
- ......
- //跳转到制定的界面
- launchSettingFragment(initialFragmentName, isSubSettings, intent);
- ......
- }
在SettingsActivity的onCreate中去其实会有三个不同布局的加载方向,以下是重点了:
Settings , Settings的内部类 , SubSettings
(a)Settings ---主界面(一级界面)
onCreate函数中会根据类名加载不同的布局,而布尔值:mIsShowingDashboard 就是当前是否为一级界面的判断
- mIsShowingDashboard = className.equals(Settings.class.getName());
- setContentView(mIsShowingDashboard ?
- R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
settings_main_dashboard.xml就很简单了只是一个FrameLayout
- <?xml version="1.0" encoding="utf-8"?>
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/main_content"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- />
在之后的界面跳转即 launchSettingFragment(initialFragmentName, isSubSettings, intent)函数中又做出了区分:
- void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) {
- if (!mIsShowingDashboard && initialFragmentName != null) {
- ......
- } else {
- // No UP affordance if we are displaying the main Dashboard
- mDisplayHomeAsUpEnabled = false;
- // Show Search affordance
- mDisplaySearch = true;
- mInitialTitleResId = R.string.dashboard_title;
- p; //此处是真正显示一级界面的操作
- switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false,
- mInitialTitleResId, mInitialTitle, false);
- }
- }
在launchSettingFragment函数中正式加载了设置的一级界面DashboardSummary.java,DashboardSummary是一个Fragment,而内部布局是使用的R.layout.dashboard,dashboard.xml包含了一个自定义的RecyclerView类FocusRecyclerView,此处就不再详细说明了.
(b)Settings的内部类
Settings的内部类的启动一版都是通过activity 中的action属性启动的,而判断的依据也是通过mIsShowingDashboard,加载的布局为R.layout.settings_main_prefs,而settings_main_prefs.xml比dashboard.xml来说就增加了一个switchBar:
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_height="match_parent"
- android:layout_width="match_parent">
- <LinearLayout
- android:orientation="vertical"
- android:layout_height="0px"
- android:layout_width="match_parent"
- android:layout_weight="1">
- <com.android.settings.widget.SwitchBar android:id="@+id/switch_bar"
- android:layout_height="?android:attr/actionBarSize"
- android:layout_width="match_parent"
- android:background="@drawable/switchbar_background"
- android:theme="?attr/switchBarTheme"
- />
- <FrameLayout
- android:id="@+id/main_content"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
- </LinearLayout>
- <RelativeLayout /><!--没怎么研究过,此处代码就省略了-->
- </LinearLayout>
Settings的内部类的主体显示内容依然是一个Fragment,而这个Fragment已经在AndroidManifest.xml中定义好了.拿StorageDashboardActivity为例:
- <activity android:name=".Settings$StorageDashboardActivity"
- android:label="@string/storage_settings"
- android:icon="@drawable/ic_settings_storage"
- android:taskAffinity="com.android.settings"
- android:parentActivityName="Settings">
- <intent-filter android:priority="1">
- <action android:name="android.settings.INTERNAL_STORAGE_SETTINGS" />
- <action android:name="android.settings.MEMORY_CARD_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>
- <intent-filter android:priority="5">
- <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.title"
- android:resource="@string/storage_usb_settings" />
- <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
- android:value="com.android.settings.deviceinfo.StorageSettings" />
- <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
- android:value="true" />
- </activity>
这个地方很容易会让人迷糊的,主要是里面的属性太多,看第一眼估计就会晕掉,这些属性是在SettingLib中的O/frameworks/base/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java有用到的,后续有时间的话可以总结一下,TileUtils主要目录加载的工具类.
言归正传,内部类StorageDashboardActivity的Fragment显示内容为:com.android.settings.FRAGMENT_CLASS,即com.android.settings.deviceinfo.StorageSettings
(c)SubSettings
和内部类就很相似了,不过是做了主题的切换:
- if (isSubSettings) {
- setTheme(R.style.Theme_SubSettings);
- }
SubSettings的启动方式是很隐蔽的这里把启动的顺序贴下,各位有时间的话可以自己研究一下:
之前N的启动子界面都是在SubSettings为Activity的,不过O好像换掉了,都是以Settings为Activity
- android.support.v7.preference.Preference$1.onClick()
- android.support.v7.preference.Preference.performClick()
- com.android.settings.fuelgauge.PowerUsageSummary.onPreferenceTreeClick()
- com.android.settings.dashboard.DashboardFragment.onPreferenceTreeClick()
- android.support.v14.preference.PreferenceFragment.onPreferenceTreeClick()
- com.android.settings.SettingsActivity.onPreferenceStartFragment()
- com.android.settings.SettingsActivity.startPreferencePanel()
- com.android.settings.Utils.startWithFragment()
- com.android.settings.Utils.onBuildStartFragmentIntent()
接下来看一看主界面的加载
packages\apps\Settings\src\com\android\settings\dashboard\DashboardSummary.java
DashboardSummary界面很简单,只有一个自定的RecyclerView,这里我们主要看看他的数据是如何加载的
进入DashboardAdapter看加载的数据:
Tile实现了Parcelable接口,DashboardCategory里面包含着Tile,所以我们看看后者如何获取的
DashboardCategory的获取是在DashboardSummary中的updateCategoryAndSuggestion方法中,获取之后在DashboardAdapter设置
在DashboardSummary的onCreate函数中有获取的有两个很重要参数:mDashboardFeatureProvider,mSuggestionFeatureProvider.这两个是主要的数据提供者,mSuggestionFeatureProvider和mDashboardFeatureProvider的数据获取是有所不同的,这里就不再说明mSuggestionFeatureProvider了,重点说明下mDashboardFeatureProvider.
mDashboardFeatureProvider提供的数据是一级菜单如"电池","显示","网络和互联网"等,实现类为DashboardFeatureProviderImpl.java,而DashboardFeatureProviderImpl中的具体的数据是通过函数getTilesForCategory()从CategoryManager获取的.
这里具体的获取过程我就不去深究了,有需要的话可以参考下面这篇博客
https://blog.csdn.net/qq_26825819/article/details/80272644
最后获取的数据是从AndroidManifest.xml中配置的,比如:
配置完之后会在onCategoriesChanged()中进行界面的加载。
二级界面的加载
以上我们知道第一级菜单是完全动态的加载,但二级菜单则是动态加载和静态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。DashboardFragment继承于PreferenceFragment
后者是通过xml文件来进行加载的,它是所有二级界面的基类。
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);
- }
- }
addPreferencesFromResource(resId)加载界面的布局
子类会添加Controller,它可以用来控制二级界面中item的显示。
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);
- }