最近一直在做Settings方面的东西,简单整理一下:

一、Settings
1、初始 AndroidManifest.xml
/packages/apps/Settings/AndroidManifest.xml

<activity android:name=".homepage.SettingsHomepageActivity"  //Settings 的主界面
                  android:label="@string/settings_label_launcher"
                  android:theme="@style/Theme.Settings.Home"
                  android:launchMode="singleTask">  //启动模式为SingleTask
            <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>

		//Alias标签 通过 <activity-alias> 标签为每个 Activity 注册一个“别名”, 通过这个别名就能启动对应的目标 Activity,它最重要的属性是:android:enabled 属性,布尔类型,是否开启别名设置,默认值为 true
		
        <!-- Alias for launcher activity only, as this belongs to each profile. -->
        <activity-alias android:name="Settings"  
                android:label="@string/settings_label_launcher"
                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>

从上面可以看到 activity-alias 只是一个别名,真正的指向的是 targetActivity,所以实际上主Activity就是SettingsHomepageActivity。

2、接着看这个别名 Settings.java

public class Settings extends SettingsActivity {

    /*
    * Settings subclasses for launching independently. //作用设置子类让其独立启动
    */
    public static class AssistGestureSettingsActivity extends SettingsActivity { /* empty */}
    public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
    public static class CreateShortcutActivity extends SettingsActivity { /* empty */ }
    public static class FaceSettingsActivity extends SettingsActivity { /* empty */ }
    public static class FingerprintSettingsActivity extends SettingsActivity { /* empty */ }
    public static class SimSettingsActivity extends SettingsActivity { /* empty */ }
    public static class TetherSettingsActivity extends SettingsActivity { /* empty */ }
    public static class WifiTetherSettingsActivity extends SettingsActivity { /* empty */ }
    public static class VpnSettingsActivity extends SettingsActivity { /* empty */ }
    public static class DataSaverSummaryActivity extends SettingsActivity{ /* empty */ }
    public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }
    public static class PrivateVolumeForgetActivity extends SettingsActivity { /* empty */ }
    public static class PrivateVolumeSettingsActivity extends SettingsActivity { /* empty */ }
    public static class PublicVolumeSettingsActivity extends SettingsActivity { /* empty */ }
    public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }
    ....
}

3、看一下主界面 SettingsHomepageActivity.java
(/packages/apps/Settings/src/com/android/settings/homepage/SettingsHomepageActivity.java)

public class SettingsHomepageActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

		 //加载 settings_homepage_container.xml文件,这个文件就是 settings 主界面的布局
        (1)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();

		 //设置 ToorBar,显示搜索框的内容
        (2)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);
        final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(this, avatarView);
        getLifecycle().addObserver(avatarViewMixin);
        
		 //判断设备的内存,是否显示 ContextualCardsFragment()
        (3)if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
            // Only allow contextual feature on high ram devices.
            showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content);
        }
        
         //通过 showFragment() 显示一级菜单
        (4)showFragment(new TopLevelSettings(), R.id.main_content);
        ((FrameLayout) findViewById(R.id.main_content))
                .getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
    }

	  //通过 showFragment() 动态添加相应的 Fragment 并显示出来
	 private void showFragment(Fragment fragment, int id) {
        final FragmentManager fragmentManager = getSupportFragmentManager();
        final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        final Fragment showFragment = fragmentManager.findFragmentById(id);

        if (showFragment == null) {
            fragmentTransaction.add(id, fragment);
        } else {
            fragmentTransaction.show(showFragment);
        }
        fragmentTransaction.commit();
    }
}

(1)通过setContentView()将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"
            android:descendantFocusability="blocksDescendants">

            <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">
        <include layout="@layout/search_bar"/>
    </com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

这个布局就是settings主界面的布局,通过一个NestedScrollView和一个AppBarLayout。AppBarLayout是用来显示搜索框的,NestedScrollView是用来显示主菜单的列表容器的。

4、TopLevelSettings.java
(/packages/apps/Settings/src/com/android/settings/homepage/TopLevelSettings.java)

public class TopLevelSettings extends DashboardFragment implements
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {

    private static final String TAG = "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);
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.top_level_settings;
    }
  }

TopLevelSettings继承DashboardFragment ,getPreferenceScreenResId()加载指定的xml,即加载top_level_settings.xml文件,top_level_settings.xml文件就是设置的一级界面,里面包含诸多的item(但不包括动态加载的item)。

接着看一下 top_level_settings.xml 一级菜单
/packages/apps/Settings/res/xml/top_level_settings.xml

