Loser工作笔记:Android6.0中Settings启动流程

时间:2024-03-21 18:25:51

1、主页面初始化

1.1 时序图

Loser工作笔记:Android6.0中Settings启动流程

1.2 启动流程

Setting主页面加载的是布局文件dashboard_categories.xml,具体的加载流程可以通过如下分析步骤进行分析。

首先看Setting模块下的AndroidManifest.xml文件,其中展示了四大组件,默认启动入口Activity等信息,并且包含了APP的配置信息,系统需要根据里面的内容运行后APP的代码以及显示界面。位置是package/app/Settings/AndroidManifest.xml。

Loser工作笔记:Android6.0中Settings启动流程

activity-alias可用来设置某个Activity的快捷入口,可以放在桌面上或者通过该别名被其他组件快速调起,可以看出入口activity为Settings。因此可以直接分析Settings.java,从下图可以看出,Settings.java继承了父类SettingsActivity,在其内部都是类的空实现,声明这些Activity的原因主要是为了提高各个设置项、整个Settings的灵活性,方便开发者进行扩展。

Loser工作笔记:Android6.0中Settings启动流程

而Settings的父类SettingsActivity.java就是初始化主界面的程序,接下来分析SettingsActivity中的onCreate初始化流程。

Loser工作笔记:Android6.0中Settings启动流程

着重来看getMetaData()与getIntent()方法,在onCreate方法中首先通过getMetaData()方法获取AndroidManifest.xml中的mate数据,从Settings(或桌面快捷图标)进入时获取的值是null。getIntent方法:布局窗口UI。接下来首先来看getMetaData()方法:

Loser工作笔记:Android6.0中Settings启动流程

从方法的实现可以看出,getMetaData()方法在加载对应的设置项时,会从节点下获取到相对应的FragmentClass的值,即为含有com.android.settings.FRAGMENT_CLASS标签的值,从而对其进行加载。

通过getIntent()方法可以获取到要跳转的fragment。

接下来继续分析onCreate方法:

Loser工作笔记:Android6.0中Settings启动流程

首先通过intent.getStringExtra(EXTRA_SHOW_FRAGMENT)方法获取到要显示的Fragment。然后可以通过mIsShortcut布尔值判断是否属于快捷方式,在判定方法中isShortCutIntent(intent)可以获取到配置文件中定义的<category…节点下的数据判断是否包含某个字符串;而isLikeShortCutIntent(intent)方法可以获取到<action…,判断其是否有特定的action,如果有就属于快捷方式;或者判断key为EXTRA_SHOW_FRAGMENT_AS_SHORTCUT中的值是否为true。

获取到组件名ComponentName以及类名className,如果类名为Settings的类名,则表示正在显示控制面板(设置主页)。

Loser工作笔记:Android6.0中Settings启动流程

通过isSubSettings值判断所加载的activity是否属于SubSettings。

Loser工作笔记:Android6.0中Settings启动流程

setContentView方法加载布局文件,如果显示的设置主界面,即mIsShowingDashboard为true,加载settings_main_dashboard布局,否则加载settings_main_prefs布局。

1.3 加载布局文件

从Settings的主页面布局文件settings_main_dashboard.xml中,可以看出其只是一个帧布局,并没有对Settings主界面布局进行定义,返回SettingsActivity.java中继续看onCreate方法。

通过判断当前显示的是否为主面板,当mIsShowingDashboard为false时(SubSettings),跳转到的界面为initialFragmentName,当mIsShowingDashboard为true时(HwSettings),通过switch ToFragment方法切换加载了DashboardSummary这个Fragment,因此可以进入DashboardSummary.java文件中查找,从onCreateView方法中看出加载的是dashboard.xml布局文件。另外着重看一下rebuildUI()方法,通过rebuildUI()方法循环遍历添加数据显示的过程。

Loser工作笔记:Android6.0中Settings启动流程

接下来看dashboard.xml布局文件内容:

Loser工作笔记:Android6.0中Settings启动流程

