分析Android 11.0Settings源码之主界面加载
作者:Brave__ 发布时间:2021-05-25 23:15:02
本篇主要记录AndroidR Settings源码主界面加载流程,方便后续工作调试其流程。
Settings代码路径:
packages/app/Settings/
Settings代码获取:
Setting 源码下载地址:https://github.com/aosp-mirror/platform_packages_apps_settings
git地址:https://github.com/aosp-mirror/platform_packages_apps_settings.git
主界面加载:
首先我们来看 Settings 模块中的 AndroidManifest.xml 文件,找到默认启动入口Activity信息:
<activity android:name=".homepage.SettingsHomepageActivity"
android:label="@string/settings_label_launcher"
android:theme="@style/Theme.Settings.Home"
android:taskAffinity="com.android.settings.root"
android:launchMode="singleTask"
android:configChanges="keyboard|keyboardHidden">
<intent-filter android:priority="1">
<action android:name="android.settings.SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
//activity-alias可用来设置某个Activity的快捷入口,可以放在桌面上或者通过该别名被其他组件快速调起。
//android:targetActivity为目标Activity.
<!-- Alias for launcher activity only, as this belongs to each profile. -->
<activity-alias android:name="Settings"
android:label="@string/settings_label_launcher"
android:taskAffinity="com.android.settings.root"
android:launchMode="singleTask"
android:targetActivity=".homepage.SettingsHomepageActivity">
<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>
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
</activity-alias>
可以看到Settings的桌面图标启动的主界面是Settings.java,但其xml定义了targetActivity属性,实质应是SettingsHomepageActivity.java,从onCreate()方法开始:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_homepage_container);
final View root = findViewById(R.id.settings_homepage_container);
root.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
setHomepageContainerPaddingTop();
final Toolbar toolbar = findViewById(R.id.search_action_bar);
FeatureFactory.getFactory(this).getSearchFeatureProvider()
.initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);
final ImageView avatarView = findViewById(R.id.account_avatar);
getLifecycle().addObserver(new AvatarViewMixin(this, avatarView));
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
// Only allow contextual feature on high ram devices.
showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content);
}
showFragment(new TopLevelSettings(), R.id.main_content);
((FrameLayout) findViewById(R.id.main_content))
.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
}
可以看到主界面的layout为settings_homepage_container.xml:
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/settings_homepage_container"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="@+id/main_content_scrollable_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.android.settings.widget.FloatingAppBarScrollingViewBehavior">
<LinearLayout
android:id="@+id/homepage_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:id="@+id/contextual_cards_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/contextual_card_side_margin"
android:layout_marginEnd="@dimen/contextual_card_side_margin"/>
<FrameLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="?android:attr/windowBackground"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:touchscreenBlocksFocus="false"
android:keyboardNavigationCluster="false">
<include layout="@layout/search_bar"/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
主界面布局中主要包含两部分:一个顶部快捷搜索栏,一个Id为main_content的FrameLayout就是用来显示主设置内容的,即Settings的一级菜单项界面。
回到onCreate()方法:
showFragment(new TopLevelSettings(), R.id.main_content);
可以看到一级菜单启动的是TopLevelSettings,TopLevelSettings继承于DashboardFragment.java:
public class TopLevelSettings extends DashboardFragment implements
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback
TopLevelSettings的构造方法:
public TopLevelSettings() {
final Bundle args = new Bundle();
// Disable the search icon because this page uses a full search view in actionbar.
args.putBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, false);
setArguments(args);
}
可以看到通过构造方法传递了一个参数,从注释中可以看出,该参数的用意是由于主界面使用完整的搜索视图所以在主界面的actionbar中隐藏了搜索图标。然后再根据framgments生命周期先来看onAttach()方法:
@Override
public void onAttach(Context context) {
super.onAttach(context);
use(SupportPreferenceController.class).setActivity(getActivity());
}
调用父类DashboardFragment.java的onAttach()方法:
@Override
public void onAttach(Context context) {
super.onAttach(context);
mSuppressInjectedTileKeys = Arrays.asList(context.getResources().getStringArray(
R.array.config_suppress_injected_tile_keys));
mDashboardFeatureProvider = FeatureFactory.getFactory(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 =
PreferenceControllerListHelper.filterControllers(
controllersFromXml, controllersFromCode);
// Add unique controllers to list.
if (controllersFromCode != null) {
mControllers.addAll(controllersFromCode);
}
mControllers.addAll(uniqueControllerFromXml);
// And wire up with lifecycle.
final Lifecycle lifecycle = getSettingsLifecycle();
uniqueControllerFromXml.forEach(controller -> {
if (controller instanceof LifecycleObserver) {
lifecycle.addObserver((LifecycleObserver) controller);
}
});
// Set metrics category for BasePreferenceController.
final int metricCategory = getMetricsCategory();
mControllers.forEach(controller -> {
if (controller instanceof BasePreferenceController) {
((BasePreferenceController) controller).setMetricsCategory(metricCategory);
}
});
mPlaceholderPreferenceController =
new DashboardTilePlaceholderPreferenceController(context);
mControllers.add(mPlaceholderPreferenceController);
for (AbstractPreferenceController controller : mControllers) {
addPreferenceController(controller);
}
}
通过方法注释可以得知此方法主要是完成preference controllers的加载。
DashboardFragment.java的onCreate()方法:
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// Set ComparisonCallback so we get better animation when list changes.
getPreferenceManager().setPreferenceComparisonCallback(
new PreferenceManager.SimplePreferenceComparisonCallback());
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定位发现,其后调用DashboardFragment.java的onCreatePreferences()方法:
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
checkUiBlocker(mControllers);
refreshAllPreferences(getLogTag());
mControllers.stream()
.map(controller -> (Preference) findPreference(controller.getPreferenceKey()))
.filter(Objects::nonNull)
.forEach(preference -> {
// Give all controllers a chance to handle click.
preference.getExtras().putInt(CATEGORY, getMetricsCategory());
});
}
调用refreshAllPreferences():
/**
* Refresh all preference items, including both static prefs from xml, and dynamic items from
* 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.
screen.removeAll();
}
// Add resource based tiles.
displayResourceTiles();
refreshDashboardTiles(tag);
final Activity activity = getActivity();
if (activity != null) {
Log.d(tag, "All preferences added, reporting fully drawn");
activity.reportFullyDrawn();
}
updatePreferenceVisibility(mPreferenceControllers);
}
刷新所有preference items,包括来自xml的静态preference和来自DashboardCategory的动态preference,静态xml定义的prefs(调用displayResourceTiles()方法),动态DashboardCategory动态加载(调用refreshDashboardTiles(TAG)方法,其中TAG为 “TopLevelSettings”)。
displayResourceTiles():此方法主要是从xml资源文件中加载显示prefs:
/**
* Displays resource based tiles.
*/
private void displayResourceTiles() {
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
return;
}
addPreferencesFromResource(resId);
final PreferenceScreen screen = getPreferenceScreen();
screen.setOnExpandButtonClickListener(this);
displayResourceTilesToScreen(screen);
}
/**
* Perform {@link AbstractPreferenceController#displayPreference(PreferenceScreen)}
* on all {@link AbstractPreferenceController}s.
*/
protected void displayResourceTilesToScreen(PreferenceScreen screen) {
mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(
controller -> controller.displayPreference(screen));
}
静态加载
首先调用getPreferenceScreenResId()方法获取所要加载的xml的ID,然后调用子类TopLevelSettings.java的getPreferenceScreenResId()方法:
@Override
protected int getPreferenceScreenResId() {
return R.xml.top_level_settings;
}
可以看到Settings主界面加载的xml文件是top_level_settings:
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="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="com.android.settings.network.NetworkDashboardFragment"
settings:controller="com.android.settings.network.TopLevelNetworkEntryPreferenceController"/>
<Preference
android:key="top_level_connected_devices"
android:title="@string/connected_devices_dashboard_title"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_connected_device"
android:order="-110"
android:fragment="com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment"
settings:controller="com.android.settings.connecteddevice.TopLevelConnectedDevicesPreferenceController"/>
<Preference
android:key="top_level_apps_and_notifs"
android:title="@string/app_and_notification_dashboard_title"
android:summary="@string/app_and_notification_dashboard_summary"
android:icon="@drawable/ic_homepage_apps"
android:order="-100"
android:fragment="com.android.settings.applications.AppAndNotificationDashboardFragment"/>
<Preference
android:key="top_level_battery"
android:title="@string/power_usage_summary_title"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_battery"
android:fragment="com.android.settings.fuelgauge.PowerUsageSummary"
android:order="-90"
settings:controller="com.android.settings.fuelgauge.TopLevelBatteryPreferenceController"/>
<Preference
android:key="top_level_display"
android:title="@string/display_settings"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_display"
android:order="-80"
android:fragment="com.android.settings.DisplaySettings"
settings:controller="com.android.settings.display.TopLevelDisplayPreferenceController"/>
<Preference
android:key="top_level_sound"
android:title="@string/sound_settings"
android:summary="@string/sound_dashboard_summary"
android:icon="@drawable/ic_homepage_sound"
android:order="-70"
android:fragment="com.android.settings.notification.SoundSettings"/>
<Preference
android:key="top_level_storage"
android:title="@string/storage_settings"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_storage"
android:order="-60"
android:fragment="com.android.settings.deviceinfo.StorageSettings"
settings:controller="com.android.settings.deviceinfo.TopLevelStoragePreferenceController"/>
<Preference
android:key="top_level_privacy"
android:title="@string/privacy_dashboard_title"
android:summary="@string/privacy_dashboard_summary"
android:icon="@drawable/ic_homepage_privacy"
android:order="-55"
android:fragment="com.android.settings.privacy.PrivacyDashboardFragment"/>
<Preference
android:key="top_level_location"
android:title="@string/location_settings_title"
android:summary="@string/location_settings_loading_app_permission_stats"
android:icon="@drawable/ic_homepage_location"
android:order="-50"
android:fragment="com.android.settings.location.LocationSettings"
settings:controller="com.android.settings.location.TopLevelLocationPreferenceController"/>
<Preference
android:key="top_level_security"
android:title="@string/security_settings_title"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_security"
android:order="-40"
android:fragment="com.android.settings.security.SecuritySettings"
settings:controller="com.android.settings.security.TopLevelSecurityEntryPreferenceController"/>
<Preference
android:key="top_level_accounts"
android:title="@string/account_dashboard_title"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_accounts"
android:order="-30"
android:fragment="com.android.settings.accounts.AccountDashboardFragment"
settings:controller="com.android.settings.accounts.TopLevelAccountEntryPreferenceController"/>
<Preference
android:key="top_level_accessibility"
android:title="@string/accessibility_settings"
android:summary="@string/accessibility_settings_summary"
android:icon="@drawable/ic_homepage_accessibility"
android:order="-20"
android:fragment="com.android.settings.accessibility.AccessibilitySettings"
settings:controller="com.android.settings.accessibility.TopLevelAccessibilityPreferenceController"/>
<Preference
android:key="top_level_system"
android:title="@string/header_category_system"
android:summary="@string/system_dashboard_summary"
android:icon="@drawable/ic_homepage_system_dashboard"
android:order="10"
android:fragment="com.android.settings.system.SystemDashboardFragment"/>
<Preference
android:key="top_level_about_device"
android:title="@string/about_settings"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_about"
android:order="20"
android:fragment="com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment"
settings:controller="com.android.settings.deviceinfo.aboutphone.TopLevelAboutDevicePreferenceController"/>
<Preference
android:key="top_level_support"
android:summary="@string/support_summary"
android:title="@string/page_tab_title_support"
android:icon="@drawable/ic_homepage_support"
android:order="100"
settings:controller="com.android.settings.support.SupportPreferenceController"/>
</PreferenceScreen>
可以看到主要配置的是一些Preference菜单项如网络和互联网、已连接的设备、应用和通知、电池等等,Preference的配置含义:
key:唯一性ID;
title:标题;
summary:简介;
ico:图标;
order:加载显示优先级,order为负时,绝对值越高,界面显示越靠前;order为正时,值越高,显示越靠后;
fragment:点击此preference所跳转的fragment界面;
controller:控制管理类。
动态加载
refreshDashboardTiles
总结:
Settings的主Activity实质实现是在SettingsHomepageActivity.java内;
Settings的主界面设置item的显示是在fragment上,fragment为TopLevelSettings.java,加载显示的布局为top_level_settings.xml;
Settings主界面设置项item的加载显示主要分为两部分,一部分是xml定义的静态加载,xml为top_level_settings.xml;一部分是DashboardCategory来获取动态加载;
每个设置项item均为一个preference,通过xml定义加载时,必须要有一个controller,可以是在xml中定义"settings:controller"属性声明,名称必须与类的包名路径相同;也可直接在相关fragment中实现createPreferenceControllers()方法去调用构造相关controller。此二者存其一即可。
xml中配置preference时,必须定义”android:key“属性;
来源:https://blog.csdn.net/Brave__/article/details/113528216


