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),
//...
}