在介绍Android O之前,先要介绍Android N的Settings模块,在N上面谷歌对架构做了调整,Settings增加了一个侧滑按钮。

android N 在Settings中作了一些调整,如上面的截图。

- 增加了侧滑菜单,采用v4下的DrawerLayout来实现;

- 在Settings主界面增加了Condition,能够在设置列表中显示状态;

- 在Settings主界面增加了Suggestion。

    

setSystemUiVisibility 设置显示状态栏 settings界面_xml

    再来看看Android O与N的Setting界面对比:

            (1)去掉7.0增加的侧滑菜单

            (2)去掉了Settings最上面的Condition和Suggestion两个菜单

setSystemUiVisibility 设置显示状态栏 settings界面_xml_02

设置的启动界面: 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在这个地方偷了下懒.


1. SettingsDrawerActivity  
2. @Override  
3. protected void onCreate(@Nullable Bundle savedInstanceState) {  
4. super.onCreate(savedInstanceState);  
5. //主要是布局设置  
6. super.setContentView(R.layout.settings_with_drawer);  
7.         mContentHeaderContainer = (FrameLayout) findViewById(R.id.content_header_container);  
8.   
9.         Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);  
10. if (theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {  
11.             toolbar.setVisibility(View.GONE);  
12. return;  
13.         }  
14.         setActionBar(toolbar);  
15.     }  
16.   
17.   
18. SettingsActivity  
19. @Override  
20. protected void onCreate(Bundle savedState) {  
21. super.onCreate(savedState);  
22.        ......   
23. //获取的类名,此处获取的可能是在Settings.java中的内部类,或者就是Settings.java  
24.   
25. // Should happen before any call to getIntent()  
26.         getMetaData();  
27. // Getting Intent properties can only be done after the super.onCreate(...)  
28. final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);  
29.         ......  
30. final ComponentName cn = intent.getComponent();  
31. final String className = cn.getClassName();  
32. //mIsShowingDashboard会根据不同的类名布置不同的布局  
33. class.getName());  
34.   
35. // This is a "Sub Settings" when:  
36. // - this is a real SubSettings  
37. // - or :settings:show_fragment_as_subsetting is passed to the Intent  
38. final boolean isSubSettings = this instanceof SubSettings ||  
39. false);  
40.   
41. // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content  
42. // insets  
43. //设置二级界面的主题  
44. if (isSubSettings) {  
45.             setTheme(R.style.Theme_SubSettings);  
46.         }  
47. //布局不同的布置,主要是为了区分一级界面  
48.         setContentView(mIsShowingDashboard ? R.layout.settings_main_dashboard : R.layout.settings_main_prefs);  
49.   
50.         mContent = (ViewGroup) findViewById(R.id.main_content);  
51.   
52. this);  
53.        ......  
54. //跳转到制定的界面  
55.         launchSettingFragment(initialFragmentName, isSubSettings, intent);  
56.   
57.        ......  
58.     }

在SettingsActivity的onCreate中去其实会有三个不同布局的加载方向,以下是重点了:

Settings , Settings的内部类 , SubSettings

(a)Settings ---主界面(一级界面)

