編輯:關於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的內容 Listcategories = ((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數據結構。
1、概述Drawable在我們平時的開發中,基本都會用到,而且給大家非常的有用。那麼什麼是Drawable呢?能夠在canvas上繪制的一個玩意,而且相比於View,並不
有時候,我們在做開發的時候,需要讓用戶更直觀的看到數據變化,而又不應該給其提供一堆表格顯示,有時候就需要用到,類似Excel中的圖表,可是Google官方並沒有提供自帶的
前言去年5月左右的時候,筆者在逛GitHub的時候,看到了一個MVP的項目,叫做mosby,仔細看了源碼和作者介紹的文章後,發現確實有點意思,雖然會需要多寫幾個類和方法,
公司的以前的項目,看到使用了這個Android自帶的倒計時控件Chronometer,現在整合了一下先看看效果:<Chronometer android:id=