Android 8 setting源码分析二

2019-12-20  本文已影响0人  梧叶已秋声

下面以点进SystemDashboardActivity为例,也就是setting下的system这一部分的设置去简单分析一下二级菜单。

//vendor\mediatek\proprietary\packages\xx\xx\src\com\android\settings\Settings.java
public class Settings extends SettingsActivity {
......
    public static class SystemDashboardActivity extends SettingsActivity {}
......

SystemDashboardActivity 是定义在Settings这个类(settings是启动activity)中的继承SettingsActivity的静态内部类。所以SystemDashboardActivity显示的部分依旧是定义在SettingsActivity中。所以这个结构是启动activity中定义了多个子activity?为什么要这样写?

 setContentView(mIsShowingDashboard ?
                R.layout.settings_main_dashboard : R.layout.settings_main_prefs);

通过打印isSubSettings和mIsShowingDashboard的值可以判断。
Log.d(TAG,"isSubSettings = " + String.valueOf(isSubSettings) + " , mIsShowingDashboard = " + String.valueOf(mIsShowingDashboard));

打开setting后界面加载的是settings_main_dashboard.xml,点进SystemDashboardActivity加载的是settings_main_prefs,然后再点进去的就是SubSettings。
settings_main_dashboard与settings_main_prefs的比较明显的差异如下所示。


settings_main_dashboard settings_main_prefs 1
2
//vendor\mediatek\proprietary\packages\xx\xx\src\com\android\settings\SettingsActivity.java
public class SettingsActivity extends SettingsDrawerActivity xxx{
........
    @VisibleForTesting
    void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) {
        if (!mIsShowingDashboard && initialFragmentName != null) {
            // UP will be shown only if it is a sub settings
            if (mIsShortcut) {
                mDisplayHomeAsUpEnabled = isSubSettings;
            } else if (isSubSettings) {
                mDisplayHomeAsUpEnabled = true;
            } else {
                mDisplayHomeAsUpEnabled = false;
            }
            setTitleFromIntent(intent);

            Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
            Log.d(TAG,"initialFragmentName = " + initialFragmentName + " , initialArguments = " + initialArguments + " , mInitialTitleResId = " +
                    mInitialTitleResId + " , mInitialTitle = " + mInitialTitle);
            switchToFragment(initialFragmentName, initialArguments, true, false,
                mInitialTitleResId, mInitialTitle, false);
        } else {
            // Show search icon as up affordance if we are displaying the main Dashboard
            mDisplayHomeAsUpEnabled = true;
            mInitialTitleResId = R.string.dashboard_title;

            switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false,
                mInitialTitleResId, mInitialTitle, false);
        }
    }
........
}

当点进setting后调用的是switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false, mInitialTitleResId, mInitialTitle, false);,然后再点进system调用的是 switchToFragment(initialFragmentName, initialArguments, true, false, mInitialTitleResId, mInitialTitle, false);

01-01 04:38:49.493 1167-1167/? D/SettingsActivity: initialFragmentName = com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment , initialArguments = Bundle[{show_drawer_menu=true, :settings:source_metrics=35, intent=Intent { flg=0x8000 cmp=com.android.settings/.Settings$ConnectedDeviceDashboardActivity (has extras) }}] , mInitialTitleResId = -1 , mInitialTitle = Connected devices
01-01 04:38:53.164 1167-1167/? D/SettingsActivity: initialFragmentName = com.android.settings.applications.AppAndNotificationDashboardFragment , initialArguments = Bundle[{show_drawer_menu=true, :settings:source_metrics=35, intent=Intent { flg=0x8000 cmp=com.android.settings/.Settings$AppAndNotificationDashboardActivity (has extras) }}] , mInitialTitleResId = -1 , mInitialTitle = Apps & notifications
01-01 04:45:53.917 1167-1167/? D/SettingsActivity: initialFragmentName = com.android.settings.system.SystemDashboardFragment , initialArguments = Bundle[{show_drawer_menu=true, :settings:source_metrics=35, intent=Intent { flg=0x8000 cmp=com.android.settings/.Settings$SystemDashboardActivity (has extras) }}] , mInitialTitleResId = -1 , mInitialTitle = System

AndroidManifest中的FRAGMENT_CLASS定义了SystemDashboardActivity所对应的fragment,即SystemDashboardFragment。

......
        <activity android:name=".Settings$AppAndNotificationDashboardActivity"
                  android:label="@string/app_and_notification_dashboard_title"
                  android:icon="@drawable/ic_apps">
            <intent-filter android:priority="9">
                <action android:name="com.android.settings.action.SETTINGS"/>
            </intent-filter>
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.applications.AppAndNotificationDashboardFragment"/>
            <meta-data android:name="com.android.settings.category"
                       android:value="com.android.settings.category.ia.homepage"/>
            <meta-data android:name="com.android.settings.summary"
                       android:resource="@string/app_and_notification_dashboard_summary"/>
        </activity>
