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源码路径如下图,其中比较重要的有前面已经见过的PreferenceScreen和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);
}
}
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?
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)