在dashboard.xml布局中最外层是ScrollView,在Android6.0的Settings界面能够滑动的原因就在于此。内层是线性布局LinearLayout,设置选项都是放在LinearLayout中的,并且都是动态添加上去的。该文件的作用就是为设置各组件提供了一个显示的模板,并且定义了设置主界面的搜索框search_view。接下来看SettingsActivity.java中的getDashboardCategories ()方法:

Loser工作笔记:Android6.0中Settings启动流程

Loser工作笔记:Android6.0中Settings启动流程

可以看到加载的是dashboard_categories中的布局数据,loadCategoriesFromResource方法解析xml并将解析好的数据资源存放到了List<DashboardCategory>这个集合中,然后通过updateTilesList方法更新categories到界面上,这里可以设置某个categories(比如wifi、蓝牙设置)是否展示。

由此可以看出,该方法内部实现是解析dashboard_categories.xml文件,获取到所有的设置项,所以6.0中Settings的显示内容的条目数量是由资源数量决定的,而这个资源就定义在布局文件,文件路径:Settings/res/xml/dashboard_categories.xml

Loser工作笔记:Android6.0中Settings启动流程

所以如果想要添加一个设置项,只需要在dashboard_categories.xml文件中添加一个控件;如果要删除一个设置项,可以在SettingsActivity.java中的updateTilesList方法中将removetile设置为 true,然后移除。

1.4 搜索框

搜索框可以帮助用户快速找到需要的设置项,搜索框的关键入口在SettingsActivity.java文件中,onCreateOptionsMenu方法,该类实现了SearchView.OnQueryTextListener, earchView.OnCloseListener,MenuItem.OnActionExpandListener几个接口实现了搜索页的弹出和缩放。

switchToSearchResultsFragmentIfNeeded()方法中会跳转到弹出的搜索界面,在搜索框输入文字就能列出用户可能需要查找的设置项。Index.java文件中的updateFromClassNameResource()方法将会把所有可以搜索的项加入到搜索项列表中,该方法最终会调用getSearchIndexProvider()方法去反射查找所有名为"SEARCH_INDEX_DATA_PROVIDER"的变量。

2、子页面初始化

2.1 子页面加载

xml文件中定义节点的样式,添加fragment和添加intent,从而实现点击跳转。当点击 DashboardTileView 的某个对象时会执行DashboardTileView.java文件中的onClick方法:

Loser工作笔记:Android6.0中Settings启动流程

在点击时会跳转至一个具体的 Fragment,而这个 Fragment 已经在 dashboard-category 节点中进行了声明。如果没有配置Fragment,则会通过android:action进行跳转。可以看出,Fragment优先级是高于action的。

2.2 Preference监听

onPreferenceClick、onPreferenceChange与onPreferenceTreeClick的区别

1、使用方式不同:

a、onPreferenceClick 与onPreferenceChange 是通过preference.setOnPreferenceClickListener 和preference.setOnPreferenceChangeListener来注册listener使用的。

Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener 中是对上面两个listener的定义。

b、onPreferenceTreeClick 是PreferenceActivity 中的一个方法,我们可以通过复写它来处理我们自己需要的事务。

2、三者之间的关系:

a、public boolean onPreferenceClick(Preference preference)

说明:当点击控件时触发发生,可以做相应操作。

b、boolean onPreferenceChange(Preference preference,Object objValue)

说明:当Preference的元素值发生改变时,触发该事件。

返回值:true代表将新值写入sharedPreference文件;false 则不将新值写入sharedPreference文件。

c、boolean onPreferenceTreeClick (PreferenceScreen preferenceScreen,Preference prefer)

说明:当Preference控件被点击时,触发该方法。

参数说明:preference点击的对象。

返回值:true代表点击事件已成功捕捉,无须执行默认动作或返回上层调用链。例如,不跳转至默认Intent;false代表执行默认动作并且返回上层调用链。例如,跳转至默认Intent。

在继承PreferenceActivity的Activity可以重写该方法,来完成我们对Preference事件的捕捉。

