启动页面Settings
settings路径是packages/apps/Settings/src/com/android/settings/Settings.java,Settings的继承SettingsActivity,并且内部有很多继承SettingActivity的内部类,这些类作用后面再详细介绍。
SettingActivity界面的oncreate中其实有三个不同布局加载方向,对应分别是Settings、Settings的内部类、SubSettings
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
long startTime = System.currentTimeMillis();
//获取mDashboardFeatureProvider和mMetricsFeatureProvider对象
final FeatureFactory factory = FeatureFactory.getFactory(this);
mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);
mSearchFeatureProvider = factory.getSearchFeatureProvider();
mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
getMetaData();
final Intent intent = getIntent();
if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
}
mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
Context.MODE_PRIVATE);
final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);
final ComponentName cn = intent.getComponent();
final String className = cn.getClassName();
//mIsShowingDashboard获取不同类名布置不同布局,true是对应一级界面
mIsShowingDashboard = className.equals(Settings.class.getName());
final boolean isSubSettings = this instanceof SubSettings ||
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
// 如果是SubSettings,设置二级界面主题
if (isSubSettings) {
setTheme(R.style.Theme_SubSettings);
}
//两个布局加载,区分开一级界面
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
mContent = (ViewGroup) findViewById(R.id.main_content);
getFragmentManager().addOnBackStackChangedListener(this);
if (savedState != null) {
//界面重新加载设置标题
setTitleFromIntent(intent);
ArrayList<DashboardCategory> categories =
savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
if (categories != null) {
mCategories.clear();
mCategories.addAll(categories);
setTitleFromBackStack();
}
mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
} else {
//跳转到对应界面
launchSettingFragment(initialFragmentName, isSubSettings, intent);
}
//后面主要对应虚拟按键逻辑,这边不介绍
mActionBar = getActionBar();
.....
}
一级界面
在上面oncreate方法根据类名加载不同的布局,mIsShowingDashboard是判断是否一级界面
mIsShowingDashboard = className.equals(Settings.class.getName());
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
一级界面的布局:settings_main_dashboard是一个单纯的FrameLayout
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_content"
android:layout_height="match_parent"
android:layout_width="match_parent"
/>
接下来看跳转界面函数 launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent)
void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) {
//对应一级界面的mIsShowingDashboard为true
if (!mIsShowingDashboard && initialFragmentName != null) {
......
} else {
mDisplayHomeAsUpEnabled = false;
mDisplaySearch = true;
mInitialTitleResId = R.string.dashboard_title;
//这是才是真正加载一级界面逻辑
switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false,
mInitialTitleResId, mInitialTitle, false);
}
}
launchSettingFragment方法最终加载的一级界面是DashboardSummary.class,对应是Fragment的父类。
接着往下看DashboardSummary.class,onCreateView函数对应加载布局dashboard.xml布局
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.dashboard, container, false);
}
<com.android.settings.dashboard.conditional.FocusRecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dashboard_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:focusable="false"
android:paddingStart="@dimen/dashboard_padding_start"
android:paddingEnd="@dimen/dashboard_padding_end"
android:paddingTop="@dimen/dashboard_padding_top"
android:paddingBottom="@dimen/dashboard_padding_bottom"
android:scrollbars="vertical"/>
dashboard.xml是一个自定义的RecyclerView
数据加载
数据加载对应看RecyclerView的适配器DashboardAdapter
@Override
public void onBindViewHolder(DashboardItemHolder holder, int position) {
final int type = mDashboardData.getItemTypeByPosition(position);
switch (type) {
case R.layout.dashboard_category:
onBindCategory(holder,
(DashboardCategory) mDashboardData.getItemEntityByPosition(position));
break;
case R.layout.dashboard_tile:
final Tile tile = (Tile) mDashboardData.getItemEntityByPosition(position);
onBindTile(holder, tile);
holder.itemView.setTag(tile);
holder.itemView.setOnClickListener(mTileClickListener);
break;
case R.layout.suggestion_header:
onBindSuggestionHeader(holder, (DashboardData.SuggestionHeaderData)
mDashboardData.getItemEntityByPosition(position));
break;
case R.layout.suggestion_tile:
final Tile suggestion = (Tile) mDashboardData.getItemEntityByPosition(position);
final String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier(
mContext, suggestion);
if (!mSuggestionsShownLogged.contains(suggestionId)) {
mMetricsFeatureProvider.action(
mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, suggestionId);
mSuggestionsShownLogged.add(suggestionId);
}
onBindTile(holder, suggestion);
holder.itemView.setOnClickListener(v -> {
mMetricsFeatureProvider.action(mContext,
MetricsEvent.ACTION_SETTINGS_SUGGESTION, suggestionId);
((SettingsActivity) mContext).startSuggestion(suggestion.intent);
});
break;
case R.layout.condition_card:
final boolean isExpanded = mDashboardData.getItemEntityByPosition(position)
== mDashboardData.getExpandedCondition();
ConditionAdapterUtils.bindViews(
(Condition) mDashboardData.getItemEntityByPosition(position),
holder, isExpanded, mConditionClickListener, v -> onExpandClick(v));
break;
}
}
可以看出数据Tile对象,DashboardCategory里面含有Title属性,接着看如何获取DashboardCategory数据,在updateCategoryAndSuggestion函数发现对应获取数据逻辑
void updateCategoryAndSuggestion(List<Tile> suggestions) {
final Activity activity = getActivity();
if (activity == null) {
return;
}
List<DashboardCategory> categories = new ArrayList<>();
//对应获取DashboardCategory的集合数据 categories.add(mDashboardFeatureProvider.getTilesForCategory(
CategoryKey.CATEGORY_HOMEPAGE));
if (suggestions != null) {
mAdapter.setCategoriesAndSuggestions(categories, suggestions);
} else {
mAdapter.setCategory(categories);
}
}
在mDashboardFeatureProvider主要获取一级菜单数据如“网络”、“wifi”、“电池”、“蓝牙”等,对应的实现类是DashboardFeatureProviderImpl.java
private final CategoryManager mCategoryManager;
public DashboardFeatureProviderImpl(Context context) {
mContext = context.getApplicationContext();
mCategoryManager = CategoryManager.get(context, getExtraIntentAction());
}
@Override
public DashboardCategory getTilesForCategory(String key) {
return mCategoryManager.getTilesByCategory(mContext, key);
}
具体数据是CategoryManager(在settinglib包下)的getTilesByCategory函数获取
public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
return getTilesByCategory(context, categoryKey, TileUtils.SETTING_PKG);
}
public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey,
String settingPkg) {
//对应获取数据该方法并保存到mCategoryByKeyMap集合
tryInitCategories(context, settingPkg);
return mCategoryByKeyMap.get(categoryKey);
}
对应沿着tryInitCategories方法获取对应数据DashboardCategory集合是TileUtils的getCategories的函数
private synchronized void tryInitCategories(Context context, boolean forceClearCache,
String settingPkg) {
if (mCategories == null) {
if (forceClearCache) {
mTileByComponentCache.clear();
}
mCategoryByKeyMap.clear();
mCategories = TileUtils.getCategories(context, mTileByComponentCache,
false /* categoryDefinedInManifest */, mExtraAction, settingPkg);
for (DashboardCategory category : mCategories) {
mCategoryByKeyMap.put(category.key, category);
}
backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
sortCategories(context, mCategoryByKeyMap);
filterDuplicateTiles(mCategoryByKeyMap);
}
}
TileUtils#getCategories函数主要对应通过解析清单文件xml类中的标签的参数信息,并把信息组合添加到对应集合DashboardCategory对象;还记得上面对应一开始获取数据方法传的参数CategoryKey.CATEGORY_HOMEPAGE对应的字符串是“com.android.settings.category.ia.homepage”代表的清单中含有 的value对应该值代表一级界面,TileUtils#getCategories对应解析具体intent清单的逻辑是在getTilesForIntent函数里面
public static void getTilesForIntent(Context context, UserHandle user, Intent intent,
Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
boolean usePriority, boolean checkCategory) {
PackageManager pm = context.getPackageManager();
//返回清单含有meta-data标签的activity
List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
PackageManager.GET_META_DATA, user.getIdentifier());
for (ResolveInfo resolved : results) {
if (!resolved.system) {
// Do not allow any app to add to settings, only system ones.
continue;
}
ActivityInfo activityInfo = resolved.activityInfo;
Bundle metaData = activityInfo.metaData;
String categoryKey = defaultCategory;
// Load category
if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
&& categoryKey == null) {
Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "
+ intent + " missing metadata "
+ (metaData == null ? "" : EXTRA_CATEGORY_KEY));
continue;
} else {
categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
}
Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
activityInfo.name);
Tile tile = addedCache.get(key);
if (tile == null) {
tile = new Tile();
tile.intent = new Intent().setClassName(
activityInfo.packageName, activityInfo.name);
tile.category = categoryKey;
tile.priority = usePriority ? resolved.priority : 0;
tile.metaData = activityInfo.metaData;
updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
pm);
if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
addedCache.put(key, tile);
}
if (!tile.userHandle.contains(user)) {
tile.userHandle.add(user);
}
if (!outTiles.contains(tile)) {
outTiles.add(tile);
}
}
}
可以看出一级数据最终是从AndroidManifest.xml中配置的,举例系统设置
<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>
//value=“com.android.settings.category.ia.homepage” 代表一级界面
<meta-data android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.homepage"/>
//value界面代表点击跳转的fragment
<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>
二级界面
一级界面加载动态的加载,二级菜单则有所区别,有两种形式动态加载和静态xml布局文件,就“系统设置”为例子。
上面刚才分析一级的可以看出来,一级的系统设置点击跳转的界面是com.android.settings.system.SystemDashboardFragment,对应该类继承DashboardFragment.java,而DashboardFragment继承与PreferenceFragment。PreferenceFragment的加载通过xml布局来加载,即所有二级界面的基类。
静态加载
private void displayResourceTiles() {
//获取布局xml
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
return;
}
//实现布局添加
addPreferencesFromResource(resId);
final PreferenceScreen screen = getPreferenceScreen();
//实现布局中的子项控件的业务控制,由DashboardFragment.java的子类实现getPreferenceControllers函数
Collection<PreferenceController> controllers = mPreferenceControllers.values();
for (PreferenceController controller : controllers) {
//controller层传布局对象
controller.displayPreference(screen);
}
}
在controllers对象将布局PreferenceScreen传给各个controller,通过PreferenceScreen找到对应的控件,PreferenceController获取对应控件对象进行逻辑控制操作
SystemDashboardFragment.class
@Override
protected int getPreferenceScreenResId() {
return R.xml.system_dashboard_fragment;
}
@Nullable
@Override
protected List<AbstractPreferenceController> getPreferenceControllers(@NotNull Context context) {
final List<AbstractPreferenceController> controllers=new ArrayList<>();
controllers.add(new SystemUpdatePreferenceController(context, UserManager.get(context)));
controllers.add(new AdditionalSystemUpdatePreferenceController(context));
controllers.add(new BackupSettingsActivityPreferenceController(context));
return controllers;
}
对应的AbstractPreferenceController的实现这边对应不具体详细介绍了
动态加载
void refreshDashboardTiles(final String TAG) {
final PreferenceScreen screen = getPreferenceScreen();
//获取DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP中获取Category值,该值通过类名获取
final DashboardCategory category =
mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
if (category == null) {
Log.d(TAG, "NO dashboard tiles for " + TAG);
return;
}
List<Tile> tiles = category.tiles;
if (tiles == null) {
Log.d(TAG, "tile list is empty, skipping category " + category.title);
return;
}
// Create a list to track which tiles are to be removed.
final List<String> remove = new ArrayList<>(mDashboardTilePrefKeys);
// There are dashboard tiles, so we need to install SummaryLoader.
if (mSummaryLoader != null) {
mSummaryLoader.release();
}
final Context context = getContext();
mSummaryLoader = new SummaryLoader(getActivity(), getCategoryKey());
mSummaryLoader.setSummaryConsumer(this);
final TypedArray a = context.obtainStyledAttributes(new int[]{
android.R.attr.colorControlNormal});
final int tintColor = a.getColor(0, context.getColor(android.R.color.white));
a.recycle();
final String pkgName = context.getPackageName();
// Install dashboard tiles.
for (Tile tile : tiles) {
final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
if (TextUtils.isEmpty(key)) {
Log.d(TAG, "tile does not contain a key, skipping " + tile);
continue;
}
if (!displayTile(tile)) {
continue;
}
if (pkgName != null && tile.intent != null
&& !pkgName.equals(tile.intent.getComponent().getPackageName())) {
// If this drawable is coming from outside Settings, tint it to match the color.
tile.icon.setTint(tintColor);
}
if (mDashboardTilePrefKeys.contains(key)) {
// Have the key already, will rebind.
final Preference preference = mProgressiveDisclosureMixin.findPreference(
screen, key);
mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), getMetricsCategory(),
preference, tile, key, mPlaceholderPreferenceController.getOrder());
} else {
// Don't have this key, add it.
final Preference pref = new Preference(getPrefContext());
mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), getMetricsCategory(),
pref, tile, key, mPlaceholderPreferenceController.getOrder());
mProgressiveDisclosureMixin.addPreference(screen, pref);
mDashboardTilePrefKeys.add(key);
}
remove.remove(key);
}
// Finally remove tiles that are gone.
for (String key : remove) {
mDashboardTilePrefKeys.remove(key);
mProgressiveDisclosureMixin.removePreference(screen, key);
}
mSummaryLoader.setListening(true);
}
getCategoryKey函数通过DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP中获取Category值
DashboardFragmentRegistry.class
public String getCategoryKey() {
return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
}
static {
PARENT_TO_CATEGORY_KEY_MAP = new ArrayMap<>();
......
PARENT_TO_CATEGORY_KEY_MAP.put(
SystemDashboardFragment.class.getName(), CategoryKey.CATEGORY_SYSTEM);
//对应的CategoryKey.CATEGORY_SYSTEM的字符串为"com.android.settings.category.ia.system"
......
}
所以对应动态从AndroidManifest.xml文件中获取数据集合,举例子
<activity android:name=".Settings$LanguageAndInputSettingsActivity"
android:label="@string/language_keyboard_settings_title"
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>