文章目錄
Android 6.0 Settings–設置主頁加載流程
聲明
鄭重聲明:博文爲原創內容,可以轉載或引用,但必須在明顯位置標明原文作者和出處,未經同意不得擅自修改本文內容!
博客地址:http://blog.csdn.net/luzhenrong45
代碼環境
以下博文內容基於Android 6.0 Setting原生代碼
Settings
Settings也即大家經常用到的設置應用,每一個Android設備都會預置Settings.apk(MTK平臺重命名爲MtkSettings.apk),一般預置在/system/priv-app/目錄下。Settings 一般是作爲系統全局類功能設置和信息展示的服務類應用,,並對外提供多種功能入口,比如網絡設置、鈴聲設置、日期設置、手機信息顯示等等,部分功能入口支持從第三方應用跳轉進入。這裏從代碼角度記錄下,從啓動設置應用到顯示一級UI頁面的大致加載流程。
首先看下原生設置啓後動的一級主頁樣貌:
圖示設置主頁加載流程
先以一張圖概述整個設置主頁的加載流程,下面再通過文字形式對這個過程進行學習記錄。
文解設置主頁加載流程
設置入口
查看AndroidMenufest.xml找到入口,Settings對應主Activity入口爲Settings
【Settings/AndroidMenufest.xml】
<!-- Alias for launcher activity only, as this belongs to each profile. -->
<activity-alias android:name="Settings"
android:taskAffinity="com.android.settings"
android:label="@string/settings_label_launcher"
android:launchMode="singleTask"
android:targetActivity="Settings">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
Settings繼承自SettingsActivity, 裏面只是聲明瞭一系列內部靜態類,這些內部靜態類對應着各個功能項的設置入口,除此之外Setting並沒有做其他工作。因此,設置的啓動加載實際是在SettingsActivity裏面實現的。
【Settings/Settings.java】
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 */ }
public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }
.......
}
設置主頁加載
SettingsActivity
SettingsActivity繼承自Activity,並實現多種接口, 可以說是整個Settings最爲核心的一個類,設置裏面絕大多數的頁面從SettingsActivity衍生或跳轉,包括今天負責主頁頁面加載顯示的DashboardSummary。
【Settings/SettingsActivity.java】
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
// Should happen before any call to getIntent()
// 讀取是否有配置好的meta數據,進入主頁這裏返回是null
getMetaData();
final Intent intent = getIntent();
// Getting Intent properties can only be done after the super.onCreate(...)
final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
final ComponentName cn = intent.getComponent();
final String className = cn.getClassName();
//mIsShowingDashboard這個變量是關鍵,決定是否顯示Dashboard主頁,
//前面我們說到設置的入口Activity就是Settings.class,因此如果是正常啓動設置,這裏會是true
mIsShowingDashboard = className.equals(Settings.class.getName());
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
mContent = (ViewGroup) findViewById(R.id.main_content);
getFragmentManager().addOnBackStackChangedListener(this);
if (savedState != null) {
......
} else {
if (!mIsShowingDashboard) {
Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
switchToFragment(initialFragmentName, initialArguments, true, false, mInitialTitleResId, mInitialTitle, false);
} else {
//mIsShowingDashboard爲true,因此會跳轉到 DashboardSummary 中
mInitialTitleResId = R.string.dashboard_title;
switchToFragment(DashboardSummary.class.getName(), null, false, false, mInitialTitleResId, mInitialTitle, false);
}
}
......
}
SettingsActivity的onCreate的函數裏面會獲取和初始化很多相關變量,其中我們比較關注的是mIsShowingDashboard這個布爾型變量,從變量名稱不難判斷,這個是代表是否顯示Dashboard主頁的。如果該變量是true,則下面會通過 Fragment 常見的replace方式,跳轉到DashboardSummary中。
【Settings/SettingsActivity.java】
switchToFragment(DashboardSummary.class.getName(), null, false, false,
mInitialTitleResId, mInitialTitle, false);
/**
* Switch to a specific Fragment with taking care of validation, Title and BackStack
*/
private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) {
if (validate && !isValidFragment(fragmentName)) {
throw new IllegalArgumentException("Invalid fragment for this activity: "
+ fragmentName);
}
Fragment f = Fragment.instantiate(this, fragmentName, args);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.main_content, f);
if (withTransition) {
TransitionManager.beginDelayedTransition(mContent);
}
if (addToBackStack) {
transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
}
if (titleResId > 0) {
transaction.setBreadCrumbTitle(titleResId);
} else if (title != null) {
transaction.setBreadCrumbTitle(title);
}
transaction.commitAllowingStateLoss();
getFragmentManager().executePendingTransactions();
return f;
}
另外,與主頁加載相關的,SettingsActivity還爲後續 的DashboardSummary做了dashboard_categories.xml的解析工作:
先看一下dashboard_categories.xml的內容和格式:
【Settings/res/xml/dashboard_categories.xml】
<dashboard-categories
xmlns:android="http://schemas.android.com/apk/res/android">
//dashboard-category代表一個大的功能分區
<!-- 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-tile代表某個具體功能項,如藍牙,wifi,數據等
<!-- 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"
/>
</dashboard-category>
...... //其他分區和子項類同
</dashboard-categories>
說明:
- 一個dashboard-categories裏面包含多個dashboard-category
- 一個dashboard-category裏面包含多個dashboard-tile
- 每一個dashboard-category代表一個大的分區,原生設置有四大區,分別爲“無線和網絡”、“設備”、“個人”、“系統”。無線與網絡區裏面會包含藍牙,wifi,數據等多個子項
- 每一個dashboard-tile代表一個具體的功能項入口,如藍牙、wifi、數據等
dashboard_categories.xml可以理解爲設置主頁的“layout”,只不過它並非我們常見的佈局文件的格式,需要對其進行解析。而提此重任的即是SettingsActivity,SA通過pull的方式對xml文件內容進行解析,將解析的結果保存在類型爲DashboardCategory的List列表中,返回給後面的DashboardSummary調用。
【Settings/SettingsActivity.java】
public List<DashboardCategory> getDashboardCategories(boolean forceRefresh) {
if (forceRefresh || mCategories.size() == 0) {
buildDashboardCategories(mCategories);
}
return mCategories;
}
private void buildDashboardCategories(List<DashboardCategory> categories) {
categories.clear();
//解析dashboard_categories.xml文件
loadCategoriesFromResource(R.xml.dashboard_categories, categories, this);
updateTilesList(categories);
}
xml解析方式是常用的pull方式:
DashboardSummary
DashboardSummary主要有以下幾個作用:
- 獲取SettingsActivity對dashboard_categories.xml的解析結果
- 根據獲取的DashboardCategory列表,依次加載顯示各個列表項,形成設置一級頁面
- 註冊Package廣播接收器,監聽Package移除、更新、改變等狀態變化,更新UI頁面
【Settings/DashboardSummary.java】
//負責更新加載主頁UI
private void rebuildUI(Context context) {
......
mDashboard.removeAllViews();
//獲取SettingsActivity對dashboard_categories.xml的解析結果
List<DashboardCategory> categories =
((SettingsActivity) context).getDashboardCategories(true);
//獲取DashboardCategory個數,原生代碼有四大區
final int count = categories.size();
//依次對解析的DashboardCategory進行處理,添加子view
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);
DashboardTileView tileView = new DashboardTileView(context);
updateTileView(context, res, tile, tileView.getImageView(),
tileView.getTitleTextView(), tileView.getStatusTextView());
tileView.setTile(tile);
categoryContent.addView(tileView);
}
// Add the category
//將處理好的DashboardCategory添加到Dashboard主頁中來
mDashboard.addView(categoryView);
}
long delta = System.currentTimeMillis() - start;
Log.d(LOG_TAG, "rebuildUI took: " + delta + " ms");
}
下面是註冊監聽package的狀態變化,及時更新UI頁面。
【Settings/DashboardSummary.java】
private class HomePackageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//監聽狀態變化,更新UI
rebuildUI(context);
}
}
private HomePackageReceiver mHomePackageReceiver = new HomePackageReceiver();
@Override
public void onResume() {
super.onResume();
sendRebuildUI();
final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addDataScheme("package");
getActivity().registerReceiver(mHomePackageReceiver, filter);
}
至此,設置的一級頁面就加載顯示出來了。
修改說明
作者 | 版本 | 修改時間 | 修改說明 |
---|---|---|---|
WalkAloner | V1.0 | 2020/03/11 | 第一版 |