......
        <activity android:name=".Settings$SystemDashboardActivity"
                  android:label="@string/header_category_system"
                  android:icon="@drawable/ic_settings_about">
            <intent-filter android:priority="-1">
                <action android:name="com.android.settings.action.SETTINGS"/>
            </intent-filter>
            <meta-data android:name="com.android.settings.category"
                       android:value="com.android.settings.category.ia.homepage"/>
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.system.SystemDashboardFragment"/>
            <meta-data android:name="com.android.settings.summary"
                       android:resource="@string/system_dashboard_summary"/>
        </activity>
......

所以,一开始点进setting用的是DashboardSummary这个fragment,而再点进 System 或 Apps & notifications 就分别对应显示SystemDashboardFragment或AppAndNotificationDashboardFragment。这些都是activity所对应的需要显示的fragment都定义在AndroidManifest中。然后在SettingsActivity中调用switchToFragment(initialFragmentName, initialArguments, true, false, mInitialTitleResId, mInitialTitle, false);去显示fragment。
下面来看DashboardFragment。DashboardFragment的使用涉及到PreferenceFragment这个知识点。这个fragment的的使用首先,要在xml文件夹下定义一个以PreferenceScreen为根节点的xx.xml文件,然后新建一个fragment继承自PreferenceFragment,然后在fragment的onCreatePreferences函数中加载xx.xml文件。

//vendor\mediatek\proprietary\packages\xx\xx\src\com\android\settings\dashboard\DashboardFragment.java
public class SystemDashboardFragment extends DashboardFragment {
......
    @Override
    protected int getPreferenceScreenResId() {
      return R.xml.system_dashboard_fragment;
    }
......
}
public class AppAndNotificationDashboardFragment extends DashboardFragment {
......
    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.app_and_notification;
    }
......
}
/**
 * Base fragment for dashboard style UI containing a list of static and dynamic setting items.
 */
public abstract class DashboardFragment extends SettingsPreferenceFragment{
......
    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        super.onCreatePreferences(savedInstanceState, rootKey);
        refreshAllPreferences(getLogTag());
    }
......
    /**
     * Get the res id for static preference xml for this fragment.
     */
    protected abstract int getPreferenceScreenResId();
......
    /**
     * Refresh all preference items, including both static prefs from xml, and dynamic items from
     * DashboardCategory.
     */
    private void refreshAllPreferences(final String TAG) {
        // First remove old preferences.
        if (getPreferenceScreen() != null) {
            // Intentionally do not cache PreferenceScreen because it will be recreated later.
            getPreferenceScreen().removeAll();
        }

        // Add resource based tiles.
        displayResourceTiles();
        mProgressiveDisclosureMixin.collapse(getPreferenceScreen());

        refreshDashboardTiles(TAG);
    }

......
       /**
     * Displays resource based tiles.
     */
    private void displayResourceTiles() {
        final int resId = getPreferenceScreenResId();
        if (resId <= 0) {
            return;
        }
        addPreferencesFromResource(resId);
        final PreferenceScreen screen = getPreferenceScreen();
        Collection<AbstractPreferenceController> controllers = mPreferenceControllers.values();
        for (AbstractPreferenceController controller : controllers) {
            controller.displayPreference(screen);
        }
    }
}
......

DashboardFragment是AppAndNotificationDashboardFragment,SystemDashboardFragment等fragment的基类,在DashboardFragment中getPreferenceScreenResId是一个抽象函数,因此AppAndNotificationDashboardFragment和SystemDashboardFragment等会重写此函数,从而加载不同的xml文件。不得不说,虽然麻烦,这个复用度很高。

system_dashboard_fragment.xml如下所示。

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:title="@string/header_category_system">

    <Preference
        android:key="gesture_settings"
        android:title="@string/gesture_preference_title"
        android:icon="@drawable/ic_settings_gestures"
        android:order="-250"
        android:fragment="com.android.settings.gestures.GestureSettings" />

    <!-- Backup -->
    <Preference
        android:key="backup_settings"
        android:title="@string/privacy_settings_title"
        android:summary="@string/summary_placeholder"
        android:icon="@drawable/ic_settings_backup"
        android:order="-60">
        <intent android:action="android.settings.BACKUP_AND_RESET_SETTINGS" />
    </Preference>

    <!-- System updates -->
    <Preference
        android:key="system_update_settings"
        android:title="@string/system_update_settings_list_item_title"
        android:summary="@string/summary_placeholder"
        android:icon="@drawable/ic_system_update"
        android:order="-30">
        <intent android:action="android.settings.SYSTEM_UPDATE_SETTINGS" />
    </Preference>

    <Preference
        android:key="additional_system_update_settings"
        android:title="@string/additional_system_update_settings_list_item_title"
        android:order="-31">
        <intent android:action="android.intent.action.MAIN"
                android:targetPackage="@string/additional_system_update"
                android:targetClass="@string/additional_system_update_menu" />
    </Preference>

    <Preference
        android:key="reset_dashboard"
        android:title="@string/reset_dashboard_title"
        android:summary="@string/reset_dashboard_summary"
        android:icon="@drawable/ic_restore"
        android:order="-20"
        android:fragment="com.android.settings.system.ResetDashboardFragment" />

