1. 程式人生 > >Android Settings模組架構淺析

Android Settings模組架構淺析

概述

Android Settings模組說簡單也簡單,說難也難,裡面涉及到的知識點也挺多的。

我們知道Settings主要是用於配置一些系統選項或屬性值,通過修改設定項就能達到修改系統配置的作用。

那麼問題來了,Settings是如何實現修改後能改變系統配置的呢?Settings又是採用怎樣的架構實現的呢?裡面又涉及到哪些知識點呢?

讓我們一起來揭開她的神祕面紗吧!

原理分析

Settings的主要功能就是改變系統配置,那麼他是如何做到的呢?
  • Settings的戰友SettingsProvider
通過跟蹤Settings原始碼發現,Settings並不是孤軍作戰,它還有一個戰友 - SettingsProvider。

SettingsProvider又是做什麼的呢?其實看到這個名字我們不難猜到他扮演著什麼樣的角色。

SettingsProvider繼承ContentProvider,ContentProvider在android中主要扮演著資料共享的角色。

SettingsProvider中有一個數據庫,並且這個資料庫是對外公開的。

Settings與SettingsProvider之間又是什麼關係呢?且看下面分析。
  • Settings和SettingsProvider之間的關係
Settings原始碼位置:
  packages/apps/Settings/

SettingsProvider原始碼位置:
  frameworks/base/packages/SettingsProvider/
  frameworks/base/core/java/android/provider/Settings.java

db在資料庫中存在的位置:
  /data/data/com.android.providers.settings/databases/settings.db
他們之間存在什麼聯絡呢?

其實,Settings會對SettingsProvider中的資料庫進行操作和監聽。

Settings中大部分選項都會涉及到對SettingsProvider的操作。
  • 原理分析
通過跟蹤程式碼發現,Settings大部分操作的就是SettingsProvider中的資料,也有一些直接作業系統屬性的等等。

當用戶在修改系統設定時,大部分實際上是在修改SettingsProvider中的值。

當SettingsProvider資料庫中的值被改變時,一些系統服務什麼的就會監聽到,這時候就會通過jni等當時操作底層,從而達到系統屬性或配置改變的效果。

架構分析

Settings處在安卓的應用層,不同於市場上的app,Settings屬於系統app,也是一個比較特別的app。
  • Settings特點
1.Settings頁面很多,但是Activity卻很少,基本上都是使用PreferenceFragment

2.Settings中包含大量對provider的操作與監聽

3.Settings UI基本上都是採用Preference來實現
  • Settings架構
1.Settings主介面Activity使用的是Settings

2.Settings子介面Activity基本上都是使用SubSettings

3.Settings與SubSettings中都是空Activity,這裡的空Activity指的是沒有重寫7大生命週期方法

4.Settings與SubSettings都是繼承於SettingsActivity

5.主介面使用的layout是:settings_main_dashboard,子介面使用的layout是:settings_main_prefs

6.主介面settings_main_dashboard中是使用DashboardSummary(Fragment)進行填充,子介面都是使用各自的Fragment進行填充

7.子介面fragment基本上都是直接或間接繼承SettingsPreferenceFragment

8.主介面選項列表是定義在dashboard_categories.xml中,此檔案是在SettingsActivity的buildDashboardCategories方法中進行解析的

9.在Settings類中定義了很多static class,這些類都是繼承SettingsActivity,但都是空的,如BluetoothSettingsActivity
  這些類主要用於對外提供跳轉頁面,比如從SystemUI跳轉至Settings中的某個介面

10.Settings類中定義了的static class被定義在AndroidManifest中,通過meta-data引數將對應的Fragment繫結在一起

11.在Activity中填充Fragment主要使用的是SettingsActivity中的switchToFragment方法
  • settings_main_dashboard中只有一個FrameLayout,後面會將其替換為DashboardSummary
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/dashboard_background_color" />
  • settings_main_prefs中也存在一個叫main_content的FrameLayout,後面會將其替換為各自的Fragment,switch_bar與button_bar只有在某些頁面才會顯示
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="1"
        android:orientation="vertical" >

        <com.android.settings.widget.SwitchBar
            android:id="@+id/switch_bar"
            android:layout_width="match_parent"
            android:layout_height="?android:attr/actionBarSize"
            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"
            android:background="?attr/preferenceBackgroundColor" />
    </LinearLayout>

    <RelativeLayout
        android:id="@+id/button_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="0"
        android:visibility="gone" >

        <Button
            android:id="@+id/back_button"
            android:layout_width="150dip"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_margin="5dip"
            android:text="@*android:string/back_button_label" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:orientation="horizontal" >

            <Button
                android:id="@+id/skip_button"
                android:layout_width="150dip"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:text="@*android:string/skip_button_label"
                android:visibility="gone" />

            <Button
                android:id="@+id/next_button"
                android:layout_width="150dip"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:text="@*android:string/next_button_label" />
        </LinearLayout>
    </RelativeLayout>

