小米开发平台Activity Embedding 适配指南
1、功能介绍
为了利用大屏幕的显示区域,Google在Jetpack WindowManager中添加了Activity Embedding,实现同一应用内不同activity分屏显示,开发者可以通过配置XML文件或者进行Jectpack WindowManager API调用确定如何显示activity(并排或堆叠)。
目前支持以下两种逻辑跳转,第一种是可以实现左容器界面固定:
第二种是支持多层次导航:
另外系统会自动维护对小屏幕的显示,当应用在小屏幕的设备上时,activity会相互堆叠,在大屏幕上,activity会并排显示,对于折叠屏,会随着设备折叠和展开而堆叠和并排显示activity。
2、快速上手
JetPack WindowManager库添加了ActivityEmbeddingComponent,可以根据分屏规则创建容器实现应用内分屏,配置分屏规则涉及到下面几个步骤:
2.1 添加依赖项
将WindowManager库依赖项添加到应用的build.gradle文件中
implementation("androidx.window:window:1.1.0-alpha02")
2.2 配置分屏规则
2.2.1 XML静态配置
放在res资源文件的xml文件夹下
<!-- The split configuration for activities. -->
<resources
xmlns:window="http://schemas.android.com/apk/res-auto">
<!-- Automatically split the following activity pairs. -->
<SplitPairRule
window:clearTop="true"
window:splitMinWidth="600dp">
<SplitPairFilter
window:primaryActivityName=".SplitActivityList"
window:secondaryActivityName=".*"/>
</SplitPairRule>
<!-- Automatically launch a placeholder for the list activity. -->
<SplitPlaceholderRule
window:placeholderActivityName=".SplitActivityListPlaceholder"
window:splitRatio="0.3"
window:splitMinWidth="600dp">
<ActivityFilter
window:activityName=".SplitActivityList"/>
</SplitPlaceholderRule>
<!-- Automatically launch a full activity. -->
<ActivityRule window:alwaysExpand="true">
<ActivityFilter
window:activityName=".FullActivity"/>
</ActivityRule>
</resources>
2.2.2 代码动态配置
运行时定义分屏的配置,开发者可以在startActivity或者onCreate时使用
splitController.registerRule(new SplitPairRule(newFilters));
splitController.unRegisterRule(new SplitPairRule(newFilters));
来动态添加/移除规则
protected void onCreate(@Nullable Bundle savedInstanceState) {
Set<SplitPairFilter> pairFilters = new HashSet<>();
SplitPairFilter filter = new SplitPairFilter(primaryActivityComponetName,
secondaryActivityComponetName,
null);
pairFilters.add(filter);
SplitPairRule pairRule = new SplitPairRule(pairFilters,
SplitRule.FINISH_ADJACENT,
SplitRule.FINISH_ALWAYS,
true,
600,
600,
0.3f,
LayoutDirection.LOCALE);
SplitController splitController = SplitController.getInstance();
splitController.registerRule(pairRule);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
2.3 将规则定义通知库
使用Jetpack Startup库在加载应用的其他组件和启动 activity 之前执行初始化。如需启用启动功能,请在应用的 build 文件中添加库依赖项:
implementation("androidx.startup:startup-runtime:1.1.0")
并在manifest中添加:
<!-- AndroidManifest.xml -->
<provider android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- This entry makes ExampleWindowInitializer discoverable. -->
<meta-data android:name="**androidx.window.sample.embedding.ExampleWindowInitializer**"
android:value="androidx.startup" />
</provider>
2.4 添加初始化程序类实现
通过将包含定义 (main_split_config) 的 xml 资源文件的 ID 提供给 SplitController.initialize() 来设置规则:
class ExampleWindowInitializer extends Initializer<SplitController> {
@Over网页一键生成appride
SplitController create(Context context) {
SplitController.initialize(context, R.xml.main_split_config);
return SplitController.getInstance(context);
}
@Override
List<Class<? extends Initializer<?>>> dependencies() {
return emptyList();
}
}
2.5 XML配置文件参数解析
参数 | 含义 |
SplitPairRule | 分屏配对情景(两侧容器均有具体activity),对应SplitPairFilter |
splitRatio | 分屏比默认为0.5f,即左右5:5分屏 ,对于IM类应用,可考虑设置分屏比为0.3f,即左右3:7分屏 |
splitMinWidth | 默认配置600dp,宽度达到600dp才可以分屏,主窗口可分屏显示的最小窗口宽度 |
splitMinSmallestWidth | 默认配置600dp,主窗口可分屏显示的最小sw值 |
finishPrimaryWithSecondary | 默认为 false,true:若secondary container中所有activity都finish,则primary container中创建分屏的activity也会finish,不推荐应用主动配置此项。(androidx.window:window:1.1.0-alpha03中默认是SplitRule.FINISH_ADJACENT) |
finishSecondaryWithPrimary | 默认为 ture, true:若primary container中所有activity都finish,则secondary container中所有activity也会finish,不推荐应用主动配置此项。(androidx.window:window:1.1.0-alpha03中默认是SplitRule.FINISH_ALWAYS) |
clearTop | 默认为 false,true:启动activity窗口分屏,存在相同的primary container,若新建secondary container,则原secondary container中的activity会被finish掉,推荐应用配置,避免右分屏出现多实例 |
SplitPairFilter | 分屏配对关系(必需配置) |
primaryActivityName | 分屏的primay activity component name |
secondaryActivityName | 分屏的secondary activity component name |
secondaryActivityAction | 分屏的secondary activity 启动的action (配置此项需要在启动的时候添加action) |
参数 | 含义 |
SplitPlaceholderRule | 用来描述分屏下的占位,对应ActivityFilter(必需配置) |
placeholderActivityIntentName | 通过同时打开两个activity创建分屏,secondary占位activity component name |
componentName | 组件名 |
intentAction | intent |
splitRatio | 分屏比默认为0.5f,即左右5:5分屏 |
splitMinWidth | 默认配置600dp,宽度达到600dp才可以分屏主窗口可分屏显示的最小窗口宽度 |
splitMinSmallestWidth | 默认配置600dp,主窗口可分屏显示的最小sw值 |
ActivityFilter | 适配占位规则的activity(必需配置) |
参数 | 含义 |
ActivityRule | 需要全屏显示的activity,对应ActivityFilter(必需配置) |
alwaysExpand | 取值”true”或false true:启动的activity全屏显示 |
componentName | 组件名 |
intentAction | intent |
ActivityFilter | 适配全屏规则的activity(必需配置) |
2.6 应用主动适配生效
2.6.1 MIUI meta-data标记
AndroidManifest.xml中声明下列meta-data字段。设置为true表示app已经主动适配,系统不会反向适配
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
<meta-data android:name="embedded" android:value="true"/>
</application>
注意:
Android 13后该meta-data将不使用
2.6.2 Google property标记
Google官方添加property字段,用来标识APP是否允许系统反向适配Activity Embedding
true表示允许系统反向适配,false不允许系统反向适配
注意:自适配Activity Embedding和不允许系统反向适配Activity Embedding的APP需要设置为false
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<property
android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
app打包工具 android:value="false" />
</applicatio
3、适配指导
3.1 关于应用横竖屏
3.1.1 PAD 竖屏下进入ActivityEmbedding
平板设备处于竖屏状态时,我们推荐应用采用左下图的全屏显示样式,而不是进入ActivityEmbedding。竖屏下的左右分屏显示会导致信息量过多,难以聚焦。如果您的应用在竖屏下进入了ActivityEmbedding,原因是:应用设置的进入ActivityEmbedding的阈值splitMinWidth
过小
- 获取屏幕大小
R版本之前:Display.getRealSize()、Display.getRealMetrics()。
R版本之后:WindowManager.getMaximumWindowMetrics() 。Jetpack WindowManager中支持使用WindowMetrics maximumWindowMetrics = WindowMetricsCalculator.
getOrCreate().computeMaximumWindowMetrics(activity);
- 获取当前窗口大小
R版本之前: Display.getSize() 。
R版本之后:WindowManager.getCurrentWindowMetrics() 。Jetpack WindowManager中支持使用WindowMetrics maximumWindowMetrics = WindowMetricsCalculator.
getOrCreate(). computeCurrentWindowMetrics(activity);
推荐解决方案:开发者根据不同屏幕大小的设备动态计算进入分屏的阈值。可使用如下计算方式:
// 获取屏幕大小
WindowMetrics currentWindowMetrics = WindowMetricsCalculator.getOrCreate().
computeCurrentWindowMetrics(activity);
// 获取当前窗口大小
WindowMetrics maximumWindowMetrics = WindowMetricsCalculator.getOrCreate().
computeMaximumWindowMetrics(activity);
// 通过configuration获取desity
float density = (float) activity.getResources().getConfiguration().densityDpi / 160f;
// 根据屏幕大小动态设置window:splitMinWidth="600dp"大小,控制竖屏下不进ActivtiyEmbedding
// 为了保证折叠屏外屏下不进入ActivityEmbedding,需要设置一下splitMinSmallestWidth的值,默认600dp
int splitMinWidth = (int) (Math.min(maximumWindowMetrics.getBounds().width(),
maximumWindowMetrics.getBounds().height()) / density + 1);
3.1.2 PAD无法横屏
原因:ActivityEmbedding 只是改变activity的显示位置,不会强制更改方向,需要app支持横屏
推荐方案:第一个启动的activity方向设置为behind以及全屏启动的activity设置为behind
3.1.3 折叠屏控制在外屏竖屏显示
为了避免您适配ActivityEmbedding后在折叠屏外屏出现如下图所示的情况,需要控制应用在折叠屏外屏竖屏显示,监听 WindowInfoTracker 来手动设置方向,具体使用参考官方文档https://developer.android.com/guide/topics/large-screens/make-apps-fold-aware
3.2 支持resizeable
应用需要在 AndroidManifest.xml 文件的 application 或者 actvivity 标签中添加 resizeableActivity=true 的属性。
若配置android:resizeableActivity=“false”,会导致无法分屏显示
<application
android:resizeableActivity="true">
<activity
android:resizeableActivity="true" />
</application>
3.3 关于Configuration
当应用在折叠屏内外屏或平板横竖屏交替使用时,activity的大小会发生变化,我们强烈推荐Activity在大小切换时不重启来保证良好的连续性体验,遵循google规范,在android:configChanges 属性增加 screenSize|screenLayout|orientation|smallestScreenSize,并在Activity的onConfigurationChanged回调中更新宽高刷新各个子布局。
3.4 视频全屏
分屏时请求横屏还是在分屏下显示,无法横屏全屏
建议配置activity 始终填满任务窗口:
<ActivityRule
window:alwaysExpand="true">
<ActivityFilter
window:activityName=".FullActivity"/>
</ActivityRule>
3.5 clearTop标记
若配置为true
启动activity窗口分屏,存在相同的primary container,会创建新的secondary container,原来的secondary container中的activity会被finish掉,避免右分屏出现多实例。开发者可以根据需要对单独的SplitPairRule 进行配置clearTop=”true”。
用户折叠手机时,屏幕 B 在屏幕 A 之上,屏幕 A 又在菜单之上。当用户从屏幕 B 进行返回导航时,系统会显示屏幕 A 而不是Menu。
可以将分屏配置为通过 clearTop
清除之前的辅助容器,并正常启动新的 activity。
<SplitPairRule
window:clearTop="true">
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenA"/>
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>
3.6 new Task与Launch Flag
当分屏任务窗口中的 activity 启动新任务中的 activity 时,新任务将与包含分屏的任务分开并显示在全窗口中。
在分屏任务窗口中,Launch flag同样起作用,可以复用activity。
3.7 多进程规则匹配
问题:同一Task任务下,子进程配置activity成全屏不生效
采用Jetpack Startup不指定android:process时默认在主进程初始化SplitController,在xml文件夹中的分屏规则不会在子进程生效
<!-- AndroidManifest.xml -->
<provider android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- This entry makes ExampleWindowInitializer discoverable. -->
<meta-data android:name="**androidx.window.sample.embedding.ExampleWindowInitializer**"
android:value="androidx.startup" />
</provider>
目前解决方案是需要重写Application在onCreate方法中对需要规则匹配的进程初始化SplitController并且加载静态规则。
public class MyApplication extends Application {
@Override
public void onCreate() {
SplitController.initialize(getApplicationContext(), R.xml.split_config);
super.onCreate();
}
}
3.8 响应分屏状态变化
ActivityEmbedding下如何判断activity是否处于分屏以及window mode变化,如何实时响应分屏状态变化。
3.8.1 判断是否在左右分屏
左右分屏下,activity中的configuration的mode是multi-window(与系统分屏下的mode一样,在进行部分业务处理时需要注意)
全屏状态下,activity中的configuration的mode是fullscreen
判断当前activity是否在左右分屏状态,可以通过SplitController.getInstance().isActivityEmbedded(Activity activity)进行区分,返回true表示处于分屏。
3.8.2 如何响应分屏状态变化
为了知道 activity 何时在分屏中,可以向SplitController注册一个监听器来监听分屏状态的变化。然后,相应地调整界面:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
splitController
.addSplitListener(this, mainThreadExecutor, SplitInfoChangeCallback());
}
class SplitInfoChangeCallback extends Consumer<List<SplitInfo>> {
public void accept(List<SplitInfo> splitInfoList) {
findViewById<View>(R.id.infoButton).visibility =
!splitInfoList.isEmpty()) ? View.GONE : View.VISIBLE;
}
}
可以在任何生命周期状态下进行回调,包括当 activity 停止时。通常应在onStart()中注册监听器,在onStop()中取消注册监听器。
3.9 占位Activity Placeholder
如需创建带有占位符的分屏,请创建一个占位符并将其与主要 activity 相关联:
<SplitPlaceholderRule
window:placeholderIntentName=".Placeholder">
<ActivityFilter
window:activityName=".Main"/>
</SplitPlaceholderRule>
注意:开发者不需要自己启动placeholder,会创建多个TaskFragment导致启动异常,在真正显示主页面时建议不启动placeholder,可以动态注册规则,比如在进入主界面前先跳转到登录界面,如果启动了placeholder有可能会导致异常。
编辑:yimen,如若转载,请注明出处:https://www.yimenapp.com/kb-yimen/12658/
部分内容来自网络投稿,如有侵权联系立删