那么当一个Preference控件被点击或者值发生改变时,触发方法是如何执行的呢?

1、首先调用onPreferenceClick方法,如果该方法返回为true,则不再调用onPreferenceTreeClick方法。如果该方法返回false,则继续调用onPreferenceTreeClick方法。

2、onPreferenceChange的方法独立于其他两种方法运行。也就是说,它总是会运行。

补充:点击某个Preference控件后,会首先回调onPreferenceChange方法,即判断是否保存值,然后再回调onPreferenceClick以及onPreferenceTreeClick方法,因此在onPreferenceClick和onPreferenceTreeClick方法中我们得到的控件值就是最新的Preference控件值。

3、Activity跳转方式

1)显式调用方式

方法一:

Intent intent = new Intent(本类,将要跳转的类); (Intent intent=new Intent(MainActivity.this, SecondActivity.class);)

startActivity(intent);

方法二:

Intent intent2 = new Intent();

Intent2.setClass(本类,将要跳转的类);

startActivity(intent2);

方法三:

Intent intent2 = new Intent();

Intent2.setClassName(this,“将要跳转页面的名字”);

startActivity(intent2);

方法四:

Intent intent2 = new Intent();

Intent2.setComponent(new ComponentName(MainActivity.this,OtherActivity.class));

startActivity(intent2);

2)隐式调用方式(只要action、category、data和将要跳转的activity在AndroidManifest.xml中设置的匹配就OK)

<activity

   android:name="com.saiermeng.intent.SecondActivity"

      android:label="@string/title_activity_second" >

      <intent-filter >

                <action android:name="com.saiermeng.intent.open02"/>

           //在data中设置了哪些,则哪些必须匹配,没设置的可以任意写          

<data android:scheme="http"

                      android:host="www.saiermeng.com"

android:port="8080"

                        android:path="/java"

                 ></data>

//因为startActivity()方法中内置了该类别,索引必须加上此类别,否则android.intent.category.DEFAULT无法跳转

                <category android:name="android.intent.category.DEFAULT"/>

        </intent-filter>

</activity>

3)跳转到另一个Activity后,当返回时能返回数据

  1. 在跳转的Activity端,调用startActivityForResult(intent2,2),跳转到下一个Activity,其中第一个参数为传入的意图对象,第二个为设置的请求码;
  2. 跳转到第二个Activity后,调用setResult(10,intent)方法可以返回上一个Activity,其中第一个参数为结果码,第二个为传入的意图对象;
  3. 在第一个Activity通过onActivityResult()方法获得返回的数据

4、Settings架构

通过Hierarchy Viewer工具可以看到Settings模块主界面显示的是Settings,而在进入子界面之后,显示的是SubSettings。

Loser工作笔记:Android6.0中Settings启动流程

4.1 架构分析

1、Settings主界面Activity使用的是HwSettings。

2、Settings子界面Activity使用的基本上都是SubSettings。

3、Settings与SubSettings中都是空Activity,即没有重写7大生命周期方法。

4、Settings、HwSettings与SubSettings都是继承SettingsActivity。

5、主页面使用的layout是settings_main_dashboard,子界面使用的layout是settings_main_prefs。

6、主界面settings_main_dashboard使用DashboardSummary(Fragment)进行填充,子界面都是使用各自的Fragment进行填充。

7、子界面Fragment基本上都是直接或者间接继承SettingsPreferenceFragment

8、主界面选项列表是定义在dashboard_categories.xml中,此文件是在SettingsActivity的buildDashboardCategories方法中进行解析的。

9、在Settings类中定义了很多空实现的静态class,都是继承的SettingsActivity,主要用于对外提供跳转页面,比如从SystemUI跳转到Settings中的某个界面

10、Settings类中定义的静态类class被定义在AndroidManifest中,通过meta-data参数将对应的Fragment绑定在一起。

11、在Activity中填充Fragment主要使用的是SettingsActivity中的switchToFragment方法