系统设置 Settings 之 Activity 嵌入

91 阅读3分钟

官方Activity嵌入链接

activity 嵌入可以将应用的一个任务窗口拆分到两个 activity 中,或者拆分到同一个 activity 的两个实例中,从而优化大屏设备上的应用。

如果应用由多个 activity 组成,activity 嵌入让您能够在平板电脑、可折叠设备和 ChromeOS 设备上提供增强的用户体验。

activity 嵌入无需重构代码。至于应用如何显示其 activity(是并排还是堆叠),可以通过创建 XML 配置文件或进行 Jetpack WindowManager API 调用来确定。

系统会自动维护对小屏幕的支持。当应用在配备小屏幕的设备上时,activity 会相互堆叠。在大屏幕上,activity 会并排显示。系统会根据您已创建的配置(不需要分支逻辑)来确定呈现方式。

activity 嵌入支持设备屏幕方向的变化,并且可以在可折叠设备上无缝运行,该功能会随着设备折叠和展开而堆叠和取消堆叠 activity。

大多数搭载 Android 12L(API 级别 32)及更高版本的大屏幕设备均支持 activity 嵌入。

1. 库引用:

在settings目录下的Android.bp文件引用window库

android.bp

android_library {
    name: "Settings-core",//lib的名字
    //..
    static_libs: [
        //..
        "androidx.window_window",
//..
android_app {
    name: "Settings",
    //..
    static_libs: ["Settings-core"], //引用上述lib

2. SettingsApplication:

在自定义的application里使用自定义的工具类进行分屏规则初始化

public class SettingsApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        final ActivityEmbeddingRulesController controller =
                new ActivityEmbeddingRulesController(this);
        controller.initRules();
    }

3. ActivityEmbeddingRulesController:

    public ActivityEmbeddingRulesController(Context context) {
        mContext = context;
        mRuleController = RuleController.getInstance(context);
    }

4. initRules:

初始化设置了主页的placeholder activity,以及不需要分屏的页面

    public void initRules() {
        if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(mContext)) {
            //不支持分屏,就不用初始化了
            return;
        }

        mRuleController.clearRules();

        //为homepage页设置 placeholder rule
        registerHomepagePlaceholderRule();
        //添加一些不需要分屏的activity规则
        registerAlwaysExpandRule();
    }

5. isEmbeddingActivityEnabled:

    public static boolean isEmbeddingActivityEnabled(Context context) {
        //这个是配置里的设定
        final boolean isFlagEnabled = FeatureFlagUtils.isEnabled(context,
                FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN);
       //这个是判断系统是否支持分屏         
        final boolean isSplitSupported = SplitController.getInstance(context).isSplitSupported();

        return isFlagEnabled && isSplitSupported;
    }

6. FeatureFlagUtils:

先判断global setting,再判断Systemproperties,最后是默认配置

    public static boolean isEnabled(Context context, String feature) {
        // Override precedence:
        // Settings.Global -> sys.fflag.override.* -> static list

        // Step 1: check if feature flag is set in Settings.Global.
        String value;
        if (context != null) {
            value = Settings.Global.getString(context.getContentResolver(), feature);
            if (!TextUtils.isEmpty(value)) {
                return Boolean.parseBoolean(value);
            }
        }

        // Step 2: check if feature flag has any override.
        // Flag name: [persist.]sys.fflag.override.<feature>
        value = SystemProperties.get(getSystemPropertyPrefix(feature) + feature);
        if (!TextUtils.isEmpty(value)) {
            return Boolean.parseBoolean(value);
        }
        // Step 3: check if feature flag has any default value.
        value = getAllFeatureFlags().get(feature);
        return Boolean.parseBoolean(value);
    }

7. registerHomepagePlaceholderRule:

分屏模式,homePage页面,右侧默认显示NetworkDashboardActivity页面

    private void registerHomepagePlaceholderRule() {
        final Set<ActivityFilter> activityFilters = new HashSet<>();
        //为下边2个主屏类设置占位activity
        addActivityFilter(activityFilters, SettingsHomepageActivity.class);
        addActivityFilter(activityFilters, Settings.class);
        //占位activity,就是右侧默认显示的页面
        final Intent intent = new Intent(mContext, Settings.NetworkDashboardActivity.class);
        intent.putExtra(SettingsActivity.EXTRA_IS_SECOND_LAYER_PAGE, true);
        SplitAttributes attributes = new SplitAttributes.Builder()
                .setSplitType(SplitAttributes.SplitType.ratio(
                        ActivityEmbeddingUtils.getSplitRatio(mContext)))//0.3636
                .build();
        final SplitPlaceholderRule placeholderRule = new SplitPlaceholderRule.Builder(
                activityFilters, intent)
                .setMinWidthDp(ActivityEmbeddingUtils.getMinCurrentScreenSplitWidthDp())
                .setMinSmallestWidthDp(ActivityEmbeddingUtils.getMinSmallestScreenSplitWidthDp())
                .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ALWAYS_ALLOW)
                .setSticky(false)
                .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ADJACENT)
                .setDefaultSplitAttributes(attributes)
                .build();

        mRuleController.addRule(placeholderRule);
    }

