启动流程
系统设置作为系统应用是一个需要高度客制化的原生应用。以Mtk平台Android S版本的MtkSettings源码为例,分析其主要的加载流程。
- 清单文件:Settings作为SettingsHomepageActivity的别名,在桌面创建一个快捷入口。启动模式为singleTask,意味着每次启动时Settings主界面活动都会位于栈顶,子页面会结束掉并出栈。
<!-- Alias for launcher activity only, as this belongs to each profile. -->
<!-- [android]以singleTask启动模式启动,若存在该实例,结束掉该实例之上的所有Activity,并将该实例置为栈顶 -->
<activity-alias android:name="Settings"
android:label="@string/settings_label_launcher"
android:taskAffinity="com.android.settings.root"
android:launchMode="singleTask"
android:exported="true"
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>
- Settings.java继承自SettingsActivity.java,里面所有的类都只是定义,没有实现。
此类定义的都是二级及其子页面的活动,用于定义独立启动的activity活动类。比如从下拉公控长按启动的wifi界面的activity
/**
- Top-level Settings activity
*/
public class Settings extends SettingsActivity {
/*
* Settings subclasses for launching independently.
*/
public static class AssistGestureSettingsActivity extends SettingsActivity { /* empty */}
public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }
ResumedActivity: ActivityRecord{e80f8b1 u0 com.android.settings/.Settings$WifiSettingsActivity t10}
- SettingsHomepageActivity.java作为桌面设置主Activity入口,实现LifecycleObserver子类接口CategoryMixin,感知全局生命周期的变化,并及时处理categories的异步加载。其中TopLevelSettings.java是展示主界面的Fragment。
/** Settings homepage activity */
/** [android]主Activity入口,实现LifecycleObserver子类接口CategoryMixin,感知生命周期的变化 */
public class SettingsHomepageActivity extends FragmentActivity implements
CategoryMixin.CategoryHandler {
private CategoryMixin mCategoryMixin;
@Override
public CategoryMixin getCategoryMixin() {
return mCategoryMixin;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//[android]settings_homepage_container布局内部包含一个支持嵌套的NestedScrollView
setContentView(R.layout.settings_homepage_container);
final View appBar = findViewById(R.id.app_bar_container);
appBar.setMinimumHeight(getSearchBoxHeight());
initHomepageContainer();
final Toolbar toolbar = findViewById(R.id.search_action_bar);
FeatureFactory.getFactory(this).getSearchFeatureProvider()
.initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
mCategoryMixin = new CategoryMixin(this);
getLifecycle().addObserver(mCategoryMixin);
...省略
//[android]显示主界面Fragment:TopLevelSettings
showFragment(new TopLevelSettings(), R.id.main_content);
((FrameLayout) findViewById(R.id.main_content))
.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
}
- TopLevelSettings类继承于DashboardFragment.java,DashboardFragment类包含静态和动态设置项列表的加载,很重要。主界面布局文件为top_level_settings.xml。
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="top_level_settings">
<Preference
android:fragment="com.android.settings.network.NetworkDashboardFragment"
android:icon="@drawable/ic_settings_wireless"
android:key="top_level_network"
android:order="-150"
android:title="@string/network_dashboard_title"
android:summary="@string/summary_placeholder"
settings:controller="com.android.settings.network.TopLevelNetworkEntryPreferenceController"/>
...省略很多Preference
<PreferenceScreen>
android:key //唯一标识,SharedPreferences可通过此Key值进行数据保存
android:defaultValue //默认值
android:enabled //是否可用
android:icon //图标
android:title //大标题
android:summary //小标题(摘要)
android:layout //布局
android:widgetLayout //小部件部分的布局
android:persistent /Preference元素值是否保存到sharedpreferences文件中。
android:order //顺序值越大,优先级越高,菜单选项排列越前
android:fragment //点击项需要启动的android:fragment
android:dependency //一个Preference的可用状态依赖于另一个Preference
android:disableDependentsState //与android:dependency相反
可见主界面列表项是由一个个Preference构成,比如网络和互联网,其中android:fragment指明了该选项加载的Fragment是NetworkDashboardFragment,settings:controller指明了每一项的功能以及对二级菜单项的控制。这种通过xml布局加载选项的方式就是静态方式加载配置项,如图:
@Override
public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
//[android] 回调OnPreferenceStartFragmentCallback的接口方法:初始化并启动目标首选项
new SubSettingLauncher(getActivity())
.setDestination(pref.getFragment())
.setArguments(pref.getExtras())
.setSourceMetricsCategory(caller instanceof Instrumentable
? ((Instrumentable) caller).getMetricsCategory()
: Instrumentable.METRICS_CATEGORY_UNKNOWN)
.setTitleRes(-1)
//[android] 使用建造者模式构建启动参数最终调用startActivity(intent),目标SubSettings
.launch();
return true;
}
LaunchRequest作为SubSettingsLauncher启动器内部类,封装了目标intent和参数等启动信息,通过建造者模式统一启动二级页面SubSettings。
public Intent toIntent() {
final Intent intent = new Intent(Intent.ACTION_MAIN);
copyExtras(intent);
//[android]所有二级页面Activity都是SubSettings.class
intent.setClass(mContext, SubSettings.class);
if (TextUtils.isEmpty(mLaunchRequest.destinationName)) {
throw new IllegalArgumentException("Destination fragment must be set");
}
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, mLaunchRequest.destinationName);
if (mLaunchRequest.sourceMetricsCategory < 0) {
throw new IllegalArgumentException("Source metrics category must be set");
}
intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
mLaunchRequest.sourceMetricsCategory);
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, mLaunchRequest.arguments);
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME,
mLaunchRequest.titleResPackageName);
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID,
mLaunchRequest.titleResId);
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, mLaunchRequest.title);
intent.addFlags(mLaunchRequest.flags);
intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
mLaunchRequest.transitionType);
return intent;
}
/**
* Simple container that has information about how to launch a subsetting.
*/
static class LaunchRequest {
//[android]启动信息
String destinationName; //标题资源Id
int titleResId; //标题资源Id
String titleResPackageName;
CharSequence title; //标题
int sourceMetricsCategory = -100; //指标类别 > 0
int flags;
Fragment mResultListener;
int mRequestCode;
UserHandle userHandle;
int transitionType;
Bundle arguments;
Bundle extras;
}
- 看下SubSettings类,isValidFragment方法验证Fragment是否有效,它的实现在父类SettingsActivity中,可见如果要新增子页面fragment,要注册在SettingsGateway类中。
public class SubSettings extends SettingsActivity {
@Override
public boolean onNavigateUp() {
finish();
return true;
}
@Override
protected boolean isValidFragment(String fragmentName) {
Log.d("SubSettings", "Launching fragment " + fragmentName);
return true;
protected boolean isValidFragment(String fragmentName) {
// Almost all fragments are wrapped in this,
// except for a few that have their own activities.
//[android]ENTRY_FRAGMENTS数组维护了多个有效的fragment,如果要在子页面新加fragment,要注册在SettingsGateway的ENTRY_FRAGMENTS数组中。
for (int i = 0; i < SettingsGateway.ENTRY_FRAGMENTS.length; i++) {
if (SettingsGateway.ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
}
return false;
}
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
Log.d(LOG_TAG, "Starting onCreate");
long startTime = System.currentTimeMillis();
final FeatureFactory factory = FeatureFactory.getFactory(this);
//[android]以抽象工厂的方式提供Dashboard特性
mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);
// Should happen before any call to getIntent()
//[android]获取定义在manifest名为com.android.settings.FRAGMENT_CLASS的meta-data
getMetaData();
//[android]检查Intent EXTRA_SHOW_FRAGMENT, 内部重新包裹了Intent。
final Intent intent = getIntent();
if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
}
// Getting Intent properties can only be done after the super.onCreate(...)
final String initialFragmentName = getInitialFragmentName(intent);
//[android]判断是否是SubSettings
// This is a "Sub Settings" when:
// - this is a real SubSettings
// - or :settings:show_fragment_as_subsetting is passed to the Intent
final boolean isSubSettings = this instanceof SubSettings ||
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
// If this is a sub settings, then apply the SubSettings Theme for the ActionBar content
// insets.
// If this is in setup flow, don't apply theme. Because light theme needs to be applied
// in SettingsBaseActivity#onCreate().
if (isSubSettings && !WizardManagerHelper.isAnySetupWizard(getIntent())) {
setTheme(R.style.Theme_SubSettings);
}
//[android]设置Activity:SubSettings的布局
setContentView(R.layout.settings_main_prefs);
//[android]监控fragment回退栈状态改变
getSupportFragmentManager().addOnBackStackChangedListener(this);
if (savedState != null) {
// We are restarting from a previous saved state; used that to initialize, instead
// of starting fresh.
//[android]可以从EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME,EXTRA_SHOW_FRAGMENT_TITLE,EXTRA_SHOW_FRAGMENT_TITLE_RESID获取页面标题
setTitleFromIntent(intent);
ArrayList<DashboardCategory> categories =
savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
if (categories != null) {
mCategories.clear();
mCategories.addAll(categories);
//[android]再从fragment回退栈中找标题
setTitleFromBackStack();
}
} else {
//[android]启动一个新的fragment
launchSettingFragment(initialFragmentName, intent);
}
final boolean isInSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
//[android]actionBar和其它按钮控件的初始化
final actionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(!isInSetupWizard);
actionBar.setHomeButtonEnabled(!isInSetupWizard);
actionBar.setDisplayShowTitleEnabled(true);
}
mMainSwitch = findViewById(R.id.switch_bar);
if (mMainSwitch != null) {
mMainSwitch.setMetricsTag(getMetricsTag());
mMainSwitch.setTranslationZ(findViewById(R.id.main_content).getTranslationZ() + 1);
}
if (DEBUG_TIMING) {
Log.d(LOG_TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms");
}
}
可以看出二级页面都会启动一个Fragment,看下launchSettingFragment(initialFragmentName, intent)这个方法。
void launchSettingFragment(String initialFragmentName, Intent intent) {
if (initialFragmentName != null) {
setTitleFromIntent(intent);
Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
//[android]通过验证、设置面包屑标题并加载一个新的fragment
switchToFragment(initialFragmentName, initialArguments, true,
mInitialTitleResId, mInitialTitle);
} else {
// Show search icon as up affordance if we are displaying the main Dashboard
mInitialTitleResId = R.string.dashboard_title;
//[android]加载主界面fragment
switchToFragment(TopLevelSettings.class.getName(), null /* args */, false,
mInitialTitleResId, mInitialTitle);
}
}
启动的Fragment名称就是EXTRA_SHOW_FRAGMENT=“:settings:show_fragment”指定的值,所传递的参数由EXTRA_SHOW_FRAGMENT_ARGUMENTS=”:settings:show_fragment_args“指定。
比如启动的二级子页面:
总结
- 设置中子页面SubSettings.java和单独启动的子页面(比如WifiSettingsActivity.java)的父类都是SettingsActivity.java,统一了加载的逻辑。SettingsActivity类持有的配置项fragment需展示出来时,需注册到SettingsGateway类的ENTRY_FRAGMENTS数组中,否则会抛出security exception。
- 每个菜单设置项对应的布局基本都是xml文件,若要增加找到对应加载的xml直接增加具体Preference选项即可。然后通过指定fragment属性跳转对应页面,这种静态加载配置项的方式相对不灵活,耦合性也较高。在另一篇**Android S版本MtkSettings的加载流程(二)**将会介绍另外一种动态加载配置项的方式。
- DashboardFragment.java -> SettingsPreferenceFragment.java,这是几乎所有fragment配置项的父类和爷类。
原文地址:https://blog.csdn.net/qq_23069607/article/details/127301432
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_48582.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!