软件编程
位置:首页>> 软件编程>> Android编程>> 分析Android 11.0Settings源码之主界面加载

分析Android 11.0Settings源码之主界面加载

作者:Brave__  发布时间:2021-05-25 23:15:02 

标签:Android,Settings,源码,主界面加载

本篇主要记录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

总结:

  1. Settings的主Activity实质实现是在SettingsHomepageActivity.java内;

  2. Settings的主界面设置item的显示是在fragment上,fragment为TopLevelSettings.java,加载显示的布局为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“属性;

来源:https://blog.csdn.net/Brave__/article/details/113528216

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com