1. 前言
本篇主要是为了记录Settings主界面的加载流程,为以后深入分析和记录在Settings应用做准备
2. 流程
2.1 Settings 文件分析
首先,我们要分析Settings应用主界面的加载工作,需要先确认哪个文件为此应用的主Activity,从AndroidManifest.xml文件中
<activity-alias android:name="Settings"
android:taskAffinity="com.android.settings"
android:label="@string/settings_label_launcher"
android:launchMode="singleTask"
android:targetActivity="Settings">
<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应用的入口函数为Settings.java
遍观整个Settings.java文件,发现其继承了SettingsActivity.java文件,因此其整个生命周期均在SettingsActivity中进行实现的
因此我们进入Settings.java的生命周期
2.1.1 SettingsActivity的onCreate方法
@Override
protected void onCreate(Bundle savedState) {
// 1. 调用父类的onCreate方法
super.onCreate(savedState);
long startTime = System.currentTimeMillis();
// 2. 初始化FeatureFactory,并且获取DashboardFeatureProvider
final FeatureFactory factory = FeatureFactory.getFactory(this);
mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);
// Should happen before any call to getIntent()
// 3. 调用getMetaData方法
getMetaData();
// 4. 获取传入的参数数据
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);
// Getting Intent properties can only be done after the super.onCreate(...)
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 = className.equals(Settings.class.getName());
// 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 (isSubSettings) {
setTheme(R.style.Theme_SubSettings);
}
// 5. 加载主界面
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
mContent = findViewById(R.id.main_content);
getFragmentManager().addOnBackStackChangedListener(this);
if (savedState != null) {
......
} else {
launchSettingFragment(initialFragmentName, isSubSettings, intent);
}
......
}
2.1.1.1 SettingsDrawerActivity的onCreate方法
protected void onCreate(@Nullable Bundle savedInstanceState) {
......
// 设置布局文件
super.setContentView(R.layout.settings_with_drawer);
......
}
这边设置了整个Settings的主界面使用的是一个DrawerLayout布局,但是打开settings_with_drawer.xml文件,会发现,DrawerLayout只包含了一个布局,因此,即使使用了DrawerLayout,也没有抽屉
2.1.1.2 FeatureFactory的本质
从代码中看,首先调用的是FeatureFactory的静态方法getFactory,传入的参数为SettingsActivity
// 单例模式
public static FeatureFactory getFactory(Context context) {
if (sFactory != null) {
return sFactory;
}
......
final String clsName = context.getString(R.string.config_featureFactory);
if (TextUtils.isEmpty(clsName)) {
throw new UnsupportedOperationException("No feature factory configured");
}
try {
sFactory = (FeatureFactory) context.getClassLoader().loadClass(clsName).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
throw new FactoryNotFoundException(e);
}
......
return sFactory;
}
<string name="config_featureFactory" translatable="false">com.android.settings.overlay.FeatureFactoryImpl</string>
之后调用getDashboardFeatureProvider方法初始化DashboardFeatureProvider对象
@Override
public DashboardFeatureProvider getDashboardFeatureProvider(Context context) {
if (mDashboardFeatureProvider == null) {
mDashboardFeatureProvider = new DashboardFeatureProviderImpl(context);
}
return mDashboardFeatureProvider;
}
public DashboardFeatureProviderImpl(Context context) {
mContext = context.getApplicationContext();
mCategoryManager = CategoryManager.get(context, getExtraIntentAction());
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
mPackageManager = context.getPackageManager();
}
2.1.1.3 调用getMetaData方法
private void getMetaData() {
try {
ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),PackageManager.GET_META_DATA);
if (ai == null || ai.metaData == null) return;
mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
} catch (NameNotFoundException nnfe) {
// No recovery
Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
}
}
从AndroidManifest.xml文件中可以看到,meta data fragment class的值是没有设定的,因此mFragmentClass的值为null
2.1.1.4 获取传入的参数数据
通过调用getIntent方法来获取从外部传入的参数数据
public Intent getIntent() {
Intent superIntent = super.getIntent();
String startingFragment = getStartingFragmentClass(superIntent);
......
if (startingFragment != null) {
Intent modIntent = new Intent(superIntent);
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
Bundle args = superIntent.getExtras();
if (args != null) {
args = new Bundle(args);
} else {
args = new Bundle();
}
args.putParcelable("intent", superIntent);
modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
return modIntent;
}
return superIntent;
}
getStartingFragmentClass方法获取启动的Fragment
private String getStartingFragmentClass(Intent intent) {
// 刚刚分析到,这个值是null
if (mFragmentClass != null) return mFragmentClass;
// 这个值获取到的结果是com.android.settings.Settings
String intentClass = intent.getComponent().getClassName();
// 因此此处直接返回null
if (intentClass.equals(getClass().getName())) return null;
......
}
回到getIntent方法中,直接返回super.getIntent
2.1.1.5 加载主界面
如上面所述,此时mIsShowingDashboard值为true,因此会直接使用settings_main_dashboard布局文件,然后因为savedState为null,因此会直接调用launchSettingFragment方法,其中,第一个参数为null,第二个参数为false。第三个参数为super.getIntent得到的值
void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) {
// 由于mIsShowingDashboard为true,因此走else分支
if (!mIsShowingDashboard && initialFragmentName != null) {
......
} 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);
}
}
调用switchToFragment方法,注意其的参数
第一个参数为DashboardSummary的class
第二个参数为null
第三个参数为false
第四个参数为false
第五个参数为字串资源索引
第六个参数为字串资源
第七个参数为false
打开switchToFragment方法
private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate, boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) {
// 由于validate为false,因此直接跳过此方法
if (validate && !isValidFragment(fragmentName)) {
......
}
Fragment f = Fragment.instantiate(this, fragmentName, args);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.main_content, f);
// false
if (withTransition) {
TransitionManager.beginDelayedTransition(mContent);
}
// false
if (addToBackStack) {
transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
}
if (titleResId > 0) {
transaction.setBreadCrumbTitle(titleResId);
} else if (title != null) {
transaction.setBreadCrumbTitle(title);
}
transaction.commitAllowingStateLoss();
getFragmentManager().executePendingTransactions();
return f;
}
由于此处第三个参数,即validate为false,因此直接会跳过抛出异常,即主界面跳过检查,直接使用DashboardSummary来替代main_content视图
接下来就会直接进入DashboardSummary来处理Settings应用的主界面布局视图
2.2 DashboardSummary分析
从上面的分析,在Settings的onCreate方法中,将DashboardSummary初始化,并且当做Settings的主内容视图,因此,接下来需要看看