Android Settings模組架構淺析
阿新 • • 發佈:2019-02-04
概述
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