Settings中主界面加载流程(一级菜单 静态加载)
1 SettingsHomepageActivity
在这个之中的onCreate方法有
showFragment(new TopLevelSettings(), .main_content);
启动了TopLevelSettings这个类,此类又是继承于DashboardFragment。先去查看它本身的构造方法。
2 TopLevelSettings
public TopLevelSettings() { final Bundle args = new Bundle(); // Disable the search icon because this page uses a full search view in
actionbar. (NEED_SEARCH_ICON_IN_ACTION_BAR, false); setArguments(args);}
可以看到通过构造方法传递了一个参数,从注释中可以看出,该参数的用意是由于主界面使用完整的搜索视图所以在主界面的actionbar中隐藏了搜索图标。然后再根据framgments生命周期先来看onAttach()方法:。然后再去查看该类中的onAttach方法。
@Overridepublic void onAttach(Context context) { (context); use().setActivity(getActivity());}
它因为是继承DashboardFragment,我去看该父类的onAttach方法。
3 DashboardFragment
@Overridepublic void onAttach(Context context) { (context); mSuppressInjectedTileKeys =
(().getStringArray( .config_suppress_injected_tile_keys)); mDashboardFeatureProvider = (context). getDashboardFeatureProvider(context); // Load preference controllers from code final List<AbstractPreferenceController> controllersFromCode = createPreferenceControllers(context); // Load preference controllers from xml definition final List<BasePreferenceController> controllersFromXml =
PreferenceControllerListHelper .getPreferenceControllersFromXml(context, getPreferenceScreenResId()); // Filter xml-based controllers in case a similar controller is created from
code already. final List<BasePreferenceController> uniqueControllerFromXml = ( controllersFromXml, controllersFromCode); // Add unique controllers to list. if (controllersFromCode != null) { (controllersFromCode); } (uniqueControllerFromXml); // And wire up with lifecycle. final Lifecycle lifecycle = getSettingsLifecycle(); (controller -> { if (controller instanceof LifecycleObserver) { ((LifecycleObserver) controller); } }); // Set metrics category for BasePreferenceController. final int metricCategory = getMetricsCategory(); (controller -> { if (controller instanceof BasePreferenceController) { ((BasePreferenceController)
controller).setMetricsCategory(metricCategory); } }); mPlaceholderPreferenceController = new DashboardTilePlaceholderPreferenceController(context); (mPlaceholderPreferenceController); for (AbstractPreferenceController controller : mControllers) { addPreferenceController(controller); }}
通过方法注释可以知道该方法主要是完成preference controllers的加载。再去看该类的oncreate方法。
在这里有一个mDashboardFeatureProvider,它提供的数据就是一级菜单这些
@Overridepublic void onCreate(Bundle icicle) { (icicle); // Set ComparisonCallback so we get better animation when list changes. getPreferenceManager().setPreferenceComparisonCallback( new ()); if (icicle != null) { // Upon rotation configuration change we need to update preference states
before any // editing dialog is recreated (that would happen before onResume is
called). updatePreferenceStates(); }}
可以通过注释看见,是设置ComparisonCallback,以便在列表获得更好的动画。
第一次进入时,icicle为null,根据log定位发现,其后调用的onCreatePrefern方法(这个是我打印了log)
@Overridepublic void onCreatePreferences(Bundle savedInstanceState, String rootKey) { ("wuzhangxiao", "wuzhangxiao onCreatePreferences"); checkUiBlocker(mControllers); refreshAllPreferences(getLogTag()); () .map(controller -> (Preference)
findPreference(())) .filter(Objects::nonNull) .forEach(preference -> { // Give all controllers a chance to handle click. /** * 给所有控制器一个处理点击的机会。 */ ().putInt(CATEGORY, getMetricsCategory()); });}
这个里面给所有的菜单点击事件,再去查看refreshAllPreferences方法。
/** * Refresh all preference items, including both static prefs from xml, and dynamic
items from * DashboardCategory. * 刷新所有首选项,包括来自 xml 的静态首选项和来自 DashboardCategory 的动态项目。 */
private void refreshAllPreferences(final String tag) { final PreferenceScreen screen = getPreferenceScreen(); // First remove old preferences. /** * 首先删除旧的偏好。 */ if (screen != null) { // Intentionally do not cache PreferenceScreen because it will be recreated
later. /** * 有意不要缓存 PreferenceScreen,因为稍后会重新创建它。 */ (); } // Add resource based tiles. /** * 添加基于资源的图块。 */ displayResourceTiles(); refreshDashboardTiles(tag); final Activity activity = getActivity(); if (activity != null) { (tag, "All preferences added, reporting fully drawn"); (); } updatePreferenceVisibility(mPreferenceControllers);}
刷新所有preference items,包括来自xml的静态preference和来自DashboardCategory的动态preference,静态xml定义的prefs(调用displayResourceTiles()方法),动态DashboardCategory动态加载(调用refreshDashboardTiles(TAG)方法,其中TAG为 “TopLevelSettings”)。displayResourceTiles():此方法主要是从xml资源文件中加载显示prefs
再去查看该方法调用了一个方法。displayResourceTiles,此方法主要从xml资源文件中加载prefs。
/** * Displays resource based tiles. * 显示基于资源的图块。 */
private void displayResourceTiles() { final int resId = getPreferenceScreenResId(); if (resId <= 0) { return; } addPreferencesFromResource(resId); final PreferenceScreen screen = getPreferenceScreen(); (this); displayResourceTilesToScreen(screen);}
我去查看getPreferenceScreenResId,我又发现它这个方法是@Overrideprotected abstract int getPreferenceScreenResId();
它又由TopLevelSettings继承,该类肯定会去实现
TopLevelSettings
`@Overrideprotected int getPreferenceScreenResId() { return .top_level_settings;}
`布局就开始去显示了。
<Preference
android:key="top_level_network"
android:title="@string/network_dashboard_title"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_network"
android:order="-120"
android:fragment=""
settings:controller=""/>
1、key定义此preference的唯一性ID;
2、title定义标题,此字串显示网络和互联网;
3、summary,此显示WLAN、移动网络、流量使用和热点;
4、icon,定义图标;
5、order,加载显示优先级,order为负时,绝对值越高,界面显示越靠前;order为正时,值越高,显示越靠后;
6、fragment,定义点击此preference所跳转的fragment界面;
7、controller,控制管理类。
相关属性配置后,在机器设备上显示的实际效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lcnDvHEP-1657868814715)(en-resource://database/1452:1)]
但是在这里我有个疑问,因为在这个布局中有着许多的菜单,但是没有显示完全,肯定还有别的方式进行显示。再回去看displayResourceTiles
displayResourceTiles
这个方法中有一个addPreferencesFromResource(resId);
看这个类的在执行什么
SettingsPreferenceFragment addPreferencesFromResource
@Override
public void addPreferencesFromResource
(@XmlRes int preferencesResId) { (preferencesResId); checkAvailablePrefs(getPreferenceScreen());}
@VisibleForTesting
void checkAvailablePrefs(PreferenceGroup preferenceGroup) {
if (preferenceGroup == null) return; for (int i = 0; i < (); i++) { Preference pref = (i); if (pref instanceof SelfAvailablePreference && !((SelfAvailablePreference) pref).isAvailable(getContext())) { (false); } else if (pref instanceof PreferenceGroup) { checkAvailablePrefs((PreferenceGroup) pref); } }}
此方法主要是将preferenceScreen下所有Preference添加到ArrayList中,然后再根据此集合构建生成PreferenceGroupAdapter,最后将此adapter设置到listview中,完成数据绑定,从而完成界面加载。
还是去查看displayResourceTiles类中的方法。
displayResourceTiles
private void displayResourceTiles()
{ final int resId = getPreferenceScreenResId();
if (resId <= 0) { return; }
addPreferencesFromResource(resId);
final PreferenceScreen screen = getPreferenceScreen(); (this);
displayResourceTilesToScreen(screen);}
查看displayResourceTilesToScreen方法
displayResourceTilesToScreen
protected void displayResourceTilesToScreen(PreferenceScreen screen) { ().stream().flatMap(Collection::stream).forEach( controller -> (screen));}
private final Map<Class, List<AbstractPreferenceController>>
mPreferenceControllers = new ArrayMap<>();
在这里时再去查看onAttach方法
onAttach
final List<AbstractPreferenceController> controllers = new ArrayList<>();
// Load preference controllers from code
final List<AbstractPreferenceController> controllersFromCode =
createPreferenceControllers(context);
// Load preference controllers from xml definition
final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
.getPreferenceControllersFromXml(context, getPreferenceScreenResId());
// Filter xml-based controllers in case a similar controller is created from code already.
final List<BasePreferenceController> uniqueControllerFromXml =
(
controllersFromXml, controllersFromCode);
// Add unique controllers to list.
if (controllersFromCode != null) {
(controllersFromCode);
}
(uniqueControllerFromXml);
// And wire up with lifecycle.
final Lifecycle lifecycle = getSettingsLifecycle();
uniqueControllerFromXml
.stream()
.filter(controller -> controller instanceof LifecycleObserver)
.forEach(
controller -> ((LifecycleObserver) controller));
mPlaceholderPreferenceController =
new DashboardTilePlaceholderPreferenceController(context);
(mPlaceholderPreferenceController);
for (AbstractPreferenceController controller : controllers) {
addPreferenceController(controller);
}
在这里又调用了addPreferenceController方法,而刚好与上面的静态加载相关。
addPreferenceController
protected void addPreferenceController(AbstractPreferenceController controller) {
if ((()) == null) {
((), new ArrayList<>());
}
(()).add(controller);
}
而onAttach方法的作用是
1、定义集合controllers
2、从代码中加载preference controllers,调用createPreferenceControllers方法
/**
* Get a list of {@link AbstractPreferenceController} for this fragment.
获取此片段的 {@link AbstractPreferenceController} 列表。
*/
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return null;
}
这个是抽象方法,具体的实现是在子类中,上面分析可知应是子类实现,由于TopLevelSettings未实现此方法,故此返回null。
3、从xml定义中加载preference controllers,调用
// Load preference controllers from xml definition 从 xml 定义加载首选项控制器final List<BasePreferenceController> controllersFromXml =
PreferenceControllerListHelper .getPreferenceControllersFromXml(context, getPreferenceScreenResId());
这个时候的getPreferenceScreenResId()根据上面分析,加载的xml应是top_level_settings.xml,调用getPreferenceControllersFromXml()方法:
PreferenceControllerListHelper
/**
* Instantiates a list of controller based on xml definition.
*/
@NonNull
public static List<BasePreferenceController> getPreferenceControllersFromXml(Context context,
@XmlRes int xmlResId) {
final List<BasePreferenceController> controllers = new ArrayList<>();
List<Bundle> preferenceMetadata;
try {
preferenceMetadata = (context, xmlResId,
MetadataFlag.FLAG_NEED_KEY | MetadataFlag.FLAG_NEED_PREF_CONTROLLER
| MetadataFlag.FLAG_INCLUDE_PREF_SCREEN);
} catch (IOException | XmlPullParserException e) {
(TAG, "Failed to parse preference xml for getting controllers", e);
return controllers;
}
for (Bundle metadata : preferenceMetadata) {
final String controllerName = (METADATA_CONTROLLER);
if ((controllerName)) {
continue;
}
BasePreferenceController controller;
try {
controller = (context, controllerName);
} catch (IllegalStateException e) {
(TAG, "Could not find Context-only controller for pref: " + controllerName);
final String key = (METADATA_KEY);
if ((key)) {
(TAG, "Controller requires key but it's not defined in xml: "
+ controllerName);
continue;
}
try {
controller = (context, controllerName,
key);
} catch (IllegalStateException e2) {
(TAG, "Cannot instantiate controller from reflection: " + controllerName);
continue;
}
}
(controller);
}
return controllers;
}
主要读取xml中配置的每个preference的METADATA_CONTROLLER即(“settings:controller”)属性,以上述网络和互联网菜单项为例,读取的即为"";
首先根据此去调用的createInstance方法,即调用的带一个参数的构造方法:
BasePreferenceController
/**
* Instantiate a controller as specified controller type.
* <p/>
* This is done through reflection. Do not use this method unless you know what you are doing.
*/
public static BasePreferenceController createInstance(Context context, String controllerName) {
try {
final Class<?> clazz = (controllerName);
final Constructor<?> preferenceConstructor = ();
final Object[] params = new Object[]{context};
return (BasePreferenceController) (params);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
throw new IllegalStateException(
"Invalid preference controller: " + controllerName, e);
}
}
而中只包含了一个带两个参数的构造函数,故执行此方法应会抛出异常。
TopLevelNetworkEntryPreferenceController
从而执行异常内语句,首先会再去读取xml中配置的每个preference的METADATA_KEY即(android:key)属性,同样的再据此去调用的构造函数:
public TopLevelNetworkEntryPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mMobileNetworkPreferenceController = new MobileNetworkPreferenceController(mContext);
mTetherPreferenceController = new TetherPreferenceController(
mContext, null /* lifecycle */);
mWifiPreferenceController = new WifiMasterSwitchPreferenceController(
mContext, null /* metrics */);
}
这个时候的preferenceKey即为xml中配置的android:key属性的值,为"top_level_network"。调用父类的构造方法,初始化其他变量完成构造:
BasePreferenceController
public BasePreferenceController(Context context, String preferenceKey) {
super(context);
mPreferenceKey = preferenceKey;
if ((mPreferenceKey)) {
throw new IllegalArgumentException("Preference key must be set");
}
}
赋值mPreferenceKey,在这个时候看一下controller构造方法的相关堆栈调用如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-myRKde08-1657868814716)(en-resource://database/1454:1)]
在这里我就知道了,故getPreferenceControllersFromXml()方法主要是获取xml中每个preference定义的“settings:controller”属性配置的controller name,通过此name去构造相应的controller类,将其添加到集合中并返回。
4、过滤重复定义的controller等,赋值填充mPreferenceControllers。
故mPreferenceControllers主要是各种控制管理类的集合,包含xml中配置的每个preference的“settings:controller”属性和代码中通过createPreferenceControllers()方法构建的。
这个就是走完了
再回到displayResourceTiles()方法:
DashboardFragment displayResourceTilesToScreen
().stream().flatMap(Collection::stream).forEach(
controller -> (screen));
这个语句就是主要调用各个controller的displayPreference
此语句主要就是调用各个controller的displayPreference()方法。
依旧以网络和互联网菜单项为例,xml中配置的controller为"",查看发现,其内并未实现displayPreference()方法,查看继承关系:
TopLevelNetworkEntryPreferenceController
public class TopLevelNetworkEntryPreferenceController extends BasePreferenceController
查看的displayPreference()方法:
BasePreferenceControlle
/**
* Displays preference in this controller.
*/
@Override
public void displayPreference(PreferenceScreen screen) {
(screen);
if (getAvailabilityStatus() == DISABLED_DEPENDENT_SETTING) {
// Disable preference if it depends on another setting.
final Preference preference = (getPreferenceKey());
if (preference != null) {
(false);
}
}
}
1.继续调用父类的displayPreference方法,而它的继承关系如下
public abstract class BasePreferenceController extends
AbstractPreferenceController implements Sliceable {
的displayPreference()方法:
AbstractPreferenceController
/**
* Displays preference in this controller.
*/
public void displayPreference(PreferenceScreen screen) {
final String prefKey = getPreferenceKey();
if ((prefKey)) {
(TAG, "Skipping displayPreference because key is empty:" + getClass().getName());
return;
}
if (isAvailable()) {
setVisible(screen, prefKey, true /* visible */);
if (this instanceof ) {
final Preference preference = (prefKey);
(
() this);
}
} else {
setVisible(screen, prefKey, false /* visible */);
}
}
获取preference的key
/**
* Returns the key for this preference.
*/
public abstract String getPreferenceKey();
的getPreferenceKey()方法:
@Override
public String getPreferenceKey() {
return mPreferenceKey;
}
而据上面分析到mPreferenceKey实质上即为xml中每个preference配置的android:key属性的值,即此处应为"top_level_network"。
isAvailable();判断此preference是否可用即是否应该被显示。如果返回true,则被显示出来,反之则不被显示:
/**
* Returns true if preference is available (should be displayed)
*/
public abstract boolean isAvailable();
抽象方法,继续看子类的实现:
/**
* @return {@code true} when the controller can be changed on the device.
*
* <p>
* Will return true for {@link #AVAILABLE} and {@link #DISABLED_DEPENDENT_SETTING}.
* <p>
* When the availability status returned by {@link #getAvailabilityStatus()} is
* {@link #DISABLED_DEPENDENT_SETTING}, then the setting will be disabled by default in the
* DashboardFragment, and it is up to the {@link BasePreferenceController} to enable the
* preference at the right time.
*
* TODO (mfritze) Build a dependency mechanism to allow a controller to easily define the
* dependent setting.
*/
@Override
public final boolean isAvailable() {
final int availabilityStatus = getAvailabilityStatus();
return (availabilityStatus == AVAILABLE
|| availabilityStatus == AVAILABLE_UNSEARCHABLE
|| availabilityStatus == DISABLED_DEPENDENT_SETTING);
}
调用getAvailabilityStatus()方法:
/**
* @return {@AvailabilityStatus} for the Setting. This status is used to determine if the
* Setting should be shown or disabled in Settings. Further, it can be used to produce
* appropriate error / warning Slice in the case of unavailability.
* </p>
* The status is used for the convenience methods: {@link #isAvailable()},
* {@link #isSupported()}
*/
@AvailabilityStatus
public abstract int getAvailabilityStatus();
抽象方法,按照上述举例,继续查看子类的getAvailabilityStatus()方法:
@Override
public int getAvailabilityStatus() {
return (mContext) ? UNSUPPORTED_ON_DEVICE : AVAILABLE_UNSEARCHABLE;
}
调用setVisible()方法设置是否可被显示:
setVisible(screen, prefKey, true /* visible */);
/**
* Show/hide a preference.
*/
protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) {
final Preference pref = (key);
if (pref != null) {
(isVisible);
}
}
判断controller是否实现了接口,是,则设置监听:
if (this instanceof ) {
final Preference preference = (prefKey);
(
() this);
}
综上,如果希望preference不被显示在界面上,可以通过实现相关preference的controller的getAvailabilityStatus()方法,使此方法的返回值不为AVAILABLE、AVAILABLE_UNSEARCHABLE、DISABLED_DEPENDENT_SETTING即可。
2、继续看查看的displayPreference()方法的剩余语句:
if (getAvailabilityStatus() == DISABLED_DEPENDENT_SETTING) {
// Disable preference if it depends on another setting.
final Preference preference = (getPreferenceKey());
if (preference != null) {
(false);
}
}
根据子类controller实现的getAvailabilityStatus()方法的返回值判断是否需要将此preference置为不可点击。
至此,中displayResourceTiles()方法分析完成。
总结:
1、Settings的主Activity实质实现是在内;
2、Settings的主界面设置item的显示是在fragment上,fragment为,加载显示的布局为top_level_settings.xml;
3、Settings主界面设置项item的加载显示主要分为两部分,一部分是xml定义的静态加载,xml为top_level_settings.xml;一部分是DashboardCategory来获取动态加载,
4、每个设置项item均为一个preference,通过xml定义加载时,必须要有一个controller,可以是在xml中定义"settings:controller"属性声明,名称必须与类的包名路径相同;也可直接在相关fragment中实现createPreferenceControllers()方法去调用构造相关controller。此二者存其一即可。
5、xml中配置preference时,必须定义”android:key“属性;
6、需要隐藏不显示某个设置项时,一是可以直接在xml中注释其定义;二是可以在相关设置项preference的controller类中实现getAvailabilityStatus()方法,使此方法的返回值不为AVAILABLE、AVAILABLE_UNSEARCHABLE、DISABLED_DEPENDENT_SETTING即可;
7、如果需要某个设置项不可点击,一是可以直接调用setEnabled():
final Preference preference = (getPreferenceKey());
if (preference != null) {
(false);
}
二是可以在相关设置项preference的controller类中实现getAvailabilityStatus()方法,使此方法的返回值为DISABLED_DEPENDENT_SETTING即可。