Android 8 setting源码分析二
下面以点进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