文章目录
Android 6.0 Settings–设置主页加载流程
声明
郑重声明:博文为原创内容,可以转载或引用,但必须在明显位置标明原文作者和出处,未经同意不得擅自修改本文内容!
博客地址:http://blog.youkuaiyun.com/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 | 第一版 |