小米开发平台Activity Embedding 适配指南

小米开发平台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> {

  @Override
  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组件名
intentActionintent
splitRatio分屏比默认为0.5f,即左右5:5分屏
splitMinWidth默认配置600dp,宽度达到600dp才可以分屏主窗口可分屏显示的最小窗口宽度
splitMinSmallestWidth默认配置600dp,主窗口可分屏显示的最小sw值
ActivityFilter适配占位规则的activity(必需配置)
参数含义
ActivityRule需要全屏显示的activity,对应ActivityFilter(必需配置
alwaysExpand取值”true”或false true:启动的activity全屏显示
componentName组件名
intentActionintent
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" 
        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有可能会导致异常。

小米大屏设备适配说明4

小米大屏设备适配说明4

5.4.1 如何支持分屏

支持分屏的方式非常简单,只需要声明一个属性。从 7.0 开始,Android 新增了一个 Activity 属性: resizeableActivity ,以声明该 Activity 是否支持多窗口显示。

android:resizeableActivity=["true" | "false"]

如果这个属性设为 true ,Activity 就可以在分屏模式下显示;设为 false ,Activity 则不会在分屏模式下显示,而是会占满整个屏幕。因此开发者可以根据具体场景,仅让部分 Activity 支持分屏。

若开发者没有为 Activity 声明该属性,Android 会根据应用的 targetSDKVersion 及 Activity 的 screenOrientation 属性来综合判断是否可以在分屏显示。关于判断的详细逻辑,可以参考官方文档:Configuring Your App for Multi-Window Mode

5.4.2 如何进一步优化分屏模式

配置 resizeableActivity 的属性,是适配分屏的最简单方式,但如果想要提供更好的使用体验,需要开发者做一定优化工作。下面是我们了解到的一些案例(测试机型:Nexus 6 Andriod 7.1),开发者可以根据自己的业务需求,做不同程度的优化。

5.4.3 减少不可滑动的页面/控件

在分屏过程中,屏幕高度只有原来的一半,如果有太多的控件不响应滑动事件,那么用户将无法上下滚动应用页面,甚至无法进行下一步操作。这类页面,最常见于Splash screen、登录注册页、音乐播放页、大图区域、弹窗等。

由于用户可以自由调整分屏的窗口比例,因此开发者只要减少了不可滑动的控件,分屏的可用性就会大大提高,是性价比非常高的优化方案。

5.4.4 尽可能使用相对位置,以兼容多种窗口尺寸

分屏时,屏幕的高度和宽度会发生变化,因此在书写控件布局时,尽量使用相对位置,以避免窗口大小改变时,控件无法显示或显示不全。这也是一种性价比很高的优化方案,可以保证用户在分屏时能正常使用应用。

5.4.5注意多窗口下 Activity 的生命周期

视频、直播等类别的应用需要特别关注这一点。Android 7.0 在分屏时会同时运行两个应用,其中用户最后操作的那个应用会处于 Resumed 状态,另一个则会处于 Paused 状态。

这会带来一些问题,以视频应用为例,如果开发者在 onPaused 中处理视频的 「暂停/播放」,那在分屏时,就会因为用户操作另一个应用,导致视频停止播放。因此我们建议开发者在 onStart/onStop 中处理视频的「暂停/播放」,或者特殊处理分屏时的 Paused 状态。详见官方文档:Multi-Window Lifecycle

5.4.6处理 Configuration Changes

由于分屏过程中,允许用户调整窗口的大小,这就会导致 Configuration 的改变。Android 的默认处理方式是 relaunch 整个 Activity,从而出现页面闪一下的问题。如果想避免闪一下的问题,建议开发者自己处理 Configuration 的变化。

5.4.7给内容更多空间

分屏后,屏幕空间变得非常小了,为了给内容让出更多空间,应尽量减少常驻控件。一种解决办法是在浏览内容时,隐藏底部tab等常驻控件,用户回滚时再出现,以展示更多的内容。

5.4.8为分屏定制新的布局(动态布局)

动态布局指根据当前的窗口大小,重新调整页面的布局。这是一项锦上添花的优化项,开发者可以酌情考虑是否添加此优化。

5.4.9 MIUI 分屏支持哪些设备

搭载 Android 7.0 或以上的 MIUI 手机均支持分屏。MIUI 也将尝试将分屏移植至 Android 6.0。因此将有数千万的新老设备支持分屏。

5.4.10 分屏如何调试

MIUI 的分屏方案完全兼容 Android 7.0,因此可以在任意运行 Android 7.x 的设备上调试,无需为 MIUI 作特别处理。同时,以上提到的案例均能在任意 Android 7.x 设备复现。调试过程中,建议开启以下设置项:「开发者选项 > 强制将活动设为可调整大小」,然后重启手机,之后系统就会强制应用进入分屏模式,以方便开发者观察应用在分屏时的表现。

6、开发与测试方式

6.1 模拟器调试

1)小米折叠屏可以在Android Studio里面选择模拟折叠设备进行参照:

2)平板设备可以在Android Studio里面选择模拟平板设备进行参照:

