android開發中Settings結構簡單分析
Settings介面結構簡單分析
Setting是android系統很重要的模組,這個模組並不是很複雜,這部分也一直在看,很多時候都是在看某個具體的選項,比如WLAN,藍芽這樣具體的原始碼,但是對於主介面的佈局以及結構並不清楚。
在使用Hierarchy Viewer工具可以看到Settings模組的主介面顯示的是Settings,
com.android.settings/com.android.settings.Settings
在進入設定的子介面的時候,顯示永遠是SubSettings,
com.android.settings/com.android.settings.SubSettings
這樣我就感覺有點奇怪,為什麼所有的子介面顯示的都是SubSettings。
這幾天搜了相關資料和結合原始碼看了一下這部分的邏輯,簡單分析如下。
- Settings主介面Activity使用的是Settings
- Settings子介面Activity基本上都是使用SubSettings
- Settings與SubSettings中都是空Activity,這裡的空Activity指的是沒有重寫7大生命週期方法
- Settings與SubSettings都是繼承於SettingsActivity
主介面使用的layout是:settings_main_dashboard,子介面使用的layout是:settings_main_prefs
在SettingsActivity的onCreate方法中,通過判斷當前是Settings還是SubSettings來確定用什麼佈局來顯示
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
.....
mIsShowingDashboard = className.equals(Settings.class.getName());
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
....
....
}
這裡會有兩個問題,Settings作為主介面,載入的是settings_main_dashboard.xml檔案,下面是這個xml檔案具體內容
<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" />
如果都是這個佈局的話,那麼主介面的不同item是如何顯示出來的呢?
主介面settings_main_dashboard中是使用DashboardSummary(Fragment)進行填充。由於設定的主介面Settings和設定的子介面SubSettings都是繼承於SettingsActivity的,Setting是和SubSettings裡面是空的,所以會執行父類SettingsActivity的onCreate方法。
在SettingsActivity的onCreate方法後面,在這裡會switchToFragment進行替換,
將其替換為DashboardSummary。DashboardSummary同樣是一個Fragment,通過mIsShowingDashboard判斷是否將DashboardSummary替換過來,而這裡mIsShowingDashboard的值是通過判斷當前是Settings還是SubSettings來獲得的。
下面是switchToFragment方法的部分原始碼:
if (savedState != null) {
.....
.....
} else {
if (!mIsShowingDashboard) {
......
......
switchToFragment(initialFragmentName, initialArguments, true, false,mInitialTitleResId, mInitialTitle, false);
} else {
switchToFragment(DashboardSummary.class.getName(), null, false, false,mInitialTitleResId, mInitialTitle, false);
}
}
然後去看DashboardSummary這個類,它是一個Fragment。DashboardSummary繼承了PreferenceFragment,並且在它的onCreateView方法中,載入的XML佈局是dashboard.xml,dashboard.xml的佈局如下,使用ScrollView巢狀了一個豎直的線性佈局,這樣,設定的主介面就是可以滾動的垂直的線性結構。
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dashboard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbarStyle="outsideOverlay"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/dashboard_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:paddingStart="@dimen/dashboard_padding_start"
android:paddingEnd="@dimen/dashboard_padding_end"
android:paddingTop="@dimen/dashboard_padding_top"
android:paddingBottom="@dimen/dashboard_padding_bottom"
android:clipToPadding="false"
android:orientation="vertical"
/>
</ScrollView>
這樣,設定主頁面Settings的整體結構就出來了,接下來看這介面的選項列表是如何載入出來的。
注意到在DashboardSummary的onResume方法中,有個方法為sendRebuildUI,這個方法通過Handler傳送Message來通知介面更新UI,更新UI的方法為rebuildUI。
在rebuildUI這個方法中,通過呼叫SettingsActivity中的getDashboardCategories來獲得主介面的選項列表,在SettingsActivity中的getDashboardCategories方法中,通過呼叫buildDashboardCategories來從佈局檔案中將Setting主介面的選項解析出來,這個佈局為dashboard_categories.xml。下面擷取的是dashboard_categories.xml部分佈局檔案,大神board-categories節點表示的大的選項的分類,dashboard-tile則表示的是具體的選項比如wifi,藍芽等。
上面紅框圈住的為DashboardTitle,下面紅框圈住的為DashboardTitleView,DashboardTitle對應的是xml佈局中的dashboard-category這個節點,而DashboardTitleView則對應的是dashboard-title這個節點。
<?xml version="1.0" encoding="utf-8"?>
<dashboard-categories
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- WIRELESS and NETWORKS -->
<dashboard-category
android:id="@+id/wireless_section"
android:key="@string/category_key_wireless"
android:title="@string/header_category_wireless_networks" >
<!-- Wifi -->
<dashboard-tile
android:id="@+id/wifi_settings"
android:title="@string/wifi_settings_title"
android:fragment="com.android.settings.wifi.WifiSettings"
android:icon="@drawable/ic_settings_wireless"
/>
.....
.....
</dashboard-category>
</dashboard-category>
通過以上一系列的操作,Setting模組的主介面的佈局就載入出來了。
在Settings類中,定義了大量靜態的內部類,但是都是空的,並未實現。
定義的這些靜態內部類主要用於跳轉用的,比如從SystemUI跳轉至Setting的某一個頁面,另外這些靜態內部類在AndroidManifest.xml檔案中通過meta-data將相應的Fragment繫結起來。
<activity android:name="Settings$WirelessSettingsActivity"
android:taskAffinity="com.android.settings"
android:label="@string/wireless_networks_settings_title"
android:parentActivityName="Settings">
<intent-filter android:priority="1">
<action android:name="android.settings.WIRELESS_SETTINGS" />
<action android:name="android.settings.AIRPLANE_MODE_SETTINGS" />
<action android:name="android.settings.NFC_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>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.WirelessSettings" />
<meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID"
android:resource="@id/wireless_settings" />
<!-- Note that this doesn't really show any Wireless settings. -->
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
這裡是如何通過meta-data將相應的Fragment進行繫結的,可以看看這個部落格:Android5.0 Settings各個子模組跳轉和佈局實現
第二個問題,進入設定子介面,也就是二級介面的時候,Hierarchy Viewer工具上顯示的SubSettings,接下來分析這一塊。
由SubSettingsActivity的onCreate方法可知,SubSettings載入的佈局檔案是settings_main_prefs.xml,佈局檔案的內容如下。
<?xml version="1.0" encoding="utf-8"?>
<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:layout_marginLeft="13dp"
android:layout_marginRight="13dp"
android:background="?attr/preferenceBackgroundColor"
/>
<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_height="wrap_content"
android:layout_width="match_parent"
android:layout_weight="0"
android:visibility="gone">
<Button android:id="@+id/back_button"
android:layout_width="150dip"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:layout_alignParentStart="true"
android:text="@*android:string/back_button_label"
/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true">
<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>
同樣的,SubSettings的Fragment的替換也是在SettingsActivity的onCreate方法中處理,注意到在onCreate方法的後面,如下,當mIsShowingDashboard為false時,會呼叫
switchToFragment(),這時第一個引數穿進去的是initalFragmentName,initalFragmentName這個引數就是對應的就是子介面的Fragment,在onCreate方法內先後執行了getMetaData和getIntent兩個方法後,會通過PackageManager獲取Activity的資訊,得到對應的Activity的meta-data中key為com.android.settings.FRAGMENT_CLASS的值value,比如
wifi設定模組的值就是com.android.settings.WirelessSettings,然後再通過構造一個intent,並且給它增加了一個特殊的鍵值對,key為:settings:show_fragment,value為mFragmentClass指定的Fragment類名。
if (savedState != null) {
.....
.....
} else {
if (!mIsShowingDashboard) {
......
......
switchToFragment(initialFragmentName, initialArguments, true, false,mInitialTitleResId, mInitialTitle, false);
} else {
switchToFragment(DashboardSummary.class.getName(), null, false, false,mInitialTitleResId, mInitialTitle, false);
}
}
這樣,SubSettings中settings_main_prefs.xml對應的Fragment就會被響應的Fragment替換過來了。
這個部分介紹的是Settings子介面是如何渲染出來的。
最後介紹下,由主介面的Item項是如何進行跳轉的,即由主介面的選項是如何響應使用者的點選事件跳轉至子介面的?
由於主介面的佈局都是採用DashboardCategory和DashboardTileView進行構造的,DashboardCategory相當於父控制元件,而DashboardTileView則為子控制元件,一般DashboardCategory一般可以包含多個DashboardTileView。
檢視DashboardTileView這個類的原始碼,會發現這裡面有個onClick方法,這樣使用者的點選事件就可以響應了。當使用者點選的時候,會呼叫Utils.startWithFragmen。
@Override
public void onClick(View v) {
if (mTile.fragment != null) {
Utils.startWithFragment(getContext(), mTile.fragment, mTile.fragmentArguments, null, 0,
mTile.titleRes, mTile.getTitle(getResources()));
} else if (mTile.intent != null) {
int numUserHandles = mTile.userHandle.size();
if (numUserHandles > 1) {
ProfileSelectDialog.show(((Activity) getContext()).getFragmentManager(), mTile);
} else if (numUserHandles == 1) {
getContext().startActivityAsUser(mTile.intent, mTile.userHandle.get(0));
} else {
getContext().startActivity(mTile.intent);
}
}
}
在startFragment中,獲得點選的item項對應的FragmentName,,然後構造一個Intent進行跳轉。
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);
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;
}
相關文章
- Android開發簡單教程.docAndroid
- 【Android開發入門教程】二.Android應用程式結構分析Android
- Android開發之ViewPager簡單使用AndroidViewpager
- android移動開發簡單的開發例項Android移動開發
- 軟體開發常用結構以及SSM框架的簡單介紹SSM框架
- Android 開發簡單記事本程式Android
- Android 目錄結構分析Android
- Android 原始碼結構分析Android原始碼
- JVM結構的簡單梳理JVM
- 資料結構簡單題資料結構
- 一隻android簡訊控制馬的簡單分析Android
- 安卓開發中RecycleView簡單使用步驟安卓View
- Android系統開發小記:Fingerprint-settingsAndroid
- Android 用WebView開發簡單的瀏覽器AndroidWebView瀏覽器
- Android開發技巧——PagerAdapter再簡單的包AndroidAPT
- Android開發之FastJson概述與簡單使用AndroidASTJSON
- Android ListView初始化簡單分析AndroidView
- 分析行連結的簡單方法
- 資料結構中樹形結構簡介資料結構
- 資料結構簡單要點總結資料結構
- html文件結構簡單介紹HTML
- 棧鏈式結構簡單操作
- Android開發中的MVP架構詳解AndroidMVP架構
- Android中Lottie的簡單使用Android
- Android熱修復簡單總結Android
- Android開發中如何結束所有的activityAndroid
- iOS開發Settings.bundle的使用iOS
- [AI開發]零程式碼分析影片結構化類應用結構設計AI
- 【PG體系結構】PG體系結構簡單說明
- prometheus 簡單使用及簡單 middleware 開發Prometheus
- JS棧結構的簡單封裝JS封裝
- 一個簡單的樹形結構
- Android開發之道(2)系統體系結構概要Android
- 開發中的易讀錯單詞小結
- Android開發筆記[16]-簡單使用wasmedge執行時Android筆記ASM
- 簡單介紹python中的mock介面開發PythonMock
- C++連結串列類的簡單操作含圖書結構體 簡單易懂C++結構體
- 什麼是軟體開發業務建模分析和結構化建模分析