編輯:關於Android編程
前述:
本人已工作兩年多,但是依然感覺還是Android的門外漢,之前一直從事Android的應用開發,每天就是各種調用SDK方法,各種拷貝網上的源碼以及jar包,從來也不管為啥這樣用,由於換了一份工作才開始接觸到Android的源碼,感覺Android的水好深啊。
今天這篇博客也是我的處女作啊,以後也希望通過多多研究源碼來寫出更多的博客,我覺得寫博客主要還是作為一個記錄吧,不然感覺有的東西真的很容易丟,尤其是平時不怎麼接觸的模塊。
好啦,接下來開始今天的Setting旅行啦。
Settings簡述:
Setting模塊大家還是比較熟悉的吧?其實Setting也不是什麼高級的東西,它就是一個APP,屬於Android的應用層,源碼在packages\apps\Settings中,今天分析的源碼是基於Android5.1,如下圖是5.1Setting模塊的界面:

查看一個應用首先是查看這個應用的AndroidManifest.xml文件,以便查看程序的入口,Setting模塊的入口是Setting.java這個類,這個類繼承SettingActivity,但是沒有繼承任何的方法,但卻定義了一大堆內部類。
/**
* Top-level Settings activity
*/
public class Settings extends SettingsActivity {
public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }
public static class StorageSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiP2pSettingsActivity extends SettingsActivity { /* empty */ }
public static class InputMethodAndLanguageSettingsActivity extends SettingsActivity { /* empty */ }
public static class KeyboardLayoutPickerActivity extends SettingsActivity { /* empty */ }
public static class InputMethodAndSubtypeEnablerActivity extends SettingsActivity { /* empty */ }
public static class VoiceInputSettingsActivity extends SettingsActivity { /* empty */ }
public static class SpellCheckersSettingsActivity extends SettingsActivity { /* empty */ }
public static class LocalePickerActivity extends SettingsActivity { /* empty */ }
public static class UserDictionarySettingsActivity extends SettingsActivity { /* empty */ }
public static class HomeSettingsActivity extends SettingsActivity { /* empty */ }
public static class DisplaySettingsActivity extends SettingsActivity { /* empty */ }
public static class DeviceInfoSettingsActivity extends SettingsActivity { /* empty */ }
public static class ApplicationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ManageApplicationsActivity extends SettingsActivity { /* empty */ }
}
這些類都是Setting模塊的子界面類,是特定功能的類,比如WifiSettingsActivity是WiFi模塊相關的類。
所以接下來我們直接分析SettingActivity這個類就可以了。
SettingActivity.java:
先看該類的OnCreate方法
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
// Should happen before any call to getIntent()
getMetaData();
final Intent intent = getIntent();
先調用getMetaData()方法,用於加載一些元數據,進入getMetaData()方法
private void getMetaData() {
try {
ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
PackageManager.GET_META_DATA);
if (ai == null || ai.metaData == null) return;
mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
} catch (NameNotFoundException nnfe) {
// No recovery
Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
}
}
主要作用就是通過META_DATA_KEY_FRAGMENT_CLASS這個屬性獲得額外的mFragmentClass,如果可以獲得將啟動對應的mFragmentClass的Activity,但是直接啟動Setting不會獲得該數據。
繼續往下看代碼
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);
由於我們是從Setting啟動的,所以mIsShowingDashboard的值為true,而isSubSettings的值是false。
setContentView(mIsShowingDashboard ? R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
由於mIsShowingDashboard的值為true,所以使用的是R.layout.settings_main_dashboard
<framelayout android:background="@color/dashboard_background_color" android:id="@+id/main_content" android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"></framelayout>
同時繼續往下走,會看到這段代碼塊:
if (savedState != null) {
...
} else {
if (!mIsShowingDashboard) {
...
} else {
// No UP affordance if we are displaying the main Dashboard
mDisplayHomeAsUpEnabled = false;
// Show Search affordance
mDisplaySearch = true;
mInitialTitleResId = R.string.dashboard_title;
switchToFragment(DashboardSummary.class.getName(), null, false, false,
mInitialTitleResId, mInitialTitle, false);
}
}
這裡由於是第一次啟動,所以savedState 為null,同時mIsShowingDashboard的值為true,看到進入了switchToFragment這個方法,這裡准備切換到DashboardSummary這個Fragment。
DashboardSummary.java
DashboardSummary的onCreateView方法加載了R.layout.dashboard,代碼如下:
接下來將是重點,開始真正加載Setting的界面了,在OnResume方法中最終會調用rebuildUI()方法,該方法源碼:
private void rebuildUI(Context context) {
if (!isAdded()) {
Log.w(LOG_TAG, "Cannot build the DashboardSummary UI yet as the Fragment is not added");
return;
}
long start = System.currentTimeMillis();
final Resources res = getResources();
//mDashboard這個View就是整個界面的總View
mDashboard.removeAllViews();
(1)這裡調用SettingActivity的getDashboardCategories,也就是加載整個Setting的內容
List categories =
((SettingsActivity) context).getDashboardCategories(true);
final int count = categories.size();
for (int n = 0; n < count; n++) {
DashboardCategory category = categories.get(n);
View categoryView = mLayoutInflater.inflate(R.layout.dashboard_category, mDashboard,
false);
TextView categoryLabel = (TextView) categoryView.findViewById(R.id.category_title);
categoryLabel.setText(category.getTitle(res));
ViewGroup categoryContent =
(ViewGroup) categoryView.findViewById(R.id.category_content);
final int tilesCount = category.getTilesCount();
for (int i = 0; i < tilesCount; i++) {
DashboardTile tile = category.getTile(i);
//(2)創建DashboardTileView,也就是每個Setting的內容
DashboardTileView tileView = new DashboardTileView(context);
updateTileView(context, res, tile, tileView.getImageView(),
tileView.getTitleTextView(), tileView.getStatusTextView());
tileView.setTile(tile);
categoryContent.addView(tileView);
}
// Add the category
mDashboard.addView(categoryView);
}
long delta = System.currentTimeMillis() - start;
Log.d(LOG_TAG, "rebuildUI took: " + delta + " ms");
}
接下來將對上面代碼標注的序號處進行說明:
(1)處最終會調用SettingActivity的buildDashboardCategories方法,
private void buildDashboardCategories(Listcategories) { categories.clear(); loadCategoriesFromResource(R.xml.dashboard_categories, categories); updateTilesList(categories); }
以下為Setting頁面的xml文檔:
vcnlfd2lyZWxlc3NfbmV0d29ya3M=">
根據這個文件可看出來,dashboard-categories這個標簽對應著Java代碼中的List
(2)處將通過for循環遍歷而來的數據通過創建DashboardTileView最終全部存入到mDashboard這個布局中,至此整個Setting模塊的界面布局已經完成了。
DashboardTileView.java
這個類是Setting中每個條目數據的類,通過onClick方法啟動不同的功能,比如WiFi,Bluetooth等
public class DashboardTileView extends FrameLayout implements View.OnClickListener {
@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) {
getContext().startActivity(mTile.intent);
}
}
}
總結一下:
1、整個Setting模塊是在SettingActivity中加載DashboardSummary這個Fragment,然後從dashboard_categories.xml中讀取預先配置好的文件來初始化Settings的首界面視圖。
2、Setting的子界面基本上都是一個Fragment,並且基本上都是通過SettingActivity的OnCreate方法去加載的,同時大部分SubSetting在加載界面時,用的都是PreferenceFragment技術(在以後的博客中會講述)。
3、Android5.1的Setting使用的是DashboardCategory和DashboardTile類來存儲整個xml數據結構。
Android Drawable 那些不為人知的高效用法分享
1、概述Drawable在我們平時的開發中,基本都會用到,而且給大家非常的有用。那麼什麼是Drawable呢?能夠在canvas上繪制的一個玩意,而且相比於View,並不
Android中自定義折線圖
有時候,我們在做開發的時候,需要讓用戶更直觀的看到數據變化,而又不應該給其提供一堆表格顯示,有時候就需要用到,類似Excel中的圖表,可是Google官方並沒有提供自帶的
從零開始的Android新項目3 - 誰告訴你MVP和MVVM是互斥的
前言去年5月左右的時候,筆者在逛GitHub的時候,看到了一個MVP的項目,叫做mosby,仔細看了源碼和作者介紹的文章後,發現確實有點意思,雖然會需要多寫幾個類和方法,
Android自帶倒計時控件Chronometer使用方法詳解
公司的以前的項目,看到使用了這個Android自帶的倒計時控件Chronometer,現在整合了一下先看看效果:<Chronometer android:id=