本文為部落客辛苦總結,針對Android4.42源碼分析,轉載請注明出處,http://blog.csdn.net/zrf1335348191/article/details/50837027
最近在研究Android的Settings源碼,先看一下源碼的目錄結構。大概967左右個檔案,是不是及其頭疼而且無從下手?待我娓娓道來~~~~~
1,初識Settings
首先,這麼多檔案,到底哪個檔案是主界面呢?在Settings目錄下找到Androidmanifest.xml清單配置檔案,找到首先啟動的activity:
<activity android:name="Settings" android:label="@string/settings_label_launcher" android:taskAffinity="com.android.settings" android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.settings.SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
可以看到,設定的主界面是Settings.Java(package com.android.settings;),
public class Settings extends PreferenceActivity
implements ButtonBarHandler, OnAccountsUpdateListener {
.....
<pre name="code" class="java"> loadHeadersFromResource(R.xml.settings_headers, headers);//加載布局
.....
}
所對應的xml檔案為Settings_headers.xml(res\xml\)檔案。在此摘列出xml檔案的一部分。
<preference-headers
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- WIRELESS and NETWORKS -->
<header android:id="@+id/wireless_section"
android:title="@string/header_category_wireless_networks" />
<!-- Wifi -->
<header
android:id="@+id/wifi_settings"
android:fragment="com.android.settings.wifi.WifiSettings"
android:title="@string/wifi_settings_title"
android:icon="@drawable/ic_settings_wireless" />
<!-- MobileData -->
<header
android:id="@+id/mobiledata_settings"
android:icon="@drawable/stat_notify_mobile_data"
android:title="@string/data_usage_enable_mobile">
<intent
android:action="android.intent.action.MAIN"
android:targetPackage="com.android.phone"
android:targetClass="com.android.phone.MobileNetworkSettings" />
</header>
.........
</preference-headers>
每個可以選擇和點選的item基本有四個屬性,以WiFi_header為例
id:對應的id
fragment:點選之後的fragment:WifiSettings
title:header的主标題,即在Settings主界面顯示的文本:WLAN
icon:header的圖示,即顯示在文本左側的圖示
分析這兩個檔案可以總結下Settings的布局,Settings主界面顯示借助PreferenceActivity,Preference意為偏愛偏好,特點是利用鍵值對記錄使用者上次的選擇,在下次進入到該界面時直接讀取上次的選擇無須再進行配置。Activity意為界面,preferenceactivity結合兩者。每行屬于一個header,相當于listview中的item,每一個header又有fragment與之對應,而fragment的加載依賴于Activity,所依賴的Activity為SubSettings.java(package com.android.settings;//繼承與Settings),在Subsetting.java中已經寫明:
/**
*Stub class for showing sub-settings; we can't use the main Settings class
* since for our app it is a special singleTask class.
* 不能直接使用Settings.java加載fragment,因為,我們的程式啟動模式是singleTask
*/
public class SubSettings extends Settings {
@Override
public boolean onNavigateUp() {
finish();
return true;
}
@Override
protected boolean isValidFragment(String fragmentName) {
return true;
}
}
對Setting源碼的分析可以分兩個步驟進行入手,
第一,headers清單的加載
第二,header的點選事件的處理
解決以上兩個問題後,就可以開始對不同子產品進行分析
2,設定界面布局,加載headers
(1),加載xml布局檔案
可以使用兩種方式加載xml檔案布局
方法一:
loadHeadersFromResource(R.xml.settings_headers, headers);
方法二:
addPreferencesFromResource(R.xml.fragmented_preferences_inner);
(2),定義adapter加載并顯示headers
設定界面布局的擴充卡adapter,有以下幾種type
i>,HEADER_TYPE_CATEGORY:無焦點,不可以點選
ii>,HEADER_TYPE_BUTTON:帶有button的header,button的visibility(可見性)有條件(可自行設定)
iii>,HEADER_TYPE_NORMAL:正常的可擷取焦點可點選的不帶button的header
3,Settings.java源碼分析(部分提取)
(1),onCreate方法中:
if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
}
以上這段代碼用于布局actionbar,即頂部的導航欄布局,如果擷取到的intent中的數值為
ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW,即表示,當螢幕較窄時導航欄有一部分會顯示在底部。
mAuthenticatorHelper = new AuthenticatorHelper();
mAuthenticatorHelper.updateAuthDescriptions(this);
mAuthenticatorHelper.onAccountsUpdated(this, null);
這段代碼屬于配置一些認證或者更新賬戶資訊,一般不做修改
getMetaData();
檢視方法源碼可以看到:方法是擷取到配置檔案Androidmanifest.xml中<meta-data.../>節點下的資料
private void getMetaData() {
try {
//擷取到配置檔案Androidmanifest.xml檔案中<meta-data.../>節點下的資料
ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
PackageManager.GET_META_DATA);
//如果沒有資訊,則傳回
if (ai == null || ai.metaData == null) return;
//擷取到header所對應的id
mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
//擷取到header所對應的fragment檔案
mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
// Check if it has a parent specified and create a Header object
//檢查一下是否有parent,若有,就建立出來
//parent的title
final int parentHeaderTitleRes = ai.metaData.getInt(META_DATA_KEY_PARENT_TITLE);
//parent的fragment
String parentFragmentClass = ai.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);
if (parentFragmentClass != null) {
mParentHeader = new Header();
mParentHeader.fragment = parentFragmentClass;
if (parentHeaderTitleRes != 0) {
mParentHeader.title = getResources().getString(parentHeaderTitleRes);
}
}
} catch (NameNotFoundException nnfe) {
// No recovery
}
}
if (!onIsHidingHeaders() && onIsMultiPane()) {
highlightHeader(mTopLevelHeaderId);
// Force the title so that it doesn't get overridden by a direct launch of
// a specific settings screen.
setTitle(R.string.settings_label);
}
onIsMultiPane()判斷是否雙螢幕MultiPane,平闆雙屏顯示,手機一般單屏SinglePane顯示,是以onIsMultiPane()方法可以設定為傳回false。
onIsHidingHeaders判斷是否是雙屏的headers均有顯示。
如果滿足條件就利用highlightHeader()方法标亮所選擇的header進行差別于其他headers,并且将導航欄title定為設定,保證不被覆寫。
if (onIsMultiPane()) {
//導航欄左上角圖示的左邊是否顯示傳回圖示,false表示不顯示
getActionBar().setDisplayHomeAsUpEnabled(false);
//導航欄左上角圖示是否可點選,false代表不可點選
getActionBar().setHomeButtonEnabled(false);
//導航欄左上角的圖示是否顯示
getActionBar().setDisplayShowHomeEnabled(true)
}
以上代碼是說如果是多屏顯示,則對導航欄左上角程式圖示以及傳回圖示的設定
接下來是利用savedInstanceState恢複資料的操作,不再貼出
showBreadCrumbs(mCurrentHeader.title, null);
設定目前header的标題顯示
if (mParentHeader != null) {
setParentTitle(mParentHeader.title, null, new OnClickListener() {
@Override
public void onClick(View v) {
。。。。。。
}
});
}
設定parentheader的标題title以及設定title的點選事件。
(2),onresume方法,顯示出來所有的header,借助于headerAdapter.resume()方法顯示
header即item需要顯示什麼類型的布局可以在該adapter中進行修改,針對不同的item配置不同的布局檔案
private static class HeaderAdapter extends ArrayAdapter<Header> {
static int getHeaderType(Header header) {
.........
}
public View getView(int position, View convertView, ViewGroup parent) {
..........
}
}
(3),onBuildStartFragmentIntent方法
@Override
public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
int titleRes, int shortTitleRes) {
Intent intent = super.onBuildStartFragmentIntent(fragmentName, args,
titleRes, shortTitleRes);
// Some fragments want split ActionBar; these should stay in sync with
// uiOptions for fragments also defined as activities in manifest.
//有些header所對應的fragment會将資訊同步更新到window即狀态欄
if (WifiSettings.class.getName().equals(fragmentName) ||
WifiP2pSettings.class.getName().equals(fragmentName) ||
BluetoothSettings.class.getName().equals(fragmentName) ||....) {
//将想要更新的資訊傳遞給fragment對應的activity,在這裡是SubSettings
intent.putExtra(EXTRA_UI_OPTIONS, ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW);
}
intent.setClass(this, SubSettings.class);
return intent;
}
(4),onBuildHeaders方法,用來布局,以及更新headers,在PreferenceActivity的oncreate()方法中被調用,以及onGetInitialHeader()方法,也是在PreferenceActivity的oncreate方法中被調用
@Override
public void onBuildHeaders(List<Header> headers) {
if (!onIsHidingHeaders()) {
loadHeadersFromResource(R.xml.settings_headers, headers);
//該方法用于判定某些特定的header是否顯示,
//比如若本機無藍牙子產品則不顯示藍牙的header
updateHeaderList(headers); } }
(5)doValidCheck(),以及isValidFragment 用來檢查fragment是否有效,為适配Android4.4以下版本,保證不出異常
(6)onNewIntent:activity啟動模式為singletask單任務模式,如果在戰中存在activity的執行個體,當再次通過intent調起時不會再去oncreate建立執行個體,而是onNewIntent去重用該執行個體
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// If it is not launched from history, then reset to top-level
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
if (mFirstHeader != null && !onIsHidingHeaders() && onIsMultiPane()) {
switchToHeaderLocal(mFirstHeader);
}
getListView().setSelectionFromTop(0, 0);
}
}
(7)Settings.java中的内部類,Settings.java中有好多實作的内部類
。。。。。。
public static class SecuritySettingsActivity extends Settings { /* empty */ }
public static class LocationSettingsActivity extends Settings { /* empty */ }
。。。。。。。。
這些内部類是為了加載那些fragment,作為fragment的宿主,可以從Androidmanifest.xml中看到,從其他快捷方式進入某個單獨的設定子產品時借助這些内部類來加載。比如可以建立藍牙快捷方式,以及狀态欄進入藍牙時需要借助這些内部類來加載那些fragment。
<activity android:name="Settings$WirelessSettingsActivity"
android:taskAffinity="com.android.settings"
android:label="@string/wireless_networks_settings_title"
android:parentActivityName="Settings">
。。。。。。
</activity>
4,自定義操作
明白Settings界面的布局原理後我們就可以随意的對Settings主界面的布局進行增删改了,對應的是header的修改
(1),修改header:在xml檔案下找到想要修改的header對應的節點,文本,文本左側圖示,以及點選進入的fragment進行相應修改即可
(2),增加header:例如我要增加一項"權限管理",做法如下:
i>,在Settings.headers.xml檔案中增加一個header節點:
<header
android:id="@+id/authority_management
android:fragment="com.android.settings.AuthorityManagementSettings"
android:icon="@drawable/ic_settings_authority"
android:title="@string/authority_settings"/>
ii>,建立一個fragment,AuthorityManagementSettings類
public class DeviceInfoSettings extends RestrictedSettingsFragment {
........
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.authority_management_settings);
.........
}
}