<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_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_accessibility"  //无障碍
        android:title="@string/accessibility_settings" //标题
        android:summary="@string/accessibility_settings_summary" //summary 内容
        android:icon="@drawable/ic_homepage_accessibility" //图标
        android:order="-20" //界面的排序
        android:fragment="com.android.settings.accessibility.AccessibilitySettings"
        settings:controller="com.android.settings.accessibility.TopLevelAccessibilityPreferenceController"/>
        /**
        通过对应的fragment在AndroidManifest.xml里面找到对应的Activity
        controller:
        主要是 getSummary() 来更新summary,但要在对应的 controller 实现  SummaryLoader.SummaryConsumer 接口;
        getAvailabilityStatus() 来判断当前item是否显示。
        */

</PreferenceScreen>

对应到页面,就是如下:

android 中settings android中settings类_java

接着看一下DashboardFragment.java 文件,(/packages/apps/Settings/src/com/android/settings/dashboard/DashboardFragment.java)

public abstract class DashboardFragment extends SettingsPreferenceFragment
        implements SettingsBaseActivity.CategoryListener, Indexable,
        SummaryLoader.SummaryConsumer, PreferenceGroup.OnExpandButtonClickListener,
        BasePreferenceController.UiBlockListener {
    private static final String TAG = "DashboardFragment";

    private final Map<Class, List<AbstractPreferenceController>> mPreferenceControllers =
            new ArrayMap<>();
    private final Set<String> mDashboardTilePrefKeys = new ArraySet<>();

    private DashboardFeatureProvider mDashboardFeatureProvider;
    private DashboardTilePlaceholderPreferenceController mPlaceholderPreferenceController;
    private boolean mListeningToCategoryChange;
    private SummaryLoader mSummaryLoader;
    private List<String> mSuppressInjectedTileKeys;
    @VisibleForTesting
    UiBlockerController mBlockerController;

    @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);
        final List<AbstractPreferenceController> controllers = new ArrayList<>();
        // Load preference controllers from code
        //从代码中加载preference controllers
        final List<AbstractPreferenceController> controllersFromCode =
                createPreferenceControllers(context);
        // Load preference controllers from xml definition
        //从xml中加载preference controllers
        final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
                .getPreferenceControllersFromXml(context, getPreferenceScreenResId());
        // Filter xml-based controllers in case a similar controller is created from code already.
        //从代码中加载preference controllers和从xml中加载preference controllers,然后进行重排
        final List<BasePreferenceController> uniqueControllerFromXml =
                PreferenceControllerListHelper.filterControllers(
                        controllersFromXml, controllersFromCode);

DashboardFragment 是一个抽象类,同时也继承SettingsPreferenceFragment,DashboardFragment 上述代码这里具体做了三件事情:1、 Load preference controllers from code;从代码中加载preference controllers。2、Load preference controllers from xml definition;从xml中加载preference controllers。3、 Filter xml-based controllers in case a similar controller is created from code already.从代码中加载preference controllers和从xml中加载preference controllers,然后进行重排。

5、SettingsActivity.java(/packages/apps/Settings/src/com/android/settings/SettingsActivity.java)

public class SettingsActivity extends SettingsBaseActivity
        implements PreferenceManager.OnPreferenceTreeClickListener,
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
        ButtonBarHandler, FragmentManager.OnBackStackChangedListener {
        ....
          @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        Log.d(LOG_TAG, "Starting onCreate");
        long startTime = System.currentTimeMillis();
	    ........

        setContentView(R.layout.settings_main_prefs);
        }
}

SettingsActivity.java 通过 setContentView() 将 settings_main_prefs.xml 布局引入,这个布局就是二级菜单界面。

接着看一下settings_main_prefs.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_height="match_parent"
              android:layout_width="match_parent">

    <com.android.settings.widget.SwitchBar
        android:id="@+id/switch_bar"
        android:layout_height="?android:attr/actionBarSize"
        android:layout_width="match_parent"
        android:theme="?attr/switchBarTheme"/>

    <FrameLayout
        android:id="@+id/main_content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <RelativeLayout android:id="@+id/button_bar"
                    android:layout_height="wrap_content"
                    android:layout_width="match_parent"
                    android:layout_weight="0"
                    android:visibility="gone">

        <Button android:id="@+id/back_button"
                android:layout_width="150dip"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:layout_alignParentStart="true"
                android:text="@*android:string/back_button_label"/>

        <LinearLayout
                android:orientation="horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true">

            <Button android:id="@+id/skip_button"
                    android:layout_width="150dip"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dip"
                    android:text="@*android:string/skip_button_label"
                    android:visibility="gone"/>

            <Button android:id="@+id/next_button"
                    android:layout_width="150dip"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dip"
                    android:text="@*android:string/next_button_label"/>

        </LinearLayout>

    </RelativeLayout>

</LinearLayout>

到这里流程基本就差不多了,后面的后续完成

二、SettingsProvider

个人改动的比较少,改动的话基本都是一些默认配置