Android Q Settings模块新特性梳理
Android Q Settings模块新特性梳理
1.代码结构差异
Android Q上Settings模块与之前P版本上的Settings模块最直观的差一点是代码结构上的差异。Q上将Settings中会使用到的特有的控件,会移动到SettingsLib下面,为每一种特有控件创建一个目录,并且创建android.bp文件,将widget编译成jar包文件,然后在SettingsLib的android.bp文件中引用这些jar包。比如:
LayoutPreference,SettingsSpinner,AppPreference等等
以LayoutPreference为例:其目录下有res, src,android.bp和AndroidManifest.xml文件,在Android.bp中定义编译完成后生成SettingsLibLayoutPreference
LayoutPreference中的android.bp文件,编译后会生成.jar
在SettingsLib中的android.bp文件中会使用到该jar文件,
其他的控件,包括SettingsSpinner,ProgressBar,SearchWidget,AppPerference等,也都是这样的结构。
2. 一级菜单加载方式差异
Android Q上Settings模块与之前P版本另一个较大差异点,在于主菜单加载方式的差异。Q上相当于在P的基础上,新增了一套加载逻辑,并且P上的加载逻辑还生效。
Android P上一级菜单加载流程:
通过PMS查询action为com.android.settings.action.SETTINGS,并且有meta-data携带有com.android.settings.category.ia.home的Activity组件,解析其数据(title,fragment,icon,等),
将其封装未Tile类型的数据,Tile是SettingsLib中定义的数据结构类,并将其存储在对应的DashboardCategory集合中.Settings启动的时候,SettingsActivity会加载主菜单布局,此时主菜单布局Content view部分其实是一个RecycleView,该RecycleView中会使用DashboardAdapter加载数据和item布局,DashboardAdapter是通过解析上一步存储的DashboardCategory集合中Tile对象获取数据的。而RecycleView中的item的布局是在Settings中定义的布局,类似于Preference的布局,却不是使用的preference布局。
Android Q上一级菜单加载流程:
首先,Q上Settings一级菜单的Content view已经不再是RecycleView,而是一个FrameLayout,后面会将TopLevelSettings(继承与DashboardFragment,最终是继承PreferenceFragment)加到该FrameLayout中。
其次,Settings代码中已经找不到DashboardAdapter这个类,因不再使用RecycleView了,因此不需要通过Adapter加载数据。
最后,Settings主界面的item,Q上是使用的Preference,因此item的布局和preference完全相同(当然,这个也可针对一级菜单单独修改)。
Android Q上一级菜单加载流程分析:
首先,从Settings的AndroidManifest.xml中开始:
点击Launcher中的Settings图标,会打开别名为Settings的Activity,其实际目标Activity是SettingsHomepageActivity,接着查看SettingsHomepageActivity:
SettingsHomepageActivity 的onCreate方法中,依次加载了主菜单父布局,设置了toolbar上的搜索框,加载显示建议的父布局,加载设置主界面TopLevelSettings到main_container中,继续查看TopLevelSettings:
从TopLevelSettings中可获得两点关键信息,
1. TopLevelSettings的父类是DashboardFragment,DashboardFragment的父类是SettingsPreferenceFragment,这也就是为什么后面能将构建的Preference直接加到布局中。
2. getPreferenceScreenResId()方法,返回的是top_level_settings.xml,但是TopLevelPreference中并没有再复写DashboardFragment其他的构造方法,因此,在上一步中new TopLevelPreference往layout_container中加载的时候,其实是优先走DashboardFragment的构造方法和生命周期方法。
在查看DashboardFragment之前,先看下top_level_settings.xml
接着查看DashboardFragment:
首先看onCreate方法,首次进入时,icicle是null,因此onCreate方法中是不会走到updatePreferenceStates()方法中的,updatePreferenceStates()主要是通过每个Preference item的contoller来更新其summary显示。
继续查看其onCreatePreferences()方法:
onCreatePreferences()中调用了refreshAllPreferences()方法,注意此处传入的getLogTag(),其实是调用的子类TopLevelSettings中实现的getLogTag(),返回的是TopLevelSettings,TopLevelSettings会在后面加载数据的时候用到。
查看refreshAllPreferences()方法:
refreshAllPreferences()中有两个关键方法,一个是displayResourceTiles(),这个方法是从xml中加载布局,对于主界面而言,此处是加载top_level_settings布局。
另一个方法,refreshDashboareTiles()这个方法是通过解析对应的DashboardCategory中的tile对象,动态创建Preference并加到布局中。这个流程,与Android P主菜单加载流程相同。
查看displayResourceTiles()方法:
查看refreshDashboardTiles()方法:
refreshDashboardTiles 方法比较长,其功能主要有一下几点:
1. 通过getTilesForCategory()获取与当前category key一直的DashboadCategory对象category(不是集合,可以理解为集合),在category中存储了声明了ia.home的tile对象。
2. 遍历category中存储的tiles,构建preference,通过bindPreferenceToTile方法,将tile中信息与Preference绑定,并将preference添加到PreferenceScreen中,这样preference就被动态添加到一级界面中的DashboardFragment中了。
接下来分析getTilesForCategory()方法,在分析getTilesForCategory()之前,需要先分析getCategoryKey()方法:
这个方法是直接从DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP中获取key为getClass().getName()的value值,而.PARENT_TO_CATEGORY_KEY_MAP是在DashboardFragmentRegistry中定义的静态键值对。
如一级菜单是fragment是从TopLevelSettings,因此返回的是Category.CATEGORY_HOMEPAGE,即com.android.settings.category.ia.home,之后getTilesForCategory()逻辑就与android P相同了,会调用DashboardFeatureProvider的实现类DashboardFeatureProviderImpl的getTilesForCategory()方法,查看Category.CATEGORY_HOMEPAGE的DashboardCategory对象是否存在,不存在的话重新调用PMS查找对应的activity组件,创建tile对象,加到对应的DashboardCategory中,返回DashboardCategory对象。
接着继续分析DashboardFeatureProvider.bindPreferenceToTile()方法,DashboardFeatureProvider的实现类DashboardFeatureProviderImpl,分析DashboardFeatureProviderImpl的bindPreferenceToTile()
查看DashboardFeatureProviderImpl的bindPreferenceToTile() :
至此,一级界面就会显示出来了,当然,这个流程并非仅仅只有一级菜单会使用,所有继承与DashboardFragment的子类显示时,都是这个流程,比如:无线和网络,应用和通知等等。
3. Android Q上设置搜索功能差异点
设置搜索功能,在Android Q上,BasePreferenceContreller中isAvailable()返回值,新增了一种类型,AVAILABLE_UNSEARCHABLE,BasePreferenceContreller的子类中isAvailable()若返回的是AVAILABLE_UNSEARCHABLE,则这个Preference在界面上是显示的,但是不会被加入到设置搜索数据库中。
BasePreferenceContreller.java中声明AVAILABLE_UNSEARCHABLE的地方:
AVAILABLE_UNSEARCHABLE 真正起作用的地方是在BasePreferenceContreller的updateNonIndexableKeys()方法中。
这块判断逻辑是:当前preference处于不可显示状态或者可现实无法搜索时,将pref的key添加到list中去,在初始化搜索数据库时,会将这个list中的数据过滤掉,不往搜索数据库写入数据。
4. 设置搜索功能新增settings:searchable属性
Android Q上Settings中新增了自定义属性settings:searchable,settings:searchable=true时设置搜索框中可搜索到,settings:searchable=false无法搜索到。
如:display_settings.xml
其功能实现的部分在BaseSearchIndexProvider中,在其getNonIndexableKeysFromXml()中会解析xml文件,获取settings:searchable属性,为false时,将其加入到NonIndex的List中,