</LinearLayout>
  • Settings主介面結構
1.從圖中可以看到,紅色框中的屬於一個DashboardCategory,藍色框中的屬於DashboardTileView
2.在DashboardSummary中有多個DashboardCategory,DashboardCategory中包含一個title和多個DashboardTileView
3.DashboardTileView具有onClick方法,點選後啟動子介面,使用的是Utils.startWithFragment進行跳轉
4.startWithFragment方法中將子介面的Fragment傳遞給activity,這裡會繫結對應的activity,也就是SubSettings

Utils.startWithFragment關鍵方法

 public static void startWithFragment(Context context, String fragmentName, Bundle args,
            Fragment resultTo, int resultRequestCode, int titleResId,
            CharSequence title) {
        startWithFragment(context, fragmentName, args, resultTo, resultRequestCode,
                null /* titleResPackageName */, titleResId, title, false /* not a shortcut */);
    }


    public static void startWithFragment(Context context, String fragmentName, Bundle args,
            Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId,
            CharSequence title, boolean isShortcut) {
        Intent intent = onBuildStartFragmentIntent(context, fragmentName, args,
                titleResPackageName,
                titleResId, title, isShortcut);
        if (resultTo == null) {
            context.startActivity(intent);
        } else {
            resultTo.startActivityForResult(intent, resultRequestCode);
        }
    }

    public static Intent onBuildStartFragmentIntent(Context context, String fragmentName,
            Bundle args, String titleResPackageName, int titleResId, CharSequence title,
            boolean isShortcut) {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        if (BluetoothSettings.class.getName().equals(fragmentName)) {
            intent.setClass(context, SubSettings.BluetoothSubSettings.class);
            intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, true);
        } else if (WifiSettings.class.getName().equals(fragmentName)) {
            intent.setClass(context, SubSettings.WifiSubSettings.class);
            intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, true);
        } else {
            intent.setClass(context, SubSettings.class);
        }
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, fragmentName);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME,
                titleResPackageName);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, titleResId);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, isShortcut);
        return intent;
    }

SettingsActivity.onCreate方法中的關鍵程式碼

    @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);

        // Should happen before any call to getIntent()
        getMetaData();

        final Intent intent = getIntent();

        // Getting Intent properties can only be done after the super.onCreate(...)
        final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);

        mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
                intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);

        final ComponentName cn = intent.getComponent();
        final String className = cn.getClassName();

        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 = className.equals(SubSettings.class.getName()) ||
                intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);

        setContentView(mIsShowingDashboard ?
                R.layout.settings_main_dashboard : R.layout.settings_main_prefs);

        mContent = (ViewGroup) findViewById(R.id.main_content);

        getFragmentManager().addOnBackStackChangedListener(this);

        if (savedState != null) {
            ......
        } else {
            if (!mIsShowingDashboard) {
                ......
                setTitleFromIntent(intent);

                Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
                switchToFragment(initialFragmentName, initialArguments, true, false,
                        mInitialTitleResId, mInitialTitle, false);
            } else {
                ......
                mInitialTitleResId = R.string.dashboard_title;
                switchToFragment(DashboardSummary.class.getName(), null, false, false,
                        mInitialTitleResId, mInitialTitle, false);
            }
        }
      ......
    }
1.當點選主介面上的item時會呼叫Utils.startWithFragment方法
2.在Utils.startWithFragment會跳轉至SubSettings,對應的fragment也作為引數傳遞給了SubSettings
3.SubSettings是一個空的activity,但SubSettings繼承於SettingsActivity,因此會呼叫父類SettingsActivity的onCreate方法
4.在onCreate方法中,className為SubSettings,isSubSettings為true,mIsShowingDashboard為false
5.因此會執行switchToFragment(initialFragmentName, initialArguments, true, false, mInitialTitleResId, mInitialTitle, false);
6.通過switchToFragment將settings_main_prefs的main_content替換為了子介面對應的fragment

簡單類圖

從下面類圖中可以看出

1.Settings中主要的Activity為SettingsActivity,其他基本上都是繼承該activity,並且其他基本上都是空的
2.Settings中fragment基本上都是繼承至SettingsPreferenceFragment

時序圖

下面的時序圖為點選Settings圖示啟動Settings,在點選item啟動子介面的時序圖
從圖中可以看出啟動的一個流程,按照這個流程,幾乎所有的介面都會執行SettingsActivity