本篇主要记录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:
可以看到主要配置的是一些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“属性;
以上就是分析Android 11.0Settings源码之主界面加载的详细内容,更多关于Android 11.0Settings源码的资料请关注w3xue其它相关文章!