Android Settings 6.0 流程與定製修改
概述
android 6.0 版本的設定程式碼相比4.4就沒有那麼直白,但仍然可以用啟動activity,介面佈局,邏輯控制流程的順序來閱讀理解。
- 入口 匯入Settings原始碼同樣以搜尋android.intent.category.LAUNCHER的方式,可以找到啟動activity名為Settings,開啟程式碼:
public class Settings extends SettingsActivity { /* * Settings subclasses for launching independently. */ public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ } public static class WirelessSettingsActivity extends SettingsActivity { /* empty */ } public static class SimSettingsActivity extends SettingsActivity { /* empty */ } public static class TetherSettingsActivity extends SettingsActivity { /* empty */ } public static class VpnSettingsActivity extends SettingsActivity { /* empty */ }
這裡可以看到與4.4的不同,即通過常規啟動activity形式找到的Settings並不是所熟悉的包含onCreate等生命週期,xml佈局檔案,一些控制邏輯這樣形式的類,其主體定義的都是一些空實現的靜態內部類,但這裡有個比較明顯的繼承關係來自 SettingsActivity,其實從命名也可以猜測得到,這個類才是需要閱讀理解的所在,開啟檢視:
public class SettingsActivity extends Activity
這就來到了熟悉的標準activity模式,這裡同樣結合執行介面找一下佈局實現,自然先從onCreate開始:
setContentView(mIsShowingDashboard ? R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
當以點選Launcher圖示的形式進來,這裡的mIsShowingDashboard為true,即這個主activity引用的佈局檔案為 R.layout.settings_main_dashboard:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main_content" android:layout_height="match_parent" android:layout_width="match_parent" />
這裡可以看到這個主佈局,並沒有太多確切內容,繼續看可以發現:
if (!mIsShowingDashboard) {
mDisplaySearch = false;
// UP will be shown only if it is a sub settings
if (mIsShortcut) {
mDisplayHomeAsUpEnabled = isSubSettings;
} else if (isSubSettings) {
mDisplayHomeAsUpEnabled = true;
} else {
mDisplayHomeAsUpEnabled = false;
}
setTitleFromIntent(intent);
Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
switchToFragment(initialFragmentName, initialArguments, true, false,
mInitialTitleResId, mInitialTitle, false);
} 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);
}
之前提過mIsShowingDashboard為true,這裡會走下面的分子,而switchToFragment運用的是FragmentTransaction替換Fragment的形式來填入實際內容,相信凡是接觸過android應用開發對fragment的使用都不陌生,而這裡正是標準的activity對應一個空的父佈局,然後具體內容由具體的fragment填充;
這裡不得不說android原始碼時常有讓人無法理解的冗長呼叫關聯,一個簡單的功能能秀一二十個類或lib的呼叫,但又時常能發現這種最為基礎,google大神程式設計師竟然也寫了和自己這種初學者類似的程式碼而欣喜。
這裡開啟DashboardSummary,這裡的流程又夾雜了一些呼叫,但仍然是從各個生命週期來分析,並且優先關注onCreateView介面到底如何實現,流程如下:
- onCreateView裡主要是對整個大框架的定義,關鍵內容為mDashboard;
- 然後在onResume()找到了比較明顯的sendRebuildUI()方法;
- 發現其實也就是經過handle機制執行到了rebuildUI(context);
而看到rebuildUI的程式碼便豁然開朗:
List<DashboardCategory> categories =
((SettingsActivity) context).getDashboardCategories(true);
關鍵就在這裡獲取了具體的DashboardCategory列表,也就是主介面一項項功能資訊的內容,後續便是簡單的迴圈解析,填充view,生成介面,而這裡的來源竟然還是在SettingsActivity中: getDashboardCategories > buildDashboardCategories:
private void buildDashboardCategories(List<DashboardCategory> categories) {
categories.clear();
loadCategoriesFromResource(R.xml.dashboard_categories, categories, this);
updateTilesList(categories);
}
到這裡就可以知道具體的功能項資訊由loadCategoriesFromResource 以XML解析的形式將R.xml.dashboard_categories內容封裝到列表變數中,而下面的 updateTilesList(categories)就是4.4也有的針對內容在某些條件下移除一些選項;
對R.xml.dashboard_categories:
<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"
/>
<!-- Bluetooth -->
<dashboard-tile
android:id="@+id/bluetooth_settings"
android:title="@string/bluetooth_settings_title"
android:fragment="com.android.settings.bluetooth.BluetoothSettings"
android:icon="@drawable/ic_settings_bluetooth"
/>
這就是和4.4的header類似的控制元件了,依然是每項對應一個功能項,並標有fragment屬性,最終在DashboardSummary中封裝具體內容,並新增到SettingsActivity的佈局中去。
- 點選事件
可以發現在SettingsActivity以及DashboardSummary中都未發現明顯的點選事件實現,那麼點選每一項是如何進入子功能介面的呢;
其實在DashboardSummary的rebuildUI中可以發現,拿到List後,最終的一個個功能子項為DashboardTileView物件,這裡開啟其程式碼:
public class DashboardTileView extends FrameLayout implements View.OnClickListener {
可以發現其繼承基本的FrameLayout,並清楚寫明瞭點選事件呼叫到Utils.startWithFragment,最終即封裝帶有fragment資訊的intent啟動SubSettings,這也就是為什麼每個功能項點選進入的都是SubSettings,而具體內容就根據fragment資訊來實現。
3.定製化 常見的定製化即增刪功能項,如果熟悉了以上流程,那麼對於刪減功能項就有太多的方式,從佈局檔案,到解析,到封裝到列表,到updateTilesList,到addView,整個流程任何地方進行一下截斷,添加個if之類的就可以去掉,這裡不一一列出;
如果要增加一個功能項,這裡同樣類似於4.4如果要加一個自動開關機功能上去,那麼就可以:
- 佈局檔案中dashboard_categories.xml中:
2. <!-- Schedule Power Alarm -->
3. <dashboard-tile
4. android:id="@+id/schedule_power_settings"
5. android:title="@string/schedule_power_settings_title"
6. android:fragment="com.android.settings.schedulePower.SchedulePowerSettings"
7. android:icon="@drawable/ic_settings_display"
8. />
<!-- About Device -->
<dashboard-tile
這裡對應的資原始檔另外新增,fragment可以先建一個空的類不寫邏輯;
2.SettingsActivity加上新加的id以及fragment類名:
@@ -277,6 +278,7 @@ public class SettingsActivity extends Activity
R.id.accessibility_settings,
R.id.print_settings,
R.id.nfc_payment_settings,
1. R.id.schedule_power_settings,
R.id.home_settings,
R.id.dashboard
};
@@ -354,6 +356,7 @@ public class SettingsActivity extends Activity
ProcessStatsSummary.class.getName(),
DrawOverlayDetails.class.getName(),
WriteSettingsDetails.class.getName(),
2. SchedulePowerSettings.class.getName()
};
這樣只要圖片字串資源都有定義,跳轉的fragment不為空,編譯apk push到機器後,即可看見新加的功能項,點選進入空的fragment,之後再具體完善邏輯功能即可。