6.2 命令模拟调试

1)可以通过如下adb命令来模拟小米MIX FOLD 2折叠屏手机切换折叠和展开的状态:

//切换到展开状态

adb shell wm size 1914×2160

//切换到折叠状态

adb shell wm size 1080×2520

2)可以通过如下adb命令来切换到平板设备的分辨率,例如:

//切换到2560×1600分辨率

adb shell wm size 1600×2560

6.3 云测平台真机测试

可以使用 小米云测平台

以折叠屏为例,点击小米 MIX Fold 2机器,进入以下页面:

右侧可以上传apk文件,左侧真机可以实时操作

6.4 测试用例建议

6.4.1 用例1

在折叠屏的展开态下打开应用,应用的界面显示正常

1.测试步骤

(1)在折叠屏展开时,打开应用,观察各个页面的显示情况;

2.预期结果

(1)应用的所有页面均显示正常,没有任何变形、截断、模糊、缺失等问题;

6.4.2 用例2

在折叠屏的折叠态下打开应用,应用的界面显示正常

1.测试步骤

(1)在折叠屏折叠时,打开应用,观察各个页面的显示情况;

2.预期结果

(1)应用的所有页面均显示正常,没有任何变形、截断、模糊、缺失等问题;

6.4.3 用例3

在折叠屏的展开态下打开应用,应用的界面功能交互正常

1.测试步骤

(1)在折叠屏展开时,打开应用,依次点击各个页面的按钮;

2.预期结果

(1)应用的所有页面按钮均可以正常响应,没有任何无反应/crash/anr等问题;

6.4.4 用例4

在折叠屏的展开态和折叠态之间切换时,应用的界面显示和功能交互均正常

1.预制条件:应用已经适配了连续性

2.测试步骤

(1)在折叠屏展开时,打开应用;

(2)切换到折叠态,观察页面显示是否正常;

(3)点击页面的各个按钮,观察功能交互是否正常;

(4)在折叠态下打开应用;

(5)切换到展开态,观察页面显示是否正常;

(6)点击页面的各个按钮,观察功能交互是否正常。

3.预期结果

(1)应用在状态切换过程中,没有任何页面闪退重启、变形、截断、模糊、缺失等问题;

(2)应用界面的所有按钮功能均交互正常。

6.4.5 用例5

在分屏模式下,应用的界面显示和功能交互均正常

1.预制条件:应用支持分屏

2.测试步骤

(1)打开应用,进入分屏模式;

(2)观察页面显示是否正常;

(3)点击页面的各个按钮,观察功能交互是否正常。

3.预期结果

(1)应用在分屏模式下,没有任何页面变形、截断、模糊、缺失等问题;

(2)应用界面的所有按钮功能均交互正常。

6.4.6 用例6

进入应用的小窗模式,应用的界面显示和功能交互均正常

1.预制条件:应用支持小窗

2.测试步骤

(1)进入应用的小窗模式;

(2)观察页面显示是否正常;

(3)依次点击各个页面的按钮,观察功能交互是否正常。

3.预期结果

(1)应用在小窗模式下,没有任何页面变形、截断、模糊、缺失等问题;

(2)应用的所有页面按钮均可以正常响应,没有任何无反应/crash/anr等问题;

OPPO推送 如何获取插入的键值对?

OPPO推送 如何获取插入的键值对?

应用在前台,在onNewIntent调用intent.getExtras()获取自定义数据。Activity为接入方应用的目标Activity,如:demo中的目标页面Activity为:

 <activity
            android:name="com.coloros.push.demo.component.InternalActivity"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="com.coloros.push.demo.internal" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

那么在配置消息的接收消息的目标Activity页面时,可以分成intent action和activity两种方式:
采用intent action的方式填写对应的:intent action:com.coloros.push.demo.internal
采用activity的方式填写类的绝对路径:activity:com.coloros.push.demo.component.InternalActivity

安卓APP如何设置activity页面节点的自定义名称


安卓APP如何设置activity页面节点的自定义名称

Q:请问下如何设置Android端的activity页面节点的自定义中文名称?

A:这种情况建议使用onPageStart、onPageEnd接口,可以传入页面名字。或者WEB端可以配置页面的映射Activity名和实际页面的映射。