8. registerAlwaysExpandRule:

指定需要全屏显示的页面

    private void registerAlwaysExpandRule() {
        final Set<ActivityFilter> activityFilters = new HashSet<>();
        if (FeatureFlagUtils.isEnabled(mContext, FeatureFlags.SETTINGS_SEARCH_ALWAYS_EXPAND)) {
        //搜索页
            final Intent searchIntent = FeatureFactory.getFactory(mContext)
                    .getSearchFeatureProvider()
                    .buildSearchIntent(mContext, SettingsEnums.SETTINGS_HOMEPAGE);
            addActivityFilter(activityFilters, searchIntent);
        }
        //指纹相关的几个页面
        addActivityFilter(activityFilters, FingerprintEnrollIntroduction.class);
        addActivityFilter(activityFilters, FingerprintEnrollIntroductionInternal.class);
        addActivityFilter(activityFilters, FingerprintEnrollEnrolling.class);
        //头像选择页面
        addActivityFilter(activityFilters, AvatarPickerActivity.class);
        ActivityRule activityRule = new ActivityRule.Builder(activityFilters).setAlwaysExpand(true)
                .build();
        mRuleController.addRule(activityRule);
    }又

9. TopLevelSettings:

点击的时候会为homepage注册分屏subSetting,而我们跳转的页面都是subSetting

    public boolean onPreferenceTreeClick(Preference preference) {
        if (isDuplicateClick(preference)) {
            return true;
        }

        // Register SplitPairRule for SubSettings.
        //preference点击的时候注册
        ActivityEmbeddingRulesController.registerSubSettingsPairRule(getContext(),
                true /* clearTop */);

        setHighlightPreferenceKey(preference.getKey());
        return super.onPreferenceTreeClick(preference);
    }

    @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)
                .setIsSecondLayerPage(true)
                .launch();
        return true;
    }

10. registerSubSettingsPairRule:

    public static void registerSubSettingsPairRule(Context context, boolean clearTop) {
        if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) {
            return;
        }

        registerTwoPanePairRuleForSettingsHome(
                context,
                new ComponentName(context, SubSettings.class),
                null /* secondaryIntentAction */,
                clearTop);

        registerTwoPanePairRuleForSettingsHome(
                context,
                COMPONENT_NAME_WILDCARD,//通配符,所有的activity都可以
                Intent.ACTION_SAFETY_CENTER,//这里只检查action
                clearTop
        );
    }
    

    public static void registerTwoPanePairRuleForSettingsHome(Context context,
            ComponentName secondaryComponent,
            String secondaryIntentAction,
            boolean clearTop) {
        if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) {
            return;
        }

        registerTwoPanePairRuleForSettingsHome(
                context,
                secondaryComponent,
                secondaryIntentAction,
                true /* finishPrimaryWithSecondary */,
                true /* finishSecondaryWithPrimary */,
                clearTop);
    }
    
    public static void registerTwoPanePairRuleForSettingsHome(Context context,
            ComponentName secondaryComponent,
            String secondaryIntentAction,
            boolean finishPrimaryWithSecondary,
            boolean finishSecondaryWithPrimary,
            boolean clearTop) {
        if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) {
            return;
        }
    
        registerTwoPanePairRule(
                context,
                new ComponentName(context, Settings.class),
                secondaryComponent,
                secondaryIntentAction,
                finishPrimaryWithSecondary ? SplitRule.FinishBehavior.ADJACENT
                        : SplitRule.FinishBehavior.NEVER,
                finishSecondaryWithPrimary ? SplitRule.FinishBehavior.ADJACENT
                        : SplitRule.FinishBehavior.NEVER,
                clearTop);

        registerTwoPanePairRule(
                context,
                new ComponentName(context, SettingsHomepageActivity.class),
                //...

        // We should finish HomePageActivity altogether even if it shows in single pane for all deep
        // link cases.
        通过deep link 打开,副屏关闭的时候主屏也关闭,FinishBehavior.ALWAYS
        registerTwoPanePairRule(
                context,
                new ComponentName(context, DeepLinkHomepageActivity.class),
                secondaryComponent,
                secondaryIntentAction,
                finishPrimaryWithSecondary ? SplitRule.FinishBehavior.ALWAYS
                        : SplitRule.FinishBehavior.NEVER,
                finishSecondaryWithPrimary ? SplitRule.FinishBehavior.ALWAYS
                        : SplitRule.FinishBehavior.NEVER,
                clearTop);

        registerTwoPanePairRule(
                context,
                new ComponentName(context, DeepLinkHomepageActivityInternal.class),
                //...
    }