Android 系统应用之Settings
Setting作为安卓一个比较重要的系统级应用,为用户提供一些系统项的设置。原生android系统的源码路径:/packages/apps/Settings。但MTK厂商的源码包中对该应用进行了重构其源码路径:/vendor/mediatek/proprietary/packages/apps/MtkSettings。一、Setting1、Setting函数入口android应用程序的入口比较简单
Setting作为安卓一个比较重要的系统级应用,为用户提供一些系统项的设置。原生android系统的源码路径:/packages/apps/Settings。但MTK厂商的源码包中对该应用进行了重构其源码路径:/vendor/mediatek/proprietary/packages/apps/MtkSettings。
一、Setting
1、入口Activity
android应用程序的入口比较简单,可以直接查看AndroidManifest.xml,里面有配置应用的包名、版本、权限、四大组件等。Setting配置文件代码如下:
<!--packages/apps/Settings/AndroidManifest.xml-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
package="com.android.settings"
coreApp="true"
android:sharedUserId="android.uid.system"> <!--标识系统应用-->
<!--包名-->
<original-package android:name="com.android.settings" />
<!--应用配置-->
<application android:label="@string/settings_label"
android:icon="@drawable/ic_launcher_settings"
android:theme="@style/Theme.Settings"
android:hardwareAccelerated="true"
android:requiredForAllUsers="true"
android:supportsRtl="true"
android:backupAgent="com.android.settings.backup.SettingsBackupHelper"
android:usesCleartextTraffic="true"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
android:appComponentFactory="androidx.core.app.CoreComponentFactory">
<uses-library android:name="org.apache.http.legacy" />
<!-- Settings -->
<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的别名,目标actvity是SettingsHomepageActivity-->
<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>
<receiver android:name=".SettingsInitialize">
<intent-filter>
<action android:name="android.intent.action.USER_INITIALIZE"/>
<action android:name="android.intent.action.PRE_BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<activity android:name=".SubSettings"/>
<!--activity配置 省略......-->
</application>
</manifest>
上面的配置文件第一个被android.intent.action.MAIN修饰的是一个activity-alias标签,该标签意思是一个activity的别名。即它是一个已经存在的activity的别名(这个已经存在的activity被属性targetActivity修饰),因此我们可以认为Settings程序的主界面是SettingsHomepageActivity.java。值得注意的是该标签的name属性并不会指定某个java文件,它只是一个命名标志而已,与他绑定相关的java文件是targetActivity对应的内容,因此这里只是把主界面命名为Settings,但是与源代码中的com.android.settings.Settings.java毫无关系。详情参考《activity-alias详解及应用》。
为了验证上面的理论如上图我分别在SettingsHomepageActivity.java和Settings.java的onCreate加了日志,在启动Settings的时候看看打印的日志是什么?
2、SettingsHomepageActivity
//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
setContentView(R.layout.settings_homepage_container);
// 获取布局文件中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);
//获取布局文件中search_action_bar:顶部搜索控件
final Toolbar toolbar = findViewById(R.id.search_action_bar);
FeatureFactory.getFactory(this).getSearchFeatureProvider().initSearchToolbar(this, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);
//设置contextual_cards_content对应的fragment,不是低内存手机显示卡片布局视图
if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content);
}
//设置main_content对应fragment为TopLevelSettings,即所有二级菜单选项
showFragment(new TopLevelSettings(), R.id.main_content);
((FrameLayout) findViewById(R.id.main_content))
.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
}
}
<!--packages/apps/Settings/res/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>
如上Activity和对应的布局文件代码,很容易理解,整个布局主要分两大块:
- 首先顶部的搜索栏通过包含search_bar.xml文件(实现了Settings的搜索设置功能),通过工厂方式来创建并初始化SearchFeatureProvider,可通过它来搜索Settings所有的子菜单
- 最后内容主体部分使用了NestedScrollView滑动控件,它包含了两个部分:顶部的卡片内容和下面主体二级菜单选项。其中二级菜单选项视图被设置成了TopLevelSettings
2.1、SearchFeatureProvider搜索子菜单
2.2、TopLevelSettings二级菜单布局
//packages/apps/Settings/src/com/android/settings/homepage/TopLevelSettings.java
@SearchIndexable(forTarget = MOBILE)
public class TopLevelSettings extends DashboardFragment implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
public TopLevelSettings() {
final Bundle args = new Bundle();
args.putBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, false); //是否禁用搜索图标
setArguments(args);
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.top_level_settings; //对于视图布局文件
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
use(SupportPreferenceController.class).setActivity(getActivity());
}
@Override
public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
new SubSettingLauncher(getActivity())
.setDestination(pref.getFragment())
.setArguments(pref.getExtras())
.setSourceMetricsCategory(caller instanceof Instrumentable ? ((Instrumentable) caller).getMetricsCategory() : Instrumentable.METRICS_CATEGORY_UNKNOWN)
.setTitleRes(-1)
.launch();
return true;
}
@Override
protected boolean shouldForceRoundedIcon() {
return getContext().getResources()
.getBoolean(R.bool.config_force_rounded_icon_TopLevelSettings);
}
}
从上面的代码可以知道TopLevelSettings是一个fragment,大部分功能在DashboardFragment父类具体实现,这里只提供了几个接口,从名字上可以看出getPreferenceScreenResId对应于视图布局文件,top_level_settings.xml如下:
2.3、Preference偏好设置
如上小节,我们发现布局文件中在PreferenceScreen里面包含了一批Preference。这个是什么东西呢?其实Preference是android原生为了持久化数据的一种"控件",注意它并不是真正意义上的View。具体用法可以参考《Android之PreferenceFragment详解》
- Preference家族
Preference源码路径如下图,其中比较重要的有前面已经见过的PreferenceScreen和Preference。
- Preference是"控件"?
Preference并不是控件,它没有继承View,但是为什么我们的布局文件中可以使用他呢?其实只有继承了PreferenceFragment或者PreferenceActivity的才能使用上面以PreferenceScreen标签开头的xml文件,因为他们内部作了一系列解析,同时Preference有方法返回一个View对象,如下代码:
//android/frameworks/base/core/java/android/preference/Preference.java
@Deprecated
public class Preference implements Comparable<Preference> {
private boolean mShouldDisableView = true;
@UnsupportedAppUsage
private int mLayoutResId = com.android.internal.R.layout.preference;
@UnsupportedAppUsage
private int mWidgetLayoutResId;
public void setLayoutResource(@LayoutRes int layoutResId) {
if (layoutResId != mLayoutResId) mRecycleEnabled = false;
mLayoutResId = layoutResId;
}
@LayoutRes
public int getLayoutResource() {
return mLayoutResId;
}
//返回一个视图控件View
public View getView(View convertView, ViewGroup parent) {
//如果第一次就创建视图View
if (convertView == null) convertView = onCreateView(parent);
//给视图View填充数据和更新ui
onBindView(convertView);
return convertView;
}
//创建视图控件View,实际上还是inflate对应的资源ID文件
@CallSuper
protected View onCreateView(ViewGroup parent) {
final LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View layout = layoutInflater.inflate(mLayoutResId, parent, false);
final ViewGroup widgetFrame = (ViewGroup) layout.findViewById(com.android.internal.R.id.widget_frame);
if (widgetFrame != null) {
if (mWidgetLayoutResId != 0) layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
else widgetFrame.setVisibility(View.GONE);
}
return layout;
}
//主要是给上面创建的视图View设置数据
@CallSuper
protected void onBindView(View view) {
final TextView titleView = (TextView) view.findViewById(com.android.internal.R.id.title);
if (titleView != null) {
final CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
titleView.setText(title);
titleView.setVisibility(View.VISIBLE);
if (mHasSingleLineTitleAttr) titleView.setSingleLine(mSingleLineTitle);
} else {
titleView.setVisibility(View.GONE);
}
}
final TextView summaryView = (TextView) view.findViewById( com.android.internal.R.id.summary);
if (summaryView != null) {
final CharSequence summary = getSummary();
if (!TextUtils.isEmpty(summary)) {
summaryView.setText(summary);
summaryView.setVisibility(View.VISIBLE);
} else {
summaryView.setVisibility(View.GONE);
}
}
final ImageView imageView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
if (imageView != null) {
if (mIconResId != 0 || mIcon != null) {
if (mIcon == null) mIcon = getContext().getDrawable(mIconResId);
if (mIcon != null) imageView.setImageDrawable(mIcon);
}
if (mIcon != null) imageView.setVisibility(View.VISIBLE);
else imageView.setVisibility(mIconSpaceReserved ? View.INVISIBLE : View.GONE);
}
final View imageFrame = view.findViewById(com.android.internal.R.id.icon_frame);
if (imageFrame != null) {
if (mIcon != null) imageFrame.setVisibility(View.VISIBLE);
else imageFrame.setVisibility(mIconSpaceReserved ? View.INVISIBLE : View.GONE);
}
if (mShouldDisableView) setEnabledStateOnViews(view, isEnabled());
}
//界面更改条目栏
public void setTitle(CharSequence title) {
if (title == null && mTitle != null || title != null && !title.equals(mTitle)) {
mTitleRes = 0;
mTitle = title;
notifyChanged();
}
}
//界面更改图标
public void setIcon(Drawable icon) {
if ((icon == null && mIcon != null) || (icon != null && mIcon != icon)) {
mIcon = icon;
notifyChanged();
}
}
//界面有改变回调监听器
protected void notifyChanged() {
if (mListener != null) mListener.onPreferenceChange(this);
}
}
- Preference如何持久化?
Settings为什么大量使用了Preference,而没有使用到我们常见的View和TextView呢,因为这里逻辑功能上主要是为了给系统进行一些设置,所有就涉及到了设置参数持久化(即断电后还继续生效),如下代码它内部已经通过PreferenceManager来进行对数据的持久化,实际上还是使用了四大存储方式之一。
//android/frameworks/base/core/java/android/preference/Preference.java
@Deprecated
public class Preference implements Comparable<Preference> {
@Nullable
private PreferenceManager mPreferenceManager;
public SharedPreferences getSharedPreferences() {
if (mPreferenceManager == null || getPreferenceDataStore() != null) return null;
return mPreferenceManager.getSharedPreferences();
}
public SharedPreferences.Editor getEditor() {
if (mPreferenceManager == null || getPreferenceDataStore() != null) return null;
return mPreferenceManager.getEditor();
}
public boolean shouldCommit() {
if (mPreferenceManager == null) return false;
return mPreferenceManager.shouldCommit();
}
public PreferenceManager getPreferenceManager() {
return mPreferenceManager;
}
}
- Preference唯一标识
- Preference如何自动跳转android:fragment?
在阅读Android系统应用Settings源码的时候,我们会发现它的布局文件有一大批Preference控件,这个是什么东西呢?其实Preference是android原生为了持久化数据的一种"控件",注意它并不是真正意义上的View。
二、深度剖析Preference
Preference家族代码被定义在frameworks/base/core/java/android/preference,改目录下的代码用来实现偏好设置相关的界面。即Preference是android原生为了持久化数据的一种"控件"。要使用Preference就必须建立在PreferenceActivity或PreferenceFragment的载体上面。具体用法可以参考《Android之PreferenceFragment详解》
- Preference家族
Preference被定义在frameworks/base/core下面,其中Preference作为视图控件在整个家族中的地位类似于View,PreferenceGroup就类似于ViewGroup,XXXPreference则继承于Preference并扩展了不同功能样式。除此之外Preference并不能直接使用在Activity和Fragment上面,因此多了PreferenceActivity和PreferenceFragment他们分别继承Activity和Fragment并在此基础上实现了数据持久化、控件解析等功能。除此之外还有两个比较重要的类: PreferenceScreen和PreferenceGroupAdapter
- Preference是"控件"?
Preference并不是控件,它没有继承View,但是为什么我们的布局文件中可以使用他呢?其实只有继承了PreferenceFragment或者PreferenceActivity的才能使用上面以PreferenceScreen标签开头的xml文件,因为他们内部作了一系列解析,同时Preference有方法返回一个View对象,如下代码:
//android/frameworks/base/core/java/android/preference/Preference.java
@Deprecated
public class Preference implements Comparable<Preference> {
private boolean mShouldDisableView = true;
@UnsupportedAppUsage
private int mLayoutResId = com.android.internal.R.layout.preference;
@UnsupportedAppUsage
private int mWidgetLayoutResId;
public void setLayoutResource(@LayoutRes int layoutResId) {
if (layoutResId != mLayoutResId) mRecycleEnabled = false;
mLayoutResId = layoutResId;
}
@LayoutRes
public int getLayoutResource() {
return mLayoutResId;
}
//返回一个视图控件View
public View getView(View convertView, ViewGroup parent) {
//如果第一次就创建视图View
if (convertView == null) convertView = onCreateView(parent);
//给视图View填充数据和更新ui
onBindView(convertView);
return convertView;
}
//创建视图控件View,实际上还是inflate对应的资源ID文件
@CallSuper
protected View onCreateView(ViewGroup parent) {
final LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View layout = layoutInflater.inflate(mLayoutResId, parent, false);
final ViewGroup widgetFrame = (ViewGroup) layout.findViewById(com.android.internal.R.id.widget_frame);
if (widgetFrame != null) {
if (mWidgetLayoutResId != 0) layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
else widgetFrame.setVisibility(View.GONE);
}
return layout;
}
//主要是给上面创建的视图View设置数据
@CallSuper
protected void onBindView(View view) {
final TextView titleView = (TextView) view.findViewById(com.android.internal.R.id.title);
if (titleView != null) {
final CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
titleView.setText(title);
titleView.setVisibility(View.VISIBLE);
if (mHasSingleLineTitleAttr) titleView.setSingleLine(mSingleLineTitle);
} else {
titleView.setVisibility(View.GONE);
}
}
final TextView summaryView = (TextView) view.findViewById( com.android.internal.R.id.summary);
if (summaryView != null) {
final CharSequence summary = getSummary();
if (!TextUtils.isEmpty(summary)) {
summaryView.setText(summary);
summaryView.setVisibility(View.VISIBLE);
} else {
summaryView.setVisibility(View.GONE);
}
}
final ImageView imageView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
if (imageView != null) {
if (mIconResId != 0 || mIcon != null) {
if (mIcon == null) mIcon = getContext().getDrawable(mIconResId);
if (mIcon != null) imageView.setImageDrawable(mIcon);
}
if (mIcon != null) imageView.setVisibility(View.VISIBLE);
else imageView.setVisibility(mIconSpaceReserved ? View.INVISIBLE : View.GONE);
}
final View imageFrame = view.findViewById(com.android.internal.R.id.icon_frame);
if (imageFrame != null) {
if (mIcon != null) imageFrame.setVisibility(View.VISIBLE);
else imageFrame.setVisibility(mIconSpaceReserved ? View.INVISIBLE : View.GONE);
}
if (mShouldDisableView) setEnabledStateOnViews(view, isEnabled());
}
//界面更改条目栏
public void setTitle(CharSequence title) {
if (title == null && mTitle != null || title != null && !title.equals(mTitle)) {
mTitleRes = 0;
mTitle = title;
notifyChanged();
}
}
//界面更改图标
public void setIcon(Drawable icon) {
if ((icon == null && mIcon != null) || (icon != null && mIcon != icon)) {
mIcon = icon;
notifyChanged();
}
}
//界面有改变回调监听器
protected void notifyChanged() {
if (mListener != null) mListener.onPreferenceChange(this);
}
}
- Preference如何持久化?
Settings为什么大量使用了Preference,而没有使用到我们常见的View和TextView呢,因为这里逻辑功能上主要是为了给系统进行一些设置,所有就涉及到了设置参数持久化(即断电后还继续生效),如下代码它内部已经通过PreferenceManager来进行对数据的持久化,实际上还是使用了四大存储方式之一
//android/frameworks/base/core/java/android/preference/Preference.java
@Deprecated
public class Preference implements Comparable<Preference> {
@Nullable
private PreferenceManager mPreferenceManager;
public SharedPreferences getSharedPreferences() {
if (mPreferenceManager == null || getPreferenceDataStore() != null) return null;
return mPreferenceManager.getSharedPreferences();
}
public SharedPreferences.Editor getEditor() {
if (mPreferenceManager == null || getPreferenceDataStore() != null) return null;
return mPreferenceManager.getEditor();
}
public boolean shouldCommit() {
if (mPreferenceManager == null) return false;
return mPreferenceManager.shouldCommit();
}
public PreferenceManager getPreferenceManager() {
return mPreferenceManager;
}
}
- Preference唯一标识
- Preference跳转到Fragment
上面初步介绍了Preference的基本用法和一些特点,想使用Preference必须建立在PreferenceActivity或者PreferenceFragment的基础之上,除此之外布局文件最外层必须使用一个PreferenceScreen嵌套所有的Preference。
1、剖析PreferenceActivity
1.1、布局文件加载
PreferenceActivity的布局跟主流布局类似,即通常由三部分组成:标题栏、内容、最底层的bar。这里我们关注标题栏和内容,因为PreferenceActivity在设计的时候就考虑到了公用同一个activity进行页面跳转,即替换内容区域的fragment。
//frameworks/base/core/java/android/preference/PreferenceActivity.java
/*继承ListActivity,即其布局文件中必须有一个ListView控件且id必须为list*/
@Deprecated public abstract class PreferenceActivity extends ListActivity implements
/*Preference树结构下所有视图控件点击回调事件*/
PreferenceManager.OnPreferenceTreeClickListener,
/*Preference被点击后如果有设置fragment就直接替换fragment回调*/
PreferenceFragment.OnPreferenceStartFragmentCallback {
//通过android:fragment属性进行跳转不同fragment页面,可能共用同一个PreferenceActivity,但是标题栏不一样,mHeaders 存储了所有的标题
private final ArrayList<Header> mHeaders = new ArrayList<Header>();
private FrameLayout mListFooter;
private ViewGroup mPrefsContainer;
private CharSequence mActivityTitle;
private ViewGroup mHeadersContainer;
//当前对应的标题,Header表示一个标题
private Header mCurHeader;
//管理器
private PreferenceManager mPreferenceManager;
//数据持久化
private Bundle mSavedInstanceState;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//布局文件加载的是preference_list_content.xml
final int layoutResId = sa.getResourceId(
com.android.internal.R.styleable.PreferenceActivity_layout,
com.android.internal.R.layout.preference_list_content);
setContentView(layoutResId);
mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs_frame);
mHeadersContainer = (ViewGroup) findViewById(com.android.internal.R.id.headers);
//......
}
}
//frameworks/base/core/res/res/layout/preference_list_content.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent">
<LinearLayout
android:id="@+id/prefs_container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1">
<!--标题栏集合headers:通常情况只会显示一个header,其他header被隐藏-->
<LinearLayout
style="?attr/preferenceHeaderPanelStyle"
android:id="@+id/headers"
android:orientation="vertical"
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="@integer/preferences_left_pane_weight">
<ListView android:id="@android:id/list"
style="?attr/preferenceListStyle"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
android:clipToPadding="false"
android:drawSelectorOnTop="false"
android:cacheColorHint="@android:color/transparent"
android:listPreferredItemHeight="48dp"
android:scrollbarAlwaysDrawVerticalTrack="true" />
<FrameLayout android:id="@+id/list_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0" />
</LinearLayout>
<!--内容fragment:其中PreferenceFrameLayout代表一个PreferenceScreen布局文件,在点击选项时通过android:fragment进行替换到对应的PreferenceFragment界面-->
<LinearLayout
android:id="@+id/prefs_frame"
style="?attr/preferencePanelStyle"
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="@integer/preferences_right_pane_weight"
android:orientation="vertical">
<include layout="@layout/breadcrumbs_in_fragment" />
<android.preference.PreferenceFrameLayout android:id="@+id/prefs"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
/>
</LinearLayout>
</LinearLayout>
<!--省略............-->
</LinearLayout>
1.2、Header对应fragment的标题
//frameworks/base/core/java/android/preference/PreferenceActivity.java
@Deprecated public abstract class PreferenceActivity extends ListActivity{
//Header对应当前界面,即存储了当前界面的基本信息:
// titleRes和title为标题栏
// summaryRes和summary为概述
@Deprecated public static final class Header implements Parcelable {
@StringRes
public int titleRes;
public CharSequence title;
@StringRes
public int summaryRes;
public CharSequence summary;
//对应的ICON图标
public int iconRes;
//存储了对应的fragment的类名,点击Preference会自动跳转到android:fragment属性的值对应的类(通过反射进行实例化)
public String fragment;
//需要传递到fragment的参数
public Bundle fragmentArguments;
public Intent intent;
public Bundle extras;
//序列化上面的内容,包括fragment的类名
@Override public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeInt(titleRes);
TextUtils.writeToParcel(title, dest, flags);
dest.writeInt(summaryRes);
TextUtils.writeToParcel(summary, dest, flags);
dest.writeInt(breadCrumbTitleRes);
TextUtils.writeToParcel(breadCrumbTitle, dest, flags);
dest.writeInt(breadCrumbShortTitleRes);
TextUtils.writeToParcel(breadCrumbShortTitle, dest, flags);
dest.writeInt(iconRes);
dest.writeString(fragment);
dest.writeBundle(fragmentArguments);
if (intent != null) {
dest.writeInt(1);
intent.writeToParcel(dest, flags);
} else dest.writeInt(0);
dest.writeBundle(extras);
}
//反序列化上面的内容,包括fragment的类名
public void readFromParcel(Parcel in) {
id = in.readLong();
titleRes = in.readInt();
title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
summaryRes = in.readInt();
summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
breadCrumbTitleRes = in.readInt();
breadCrumbTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
breadCrumbShortTitleRes = in.readInt();
breadCrumbShortTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
iconRes = in.readInt();
fragment = in.readString();
fragmentArguments = in.readBundle();
if (in.readInt() != 0) intent = Intent.CREATOR.createFromParcel(in);
extras = in.readBundle();
}
Header(Parcel in) {
readFromParcel(in);
}
public static final @android.annotation.NonNull Creator<Header> CREATOR = new Creator<Header>() {
public Header createFromParcel(Parcel source) {
return new Header(source);
}
public Header[] newArray(int size) {
return new Header[size];
}
};
}
private static class HeaderAdapter extends ArrayAdapter<Header> {
private static class HeaderViewHolder {
ImageView icon;
TextView title;
TextView summary;
}
private LayoutInflater mInflater;
private int mLayoutResId;
private boolean mRemoveIconIfEmpty;
public HeaderAdapter(Context context, List<Header> objects, int layoutResId, boolean removeIconBehavior) {
super(context, 0, objects);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mLayoutResId = layoutResId;
mRemoveIconIfEmpty = removeIconBehavior;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder holder;
View view;
if (convertView == null) {
view = mInflater.inflate(mLayoutResId, parent, false);
holder = new HeaderViewHolder();
holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);
holder.title = (TextView) view.findViewById(com.android.internal.R.id.title);
holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary);
view.setTag(holder);
} else {
view = convertView;
holder = (HeaderViewHolder) view.getTag();
}
}
}
}
1.3、切换fragment
//frameworks/base/core/java/android/preference/PreferenceActivity.java
@Deprecated public abstract class PreferenceActivity extends ListActivity{
//该方法很多地方被调用,例如创建或者点击需要切换fragment或者当前header的时候
public void switchToHeader(Header header) {
//判断是否当前header,是不切换
if (mCurHeader == header) {
getFragmentManager().popBackStack(BACK_STACK_PREFS, FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
if (header.fragment == null) throw new IllegalStateException("can't switch to header that has no fragment");
//切换对应的fragment
switchToHeaderInner(header.fragment, header.fragmentArguments);
//切换header布局里面的字符串和图标等信息
setSelectedHeader(header);
}
}
//还是使用了FragmentTransaction 方式进行切换
private void switchToHeaderInner(String fragmentName, Bundle args) {
getFragmentManager().popBackStack(BACK_STACK_PREFS, FragmentManager.POP_BACK_STACK_INCLUSIVE);
if (!isValidFragment(fragmentName)) throw new IllegalArgumentException("Invalid fragment for this activity: " + fragmentName);
Fragment f = Fragment.instantiate(this, fragmentName, args);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(mSinglePane
? FragmentTransaction.TRANSIT_NONE
: FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.replace(com.android.internal.R.id.prefs, f);
transaction.commitAllowingStateLoss();
if (mSinglePane && mPrefsContainer.getVisibility() == View.GONE) {
mPrefsContainer.setVisibility(View.VISIBLE);
mHeadersContainer.setVisibility(View.GONE);
}
}
//切换header的内容,通过触发点击事件
void setSelectedHeader(Header header) {
mCurHeader = header;
int index = mHeaders.indexOf(header);
if (index >= 0) getListView().setItemChecked(index, true);
else getListView().clearChoices();
showBreadCrumbs(header);
}
}
2、剖析PreferenceFragment
2.1、布局文件加载
PreferenceFragment的布局跟PreferenceActivity类似,如下代码:
//frameworks/base/core/java/android/preference/PreferenceFragment.java
@Deprecated public abstract class PreferenceFragment extends Fragment implements
/*Preference树结构下所有视图控件点击回调事件*/
PreferenceManager.OnPreferenceTreeClickListener {
@UnsupportedAppUsage
private PreferenceManager mPreferenceManager;
private ListView mList;
//PreferenceFragment的布局文件,与PreferenceActivity使用了同样的布局
private int mLayoutResId = com.android.internal.R.layout.preference_list_fragment;
private static final int MSG_BIND_PREFERENCES = 1;
private Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_BIND_PREFERENCES:
bindPreferences();
break;
}
}
};
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE);
mPreferenceManager.setFragment(this);
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//加载preference_list_fragment.xml,如果有acitivity沿用
mLayoutResId = a.getResourceId(com.android.internal.R.styleable.PreferenceFragment_layout, mLayoutResId);
return inflater.inflate(mLayoutResId, container, false);
}
//从布局文件中找到list
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ListView lv = (ListView) view.findViewById(android.R.id.list);
if (lv != null && a.hasValueOrEmpty(com.android.internal.R.styleable.PreferenceFragment_divider)) {
lv.setDivider( a.getDrawable(com.android.internal.R.styleable.PreferenceFragment_divider));
}
}
//这里很重要
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//bindPreferences方法是用来绑定加载所有的Preference类的
if (mHavePrefs) bindPreferences();
mInitDone = true;
if (savedInstanceState != null) {
Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
if (container != null) {
//如果有缓存PreferenceScreen,直接获取出来
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) preferenceScreen.restoreHierarchyState(container);
}
}
}
//设置Preference点击事件
@Override public void onStart() {
super.onStart();
mPreferenceManager.setOnPreferenceTreeClickListener(this);
}
@Override public void onStop() {
super.onStop();
mPreferenceManager.dispatchActivityStop();
mPreferenceManager.setOnPreferenceTreeClickListener(null);
}
}
2.2、什么时候回调onBindPreferences方法?
public abstract class PreferenceFragment{
private static final int MSG_BIND_PREFERENCES = 1;
private Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_BIND_PREFERENCES:
bindPreferences();
break;
}
}
};
private void bindPreferences() {
//获取当前对应的PreferenceScreen
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
View root = getView();
if (root != null) {
View titleView = root.findViewById(android.R.id.title);
if (titleView instanceof TextView) {
CharSequence title = preferenceScreen.getTitle();
if (TextUtils.isEmpty(title)) {
titleView.setVisibility(View.GONE);
} else {
((TextView) titleView).setText(title);
titleView.setVisibility(View.VISIBLE);
}
}
}
preferenceScreen.bind(getListView());
}
//回调子类(继承于PreferenceFragment中的onBindPreferences方法,通常在子类该方法钟回调所有需要显示的Preference控件)
onBindPreferences();
}
//通过mPreferenceManager获取当前的PreferenceScreen
public PreferenceScreen getPreferenceScreen() {
return mPreferenceManager.getPreferenceScreen();
}
//通过mPreferenceManager设置当前的PreferenceScreen
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
onUnbindPreferences();
mHavePrefs = true;
if (mInitDone) postBindPreferences();
}
}
//这个PreferenceScreen怎么来的呢,最终还是用户调用该接口设置进去的
public void addPreferencesFromResource(@XmlRes int preferencesResId) {
requirePreferenceManager();
setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(), preferencesResId, getPreferenceScreen()));
}
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)