onCreate函数中会根据类名加载不同的布局,而布尔值:mIsShowingDashboard 就是当前是否为一级界面的判断

    1. mIsShowingDashboard = className.equals(Settings.class.getName());  
    2. setContentView(mIsShowingDashboard ?  
    3.         R.layout.settings_main_dashboard : R.layout.settings_main_prefs);



    settings_main_dashboard.xml就很简单了只是一个FrameLayout


      1. <?xml version="1.0" encoding="utf-8"?>  
      2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
      3. android:id="@+id/main_content"  
      4. android:layout_height="match_parent"  
      5. android:layout_width="match_parent"  
      6. />


      在之后的界面跳转即 launchSettingFragment(initialFragmentName, isSubSettings, intent)函数中又做出了区分:


        1. void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) {  
        2. if (!mIsShowingDashboard && initialFragmentName != null) {  
        3.         ......  
        4. else {  
        5. // No UP affordance if we are displaying the main Dashboard  
        6. false;  
        7. // Show Search affordance  
        8. true;  
        9.         mInitialTitleResId = R.string.dashboard_title;  
        10. p;           //此处是真正显示一级界面的操作  
        11. class.getName(), null /* args */, false, false,  
        12. false);  
        13.     }  
        14. }


        在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:


        1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        2. android:orientation="vertical"  
        3. android:layout_height="match_parent"  
        4. android:layout_width="match_parent">  
        5.   
        6. <LinearLayout  
        7. android:orientation="vertical"  
        8. android:layout_height="0px"  
        9. android:layout_width="match_parent"  
        10. android:layout_weight="1">  
        11.   
        12. <com.android.settings.widget.SwitchBar android:id="@+id/switch_bar"  
        13. android:layout_height="?android:attr/actionBarSize"  
        14. android:layout_width="match_parent"  
        15. android:background="@drawable/switchbar_background"  
        16. android:theme="?attr/switchBarTheme"  
        17. />  
        18.   
        19. <FrameLayout  
        20. android:id="@+id/main_content"  
        21. android:layout_width="match_parent"  
        22. android:layout_height="match_parent"  
        23. />  
        24.   
        25. </LinearLayout>  
        26.   
        27. <RelativeLayout /><!--没怎么研究过,此处代码就省略了-->  
        28.   
        29. </LinearLayout>


        Settings的内部类的主体显示内容依然是一个Fragment,而这个Fragment已经在AndroidManifest.xml中定义好了.拿StorageDashboardActivity为例:



        1. <activity android:name=".Settings$StorageDashboardActivity"  
        2. android:label="@string/storage_settings"  
        3. android:icon="@drawable/ic_settings_storage"  
        4. android:taskAffinity="com.android.settings"  
        5. android:parentActivityName="Settings">  
        6. <intent-filter android:priority="1">  
        7. <action android:name="android.settings.INTERNAL_STORAGE_SETTINGS" />  
        8. <action android:name="android.settings.MEMORY_CARD_SETTINGS" />  
        9. <category android:name="android.intent.category.DEFAULT" />  
        10. </intent-filter>  
        11. <intent-filter>  
        12. <action android:name="android.intent.action.MAIN" />  
        13. <category android:name="android.intent.category.DEFAULT" />  
        14. <category android:name="android.intent.category.VOICE_LAUNCH" />  
        15. </intent-filter>  
        16. <intent-filter android:priority="5">  
        17. <action android:name="com.android.settings.action.SETTINGS" />  
        18. </intent-filter>  
        19. <meta-data android:name="com.android.settings.category"  
        20. android:value="com.android.settings.category.ia.homepage" />  
        21. <meta-data android:name="com.android.settings.title"  
        22. android:resource="@string/storage_usb_settings" />  
        23. <meta-data android:name="com.android.settings.FRAGMENT_CLASS"  
        24. android:value="com.android.settings.deviceinfo.StorageSettings" />  
        25. <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"  
        26. android:value="true" />  
        27. </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

        和内部类就很相似了,不过是做了主题的切换:



          1. if (isSubSettings) {  
          2.     setTheme(R.style.Theme_SubSettings);  
          3. }


          SubSettings的启动方式是很隐蔽的这里把启动的顺序贴下,各位有时间的话可以自己研究一下:

          之前N的启动子界面都是在SubSettings为Activity的,不过O好像换掉了,都是以Settings为Activity


            1. android.support.v7.preference.Preference$1.onClick()  
            2. android.support.v7.preference.Preference.performClick()  
            3. com.android.settings.fuelgauge.PowerUsageSummary.onPreferenceTreeClick()  
            4. com.android.settings.dashboard.DashboardFragment.onPreferenceTreeClick()  
            5. android.support.v14.preference.PreferenceFragment.onPreferenceTreeClick()  
            6. com.android.settings.SettingsActivity.onPreferenceStartFragment()  
            7. com.android.settings.SettingsActivity.startPreferencePanel()  
            8. com.android.settings.Utils.startWithFragment()  
            9. com.android.settings.Utils.onBuildStartFragmentIntent()  
            10.


               接下来看一看主界面的加载

            packages\apps\Settings\src\com\android\settings\dashboard\DashboardSummary.java

            DashboardSummary界面很简单,只有一个自定的RecyclerView,这里我们主要看看他的数据是如何加载的

            进入DashboardAdapter看加载的数据:

            Tile实现了Parcelable接口,DashboardCategory里面包含着Tile,所以我们看看后者如何获取的

            setSystemUiVisibility 设置显示状态栏 settings界面_java_03

            DashboardCategory的获取是在DashboardSummary中的updateCategoryAndSuggestion方法中,获取之后在DashboardAdapter设置

            setSystemUiVisibility 设置显示状态栏 settings界面_android_04

            在DashboardSummary的onCreate函数中有获取的有两个很重要参数:mDashboardFeatureProvider,mSuggestionFeatureProvider.这两个是主要的数据提供者,mSuggestionFeatureProvider和mDashboardFeatureProvider的数据获取是有所不同的,这里就不再说明mSuggestionFeatureProvider了,重点说明下mDashboardFeatureProvider.

            mDashboardFeatureProvider提供的数据是一级菜单如"电池","显示","网络和互联网"等,实现类为DashboardFeatureProviderImpl.java,而DashboardFeatureProviderImpl中的具体的数据是通过函数getTilesForCategory()从CategoryManager获取的.

            这里具体的获取过程我就不去深究了,有需要的话可以参考下面这篇博客


            最后获取的数据是从AndroidManifest.xml中配置的,比如:

            setSystemUiVisibility 设置显示状态栏 settings界面_android_05


            配置完之后会在onCategoriesChanged()中进行界面的加载。


            二级界面的加载

                以上我们知道第一级菜单是完全动态的加载,但二级菜单则是动态加载和静态xml布局文件,就拿“系统”这项为例。

            packages\apps\Settings\AndroidManifest.xml:


            1. <activity android:name=".Settings$SystemDashboardActivity"  
            2. android:label="@string/header_category_system"  
            3. android:icon="@drawable/ic_settings_about">  
            4. <intent-filter android:priority="-1">  
            5. <action android:name="com.android.settings.action.SETTINGS"/>  
            6. </intent-filter>  
            7. <meta-data android:name="com.android.settings.category"  
            8. android:value="com.android.settings.category.ia.homepage"/>  
            9. <meta-data android:name="com.android.settings.FRAGMENT_CLASS"  
            10. android:value="com.android.settings.system.SystemDashboardFragment"/>  
            11. <meta-data android:name="com.android.settings.summary"  
            12. android:resource="@string/system_dashboard_summary"/>  
            13. </activity>


            SystemDashboardFragment.java继承DashboardFragment.java。DashboardFragment继承于PreferenceFragment

            后者是通过xml文件来进行加载的,它是所有二级界面的基类。

            packages\apps\Settings\src\com\android\settings\dashboard\DashboardFragment.java:

            1,静态加载部分:

            静态加载部分的实现方法为displayResourceTiles()->


              1. /**
              2.     * Displays resource based tiles.
              3.     */  
              4. private void displayResourceTiles() {  
              5. //获取xml布局文件的id(DashboardFragment.java实现该方法)  
              6. final int resId = getPreferenceScreenResId();  
              7. if (resId <= 0) {  
              8. return;  
              9.        }  
              10.        addPreferencesFromResource(resId);  
              11. final PreferenceScreen screen = getPreferenceScreen();  
              12. /** 实现布局文件中的子项控件的业务逻辑
              13.      *  DashboardFragment.java的子类实现getPreferenceControllers()方法,该方法加载继承于AbstractPreferenceController.java的实现业务逻辑类
              14.      */  
              15.        Collection<AbstractPreferenceController> controllers = mPreferenceControllers.values();  
              16. for (AbstractPreferenceController controller : controllers) {  
              17.            controller.displayPreference(screen);  
              18.        }  
              19.    }


              addPreferencesFromResource(resId)加载界面的布局

              setSystemUiVisibility 设置显示状态栏 settings界面_java_06

              子类会添加Controller,它可以用来控制二级界面中item的显示。

              2,动态加载部分:

              动态加载部分的实现方法refreshDashboardTiles()->


              1. /**
              2.      * Refresh preference items backed by DashboardCategory.
              3.      */  
              4. @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)  
              5. void refreshDashboardTiles(final String TAG) {  
              6. final PreferenceScreen screen = getPreferenceScreen();  
              7. /* 获取子项 
              8.          * getCategoryKey()从DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP中获取Category值。
              9.          * 该值通过类名获取
              10.          * 存:PARENT_TO_CATEGORY_KEY_MAP.put(SystemDashboardFragment.class.getName(), CategoryKey.CATEGORY_SYSTEM);
              11.          * CATEGORY_SYSTEM = "com.android.settings.category.ia.system";
              12.          */  
              13. final DashboardCategory category =  
              14.                 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());  
              15.         ... ...  
              16. // Install dashboard tiles.  
              17. for (Tile tile : tiles) {  
              18.             ... ...  
              19. if (mDashboardTilePrefKeys.contains(key)) {  
              20.                 ... ...  
              21. else {  
              22. // Don't have this key, add it.  
              23. final Preference pref = new Preference(getPrefContext());  
              24. /*在这里进行绑定,加载
              25.                  *packages\apps\Settings\src\com\android\settings\dashboard\DashboardFeatureProviderImpl.java->bindPreferenceToTile()
              26.                  */  
              27.                 mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), getMetricsCategory(),  
              28.                         pref, tile, key, mPlaceholderPreferenceController.getOrder());  
              29.                 mProgressiveDisclosureMixin.addPreference(screen, pref);  
              30.                 mDashboardTilePrefKeys.add(key);  
              31.             }  
              32.             remove.remove(key);  
              33.         }  
              34. // Finally remove tiles that are gone.  
              35. for (String key : remove) {  
              36.             mDashboardTilePrefKeys.remove(key);  
              37.             mProgressiveDisclosureMixin.removePreference(screen, key);  
              38.         }  
              39. true);  
              40.     }

              该文的Settings加载流程就差不多到这里了。

              四,顺便说说

              下拉菜单栏时长按设置图标进入设置,在系统项里面会多一个《系统界面调节工具》。那么这是怎么显示和隐藏的了?

              frameworks\base\packages\SystemUI\src\com\android\systemui\tuner\TunerService.java

              ->setTunerEnabled():

                1. public static final void setTunerEnabled(Context context, boolean enabled) {  
                2. //隐藏应用图标,隐藏某个组件启动也可以使用该方法  
                3.         userContext(context).getPackageManager().setComponentEnabledSetting(  
                4. new ComponentName(context, TunerActivity.class),  
                5.                 enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED  
                6.                         : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,  
                7.                 PackageManager.DONT_KILL_APP);  
                8.     }