猜你喜欢
- 前言目前Google已经发布了Android13的正式版,虽然国内的手机能用上Android13还有一段时间,不过开发者们可以通过模拟器来体
- 本文实例讲述了java取两个字符串的最大交集的实现方法,分享给大家供大家参考。具体实现方法如下:package com.itheima.ne
- 总之是用jdbc 的游标移动package com.sp.person.sql.util; import java.sql.Connecti
- java 进制转换实例详解十进制转成十六进制:  
- 因项目集成了Redis缓存部分数据,需要在程序启动时将数据加载到Redis中,即初始化数据到Redis。在SpringBoot项目下,即在容
- Java读取txt文件内容。可以作如下理解:首先获得一个文件句柄。File file = new File(); file即为文件句柄。两人
- 本文实例为大家分享了Android判断当前App状态的具体实现代码,供大家参考,具体内容如下第一种: /** *判断当前应用程序
- JDK7前处理之前的练习,我们一直把异常抛出,而实际开发中并不能这样处理,建议使用try...catch...finally 代码块,处理异
- springboot 启动项目打印接口列表环境springboot 2.3.2.RELEASE修改配置文件logging: le
- Lambda用到了JDK8自带的一个函数式接口Comparator<T>。准备一个Apple类public class Appl
- Java中使用也比较简单:1. 编译正则表达式的字面值得到对应的模式Pattern对象;2. 创建匹配给定输入与此模式的匹配器Matcher
- 解决问题:我在做移动端accessToken的使用遇到一个问题,就是普通类死活注入不进去spring bean,我和同事雷杰通过各种注解,x
- 继承JpaRepository,找不到findOne()方法问题:以前一直使用findOne(ID id);,这次用SpringBoot构建
- 一、Images1、概述Image 类为Bitmap(位图) 和 Metafile(矢量图) 的类提供功能的抽象基类。Image类不能直接创
- 本文实例为大家分享了C#实现订单管理程序的具体代码,供大家参考,具体内容如下订单管理的控制台程序,能够实现添加订单、删除订单、修改订单、查询
- 一、二叉搜索树插入元素/** * user:ypc; * date:2021-05-18; * time: 15:09; */
- 一、前言在java中,异常机制是非常有用的构成部分,异常信息对于查找错误来说是必不可少至关重要的信息,因此我们希望在发生错误的时候先看到捕捉
- 什么是RecyclerView关于RecyclerView,是一个主要用于展示和回收View的有一个控件,在官用了一句话来概括Recycle
- spring in action第三版读书笔记spring3.0引入了spring expression language(spel)语言,
- Android TimePicker 直接输入的问题解决方案TimePicker 提供了上下的按钮,点击按钮,相关操作都是正常的。但是如果直