Android S版本MtkSettings的加载流程(一)

启动流程

系统设置作为系统应用是一个需要高度客制化的原生应用。以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>

了解下Preference的通用XML元素属性

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布局加载选项的方式就是静态方式加载配置项,如图:

在这里插入图片描述
TopLevelSettings类定义了如何启动二级页面:

    @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;
    }
  • 查看SettingsActivity类的onCreate生命周期方法,我以[android]前缀添加了注释信息。
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配置项的父类和爷类。

猜你喜欢

转载自blog.csdn.net/qq_23069607/article/details/127301432
今日推荐