</PreferenceScreen>

这里的Preference有2种方式可以跳转新的界面,一种是Preference中定义了android:fragment属性,当点击Preference的时候,会跳转android:fragment所指向的fragment,另一种是添加intent。

https://developer.android.com/guide/topics/ui/settings/customize-your-settings?hl=zh-cn#%E8%AE%BE%E7%BD%AE_intent
设置 Intent
您可以在 Preference 上设置 Intent,以在每次点按 Preference 时,启用新的 Fragment、Activity、或单独的应用。 这等同于使用给定 Intent 的 Context.startActivity()。
您可以使用嵌套 <intent> 标签在 XML 中设置 Intent。 下方示例定义了启动 Activity 的 Intent:

<Preference
        app:key=”activity”
        app:title="Launch activity">
    <intent
            android:targetPackage="com.example"
            android:targetClass="com.example.ExampleActivity"/>
</Preference>

或者,您也可以在 Preference 上直接使用 setIntent(),如下所示:

Intent intent = new Intent(getContext(), ExampleActivity.class);
activityPreference.setIntent(intent);

Sets an Intent to be used for Context#startActivity(Intent) when this Preference is clicked.

OnPreferenceClickListener
您可以在 Preference 上设置 OnPreferenceClickListener,当点按 Preference 时,此操作会为 onPreferenceClick() 添加回调。 例如,如果您有更复杂的逻辑可以用于处理跳转,则可以使用侦听器跳转至另一个 Fragment 或 Activity。

要设置 OnPreferenceClickListener,请使用与下方所示内容类似的代码:

onClickPreference.setOnPreferenceClickListener(preference -> {
    // do something
    return true;
});

当Preference被点击时,最终会调用performClick函数,因此,添加intent的情况下会调用 context.startActivity(mIntent)从而实现跳转。而fragment的跳转应该是通过PreferenceManager去实现的。

//frameworks\base\core\java\android\preference\Preference.java
    /**
     * Called when a click should be performed.
     *
     * @param preferenceScreen A {@link PreferenceScreen} whose hierarchy click
     *            listener should be called in the proper order (between other
     *            processing). May be {@code null}.
     * @hide
     */
    public void performClick(PreferenceScreen preferenceScreen) {

        if (!isEnabled()) {
            return;
        }

        onClick();

        if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) {
            return;
        }

        PreferenceManager preferenceManager = getPreferenceManager();
        if (preferenceManager != null) {
            PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
                    .getOnPreferenceTreeClickListener();
            if (preferenceScreen != null && listener != null
                    && listener.onPreferenceTreeClick(preferenceScreen, this)) {
                return;
            }
        }

        if (mIntent != null) {
            Context context = getContext();
            context.startActivity(mIntent);
        }
    }

Preference更多属性可以看这里
https://developer.android.com/reference/android/preference/Preference

除了在xml中定义的Preference之外,还有AndroidManifest.xml中定义的value为com.android.settings.category.ia.system的类也会显示在System下(我是根据显示结果来下结论的,具体初始化的地方在代码中未找到)。

        <activity android:name=".Settings$LanguageAndInputSettingsActivity"
            android:label="@string/language_settings"
            android:icon="@drawable/ic_settings_language"
            android:taskAffinity="com.android.settings"
            android:parentActivityName="Settings$SystemDashboardActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.VOICE_LAUNCH" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter android:priority="260">
                <action android:name="com.android.settings.action.SETTINGS"/>
            </intent-filter>
            <meta-data android:name="com.android.settings.category"
                       android:value="com.android.settings.category.ia.system"/>
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.language.LanguageAndInputSettings"/>
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                       android:value="true"/>
        </activity>

关键词:SettingsActivity,DashboardSummary,DashboardFragment。
首先,点进setting是SettingsActivity显示DashboardSummary(Settings),然后再点进去下一级是显示DashboardFragment(.Settings$xxxActivity)。

Settings以及.Settings$xxxActivity部分的显示基本就到此为止了,下一篇分析SubSettings部分的显示。

参考链接:
Preferencescreen中利用intent跳转activity

Android进阶——Preference详解之Preference系的基本应用和管理(二)

Android O Settings源码流程分析(数据加载之二级菜单)

上一篇下一篇

猜你喜欢

热点阅读