Android 系统 Settings 启动流程详解

Settings简介

  Settings 是 Android 系统自带的一个很重要的应用,给用户提供了操作 Android 系统功能的界面。它里面包含了 Wireless & network,device,personal 以及 system 等几大块的功能设置。在 Android 源码中,该应用位于 packages/apps/Settings 下。该应用的源码是相当复杂的,设计思想很是先进,很难完全讲清楚,笔者也是读了好几遍源码再综合了几篇博客才勉强懂了Settings其启动流程的大体思路。通过博客记录下来以加深理解和印象,同时分享给大家。   

Settings 启动流程详解

1.直接跳转子界面

  首先找到 Settings 目录,其目录结构如下,文件太多,无法完展开。
  这里写图片描述
  
这里通过每个文件夹的命名可以大概知道,每个包的大体作用是什么。由于本文主要讲解启动流程,所以先不管这些。我们先找到 Settings 的启动类,通常我们可以从清单文件中得知该应用的启动类,如下图:
这里写图片描述

从图中可以清楚的看到,Settings 的启动类为 Settings。从 Settings 源码中我们找到了Settings.java文件。但是,打开这个文件后,会感到了一脸懵逼。如下图:
这里写图片描述

该类中都是些空实现的静态内部类,没有任何与界面加载相关的内容。这是为什么呢?看上面有句英文注释就明白了,意思是这些子类是为了启动特定独立的 Settings 选项而创建的,例如在某个应用里需要设置无线那么只需要启动无线对应的类就可以了,而没必要打开settings应用再点击wifi设置项进行设置。再看此类继承于 SettinggsActivity,这时我们就应该可以想到,初始化界面应该在它父类 SettinggsActivity 里完成的。为了方便讲解,我们先以wifi设置页面WifiSettingsActivity 的直接跳转为例,详细讲解这个启动流程。懂了这个之后,其他子页面的启动自然就明白了。

接下来我们在清单文件中找到 WifiSettingsActivity 的定义如下:
  这里写图片描述
  
其中有 meta-data 的标签使用,从这个标签的 key-value 来看,很明显可以认为WifiSettings的具体实现应该是由 WifiSettings 这个 Fragment 来布局渲染的。然后我们回到 SettingsActivity 中,找到 onCreate() 方法如下:
这里写图片描述

可以看到,一进入 oncreate 里有个 getMetaData(), 这和我们之前看到的清单文件里的meta似乎有某种联系,点进去看,代码如下:
这里写图片描述

可以看到,这个函数的主要作用就是从 Activity 标签中获取 meta-data 标签中key为 com.android.settings.FRAGMENT_CLASS 的值,并将其赋值给 mFragmentClass 这个私有变量。
以 WifiSettingsActivity为 例,从这个 Activity 中 meta-data 标签中获取的信息为 com.android.settings.wifi.WifiSettings,即mFragmentClass=”com.android.settings.wifi.WifiSettings”。
getMetaData() 执行完后紧接着执行了 getIntent(),getMetaData() 上面有句注释 should happen before any call to getIntent。意思是 getIntent() 必须在 getMetaData() 之后执行,其实这也有原因的,点进 getIntent() 方法看看就知道了。代码具体如下:
这里写图片描述

继续看 getStartingFragmentClass():
这里写图片描述

从源码看以看出,getIntent 的作用就是构造了一个 Intent,并且给它增加了一个特殊的键值对,key为”:settings:show_fragment”,value为 mFragmentClass 指定的 Fragment 类名。
之所以要先执行getMetaData,是因为 mFragmentClass 赋值是在 getMeatData 中进行的。

明白之后我们继续分析onCreate()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
final ComponentName cn = intent.getComponent();
final String className = cn.getClassName();// 本例中,className为WifiSettingsActivity
mIsShowingDashboard = className.equals(Settings.class.getName()); //因此这里为false
...
...
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);//本例中这里选择了后者
...
...
} else {
if (!mIsShowingDashboard) {//因为mIsShowingDashboard为false,所以会到这里
....
//initialArguments通过赋值保存了meta-data中指定的com.android.settings.wifi.WifiSettings
Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
switchToFragment(initialFragmentName, initialArguments, true, false,
mInitialTitleResId, mInitialTitle, false);//走到这里进行fragment替换
} 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);
}
}

我们来具体看一下 switchToFragment() 方法:
这里写图片描述

通过 FragmentTransaction 的 replace 方法,将Fragment的布局在 R.id.main_content 指定的位置进行渲染。

2.主界面启动流程

上面讲的是没有通过点击 Settings 主界面的选项而直接打开子界面的启动过程,下面我们介绍通过点击setting主界面的选项进入子界面的过程。
  通过前面的讲解我们知道,mIsShowingDashboard 的值( true/false )是确实加载主界面还是子界面的唯一条件。我们回到相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
final ComponentName cn = intent.getComponent();
final String className = cn.getClassName();// 因为从主界面启动,所以这里className为Settings
mIsShowingDashboard = className.equals(Settings.class.getName()); //因此这里变成了true
...
...
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);//选择了前者
...
...
} else {
if (!mIsShowingDashboard) {//因为mIsShowingDashboard为true,不走这里了
....

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);//接下来重点分析这里
}
}

从上面分析可以知道如果从主界面进入的话 switchToFragment 会将当前页面替换成 DashboardSummary,我们看一下 DashboardSummary.java 的代码:
这里写图片描述

这是一个 fragment,在 onCreateView 里,填充了 dashboard.xml. 来看一下这个布局:
这里写图片描述

这是一个垂直可滚动的线性结构,很容易联想到我们手机里的设置主页面,的确如此。再继续看DashboardSummary 代码,在 onResume() 里:
这里写图片描述

有 SendReBuildUI(),点进去查看:
这里写图片描述

原来里面是在发消息,找到消息的接收者:
这里写图片描述

终于发现了里面的 reBuildUI 的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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.removeAllViews();
//(1)这里调用SettingActivity的getDashboardCategories,也就是加载整个Setting的内容
List<DashboardCategory> 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)处 rebuildUI 里调用 getDashboardCategories() 方法,该方法如下:

这里写图片描述

这个方法里又调用了 buildDashboardCategories() 方法:
这里写图片描述

看到这里终于明白了,里面有个对 dashboard_categories.xml 的处理, loadCategoriesFromResource() 方法就不看了,它的作用是解析 dashboard_categories.xml 这个 xml 文件。我们看一下dashboard_categories.xml 吧:
这里写图片描述

部分截图,没有截图,因为内容太多了。不过从这局部就可以看出这对应的就是我们设置主页面的各个选项。
这里写图片描述

(2)处将通过 for 循环遍历而来的数据通过创建 DashboardTileView 最终全部存入到 mDashboard 这个布局中,至此整个 Setting 模块的界面布局已经完成了。

在 DashboardTileView.java 里,有个 onclick 方法,这就是 settings 主页面每个子选项的点击事件了,通过点击进入不同的子设置选项,如 wifi,蓝牙等。
这里写图片描述

至此,Settings 的启动方式讲解完了,下面附一张自己手画的一张 Settings 启动流程的草图,画的比较丑,凑合的看….:
这里写图片描述

参考文章:
http://www.itdadao.com/articles/c15a227784p0.html
http://blog.csdn.net/wzy_1988/article/details/50556113

文章目录
  1. 1. Settings简介
  2. 2. Settings 启动流程详解
  3. 3. 1.直接跳转子界面
  4. 4. 2.主界面启动流程
|