在兩個活動 (或同一活動的兩個例項) 之間,活動嵌入功能會分割應用程式的工作視窗,藉此在大螢幕裝置上將應用程式最佳化。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/settings_app.png?authuser=9&hl=zh-tw)
如果您的應用程式含有多項活動,活動嵌入功能可協助您在平板電腦、摺疊式裝置和 ChromeOS 裝置上提供更優質的使用者體驗。
使用活動嵌入功能無需重構程式碼。您可以建立 XML 設定檔或發出 Jetpack WindowManager API 呼叫,決定要以並排還是堆疊的方式顯示應用程式活動。
系統會自動持續為小螢幕提供支援。如果應用程式是在螢幕較小的裝置上,活動會疊放顯示。如果是大螢幕,活動會並列顯示。系統會根據您建立的設定來決定呈現方式,不需要用到分支版本邏輯。
活動嵌入功能可支援裝置螢幕方向的相關變更,而且可在摺疊式裝置上順暢運作,隨著裝置是處於摺疊還是展開狀態來堆疊及拆開活動。
搭載 Android 12L (API 級別 32) 以上版本的大螢幕裝置大多都支援活動嵌入功能。
分割工作視窗
活動嵌入會將應用程式工作視窗分割為兩個容器:主要容器與次要容器。除了保有從主要活動中啟動的活動以外,這些容器也會保有從容器現有的其他活動中啟動的活動。
啟動後,活動會堆疊在次要容器中,次要容器則堆放在螢幕較小的主要容器上方,因此活動堆疊和返回瀏覽會與已內建在應用程式中的活動順序保持一致。
活動嵌入功能可讓您以多種方式顯示活動。您的應用程式可以同時啟動兩個並排顯示的活動以分割工作視窗:
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/a_b.png?authuser=9&hl=zh-tw)
或者,如果活動會占用整個工作視窗,您可以在側邊啟動新活動,替原活動建立分割畫面:
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/a_to_a_b.png?authuser=9&hl=zh-tw)
已分割及共用工作視窗中的活動,可以透過以下幾種方式啟動其他活動:
在側邊的其他活動之上:
圖 4 活動 A 在側邊的活動 B 上方啟動活動 C。 在側邊啟動一個活動,並使分割視窗側移,以隱藏先前的主要活動:
圖 5 活動 B 在側邊啟動活動 C,並將分割視窗側移。 從原活動上方啟動活動;也就是兩個活動位在相同的活動堆疊中:
圖 6 活動 B 啟動了活動 C,並且沒有額外的意圖旗標。 在同一個工作中以全視窗模式啟動活動:
圖 7.活動 A 或活動 B 啟動了活動 C,而 C 會填滿整個工作視窗。
返回瀏覽
不同類型的應用程式可以在分割工作視窗狀態中加入不同的返回瀏覽規則,具體視活動之間的相依性或使用者觸發返回事件的方式而定,例如:
- 一起執行:如果活動之間具有關聯性,且不應單獨顯示,則可以將返回瀏覽設定為完成這兩個活動。
- 單獨執行:如果活動完全獨立,則一個活動的返回瀏覽不會影響工作視窗中另一個活動的狀態。
使用按鈕瀏覽時,系統會將返回事件傳送至上一個聚焦的活動。若是以手勢進行的瀏覽,系統會將返回事件傳送至手勢發生時的活動。
多窗格版面配置
無論您使用的是搭載 Android 12L (API 級別 32) 以上版本的大螢幕裝置,還是某些搭載舊版平台的裝置,Jetpack WindowManager 都可讓您在裝置上建構具備活動嵌入功能的多窗格版面配置。現有以多個活動為基礎的應用程式 (而非使用片段或檢視畫面的版面配置,例如:SlidingPaneLayout
) 可改善大螢幕的使用者體驗,而無需重構原始碼。
常見的例子是清單/詳細資料分割畫面。為了確保能以高品質呈現畫面,系統會啟動清單活動,接著應用程式會立刻啟動詳細資料活動。等到兩個活動都繪製完成後,轉換系統會一併顯示兩者。對使用者而言,這兩個活動會做為一個活動啟動。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/launcher_to_list-detail.png?authuser=9&hl=zh-tw)
分割屬性
您可以指定工作視窗在分割容器間的比例,以及容器彼此間的版面配置方式。
針對 XML 設定檔中定義的規則,請設定下列屬性:
splitRatio
:設定容器比例。這個值是開區間內 (0.0、1.0) 的浮點數。splitLayoutDirection
:指定分割容器彼此間的版面配置方式。相關的值包括:ltr
:從左到右rtl
:從右到左locale
:ltr
或rtl
,取決於語言代碼設定
如需示例,請參閱下方的「XML 設定」。
針對使用 WindowManager API 建立的規則,請以 SplitAttributes.Builder
建立 SplitAttributes
物件,並呼叫下列建構工具方法:
setSplitType()
:設定分割容器的比例。如需涵蓋SplitAttributes.SplitType.ratio()
方法在內的有效引數,請參閱SplitAttributes.SplitType
。setLayoutDirection()
:設定容器的版面配置。如要查看可能的值,請參閱SplitAttributes.LayoutDirection
。
如需示例,請參閱下方的「WindowManager API」。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/a_b_vs_a_b.png?authuser=9&hl=zh-tw)
預留位置
預留位置活動是空白的次級活動,會占用活動分割區域。它們最終將被替換為含有內容的其他活動。舉例來說,在清單/詳細資料的版面配置中,預留位置活動可能會占用活動分割中次級的一側,直到選取了清單中的一個項目,具有該選取清單項目內容的活動將該預留位置取代為止。
根據預設,只有在空間足以進行活動分割時,系統才會顯示預留位置。當顯示畫面尺寸改變,導致寬度或高度過小而無法顯示活動分割時,預留位置就會自動結束。當空間足夠時,系統會重新啟動預留位置 (狀態則重新初始化)。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/placeholder_finished_recreated.png?authuser=9&hl=zh-tw)
不過,SplitPlaceholderRule
的 stickyPlaceholder
屬性或 SplitPlaceholder.Builder
的 setSticky()
方法可能會覆寫預設行為。當屬性或方法指定 true
的值時,如果顯示畫面從雙窗格縮小為單窗格顯示畫面,系統會在工作視窗中將預留位置顯示為最上方活動 (如需示例,請參閱「分割設定」)。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/placeholder_sticky.png?authuser=9&hl=zh-tw)
視窗大小變化
如果裝置設定變更會縮減工作視窗寬度,以致於大小不足以顯示多窗格版面配置 (例如大螢幕摺疊式裝置從平板電腦尺寸摺疊成手機尺寸,或者應用程式視窗在多視窗模式下重新調整大小),工作視窗中次要窗格內的非預留位置活動會堆疊在主要窗格的活動上方。
只有在分割畫面的顯示寬度足夠時,才會顯示預留位置活動。在較小的螢幕上,預留位置會自動關閉。當顯示區再次變得夠大時,系統會重新建立預留位置。(請參閱上方的預留位置)。
而活動堆疊是可能的,因為 WiWindowManager 會以 Z 順序的方式排序位於主要窗格中活動上方的次要窗格活動。
輔助窗格中的多個活動
活動 B 啟動了活動 C,其中沒有額外的意圖旗標:
結果導致同一工作中的活動會按照以下 Z 順序排序:
因此,在較小的工作視窗中,應用程式會縮減為單一活動,堆疊頂端是活動 C:
在較小視窗中返回瀏覽可以瀏覽堆疊在彼此之上的活動。
如果工作視窗設定還原到較大尺寸,能夠容納多個窗格,活動就會再次並排顯示。
堆疊分割
活動 B 則會在側邊啟動活動 C,並將分割移至兩側:
結果導致同一工作中的活動會按照以下 Z 順序排序:
在較小的工作視窗中,應用程式會縮減為單一活動,上方顯示 C:
固定直向螢幕方向
android:screenOrientation 資訊清單設定可讓應用程式將活動限制為直向或橫向螢幕方向。如要提升在平板電腦和摺疊式裝置等大螢幕裝置上的使用者體驗,裝置製造商 (OEM) 可以忽略螢幕方向要求,並在橫向顯示畫面的直向螢幕方向,或在直向顯示畫面的橫向螢幕方向中,讓應用程式出現上下黑邊。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/a_letterboxed_portrait_and_landscape.png?authuser=9&hl=zh-tw)
同樣地,啟用活動嵌入時,原始設備製造商 (OEM) 便可針對大螢幕 (寬度 ≥ 600dp) 的橫向螢幕方向自訂裝置的上下黑邊固定直向活動。固定直向活動啟動第二個活動時,裝置可以在雙窗格顯示畫面中並排顯示兩個活動。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/a_letterboxed_portrait_to_a_b.png?authuser=9&hl=zh-tw)
請務必在應用程式資訊清單檔案中新增 android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
屬性,以告知裝置您的應用程式支援活動嵌入 (請參閱以下的分割設定)。原始設備製造商 (OEM) 客製化裝置即可判斷是否要讓固定直向活動出現上下黑邊。
分割設定
活動分割是由分割規則所設定。您可以建立 XML 設定檔或發出 Jetpack WindowManager API 呼叫,定義分割規則。
無論採取哪種做法,您的應用程式都必須存取 WindowManager 程式庫,並向系統說明該應用程式已實作活動嵌入功能。
請完成下列步驟:
將最新的 WindowManager 程式庫依附元件新增至應用程式模組層級的
build.gradle
檔案,例如:implementation 'androidx.window:window:1.1.0-beta02'
WindowManager 程式庫提供了活動嵌入功能所需的所有元件。
向系統說明您的應用程式已實作活動嵌入功能。
在應用程式資訊清單檔案的 <application> 元素中新增
android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
屬性,然後將值設為 true,例如:<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <property android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED" android:value="true" /> </application> </manifest>
在 WindowManager 1.1.0-alpha06 以上版本中,如果這個屬性未加入資訊清單並設為 true,活動嵌入功能的分割作業就會停用。
此外,裝置製造商也會利用這項設定,為支援活動嵌入功能的應用程式啟用自訂功能。舉例來說,如果螢幕處於橫向模式,裝置可以在僅限直向顯示的活動兩側加上黑邊,並在第二項活動開始時調整第一項活動的位置,轉換為雙窗格的版面配置 (請參閱「固定直向螢幕方向」)。
XML 設定
如要建立以 XML 為依據的活動嵌入功能實作,請完成下列步驟:
建立可執行下列操作的 XML 資源檔案:
- 定義共用分割作業的活動
- 設定分割選項
- 在沒有可用內容時,為分割作業的次要容器建立預留位置
- 指定一律不得納入分割作業的活動
例如:
<!-- main_split_config.xml --> <resources xmlns:window="http://schemas.android.com/apk/res-auto"> <!-- Define a split for the named activities. --> <SplitPairRule window:splitRatio="0.33" window:splitLayoutDirection="locale" window:splitMinWidthDp="840" window:splitMaxAspectRatioInPortrait="alwaysAllow" window:finishPrimaryWithSecondary="never" window:finishSecondaryWithPrimary="always" window:clearTop="false"> <SplitPairFilter window:primaryActivityName=".ListActivity" window:secondaryActivityName=".DetailActivity"/> </SplitPairRule> <!-- Specify a placeholder for the secondary container when content is not available. --> <SplitPlaceholderRule window:placeholderActivityName=".PlaceholderActivity" window:splitRatio="0.33" window:splitLayoutDirection="locale" window:splitMinWidthDp="840" window:splitMaxAspectRatioInPortrait="alwaysAllow" window:stickyPlaceholder="false"> <ActivityFilter window:activityName=".ListActivity"/> </SplitPlaceholderRule> <!-- Define activities that should never be part of a split. Note: Takes precedence over other split rules for the activity named in the rule. --> <ActivityRule window:alwaysExpand="true"> <ActivityFilter window:activityName=".ExpandedActivity"/> </ActivityRule> </resources>
建立 Initializer。
WindowManager 的
RuleController
元件會剖析 XML 設定檔,為系統提供規則。Jetpack Startup 程式庫Initializer
會在應用程式啟動時為RuleController
提供 XML 檔案,讓規則可在任一活動開始時生效。如要建立 Initializer,請執行下列步驟:
將最新的 Jetpack Startup 程式庫依附元件新增至模組層級的
build.gradle
檔案,例如:implementation 'androidx.startup:startup-runtime:1.1.1'
建立實作
Initializer
介面的類別。Initializer 會將 XML 設定檔 (
main_split_config.xml
) 的 ID 傳遞給RuleController.parseRules()
方法,進而向RuleController
提供分割規則。Kotlin
class SplitInitializer : Initializer<RuleController> { override fun create(context: Context): RuleController { return RuleController.getInstance(context).apply { setRules(RuleController.parseRules(context, R.xml.main_split_config)) } } override fun dependencies(): List<Class<out Initializer<*>>> { return emptyList() } }
Java
public class SplitInitializer implements Initializer<RuleController> { @NonNull @Override public RuleController create(@NonNull Context context) { RuleController ruleController = RuleController.getInstance(context); ruleController.setRules( RuleController.parseRules(context, R.xml.main_split_config) ); return ruleController; } @NonNull @Override public List<Class<? extends Initializer<?>>> dependencies() { return Collections.emptyList(); } }
建立規則定義的內容供應器。
在應用程式資訊清單檔案中,新增
androidx.startup.InitializationProvider
做為<provider>
。加入RuleController
初始化工具實作的參照,SplitInitializer
:<!-- AndroidManifest.xml --> <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <!-- Make SplitInitializer discoverable by InitializationProvider. --> <meta-data android:name="${applicationId}.SplitInitializer" android:value="androidx.startup" /> </provider>
在呼叫應用程式的
onCreate()
方法之前,InitializationProvider
會探索並初始化SplitInitializer
。因此,應用程式的主要活動開始時,分割規則就會生效。
WindowManager API
您可以發出幾個 API 呼叫,透過程式輔助的方式實作活動嵌入功能。請在 Application
子類別的 onCreate()
方法內發出呼叫,確保規則在任何活動啟動前生效。
如要透過程式輔助的方式建立活動分割作業,請完成下列步驟:
建立分割規則:
建立
SplitPairFilter
,用於識別共用分割作業的活動:Kotlin
val splitPairFilter = SplitPairFilter( ComponentName(this, ListActivity::class.java), ComponentName(this, DetailActivity::class.java), null )
Java
SplitPairFilter splitPairFilter = new SplitPairFilter( new ComponentName(this, ListActivity.class), new ComponentName(this, DetailActivity.class), null );
在篩選器組合中新增篩選器:
Kotlin
val filterSet = setOf(splitPairFilter)
Java
Set<SplitPairFilter> filterSet = new HashSet<>(); filterSet.add(splitPairFilter);
建立分割畫面的版面配置屬性:
Kotlin
val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build()
Java
final SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build();
SplitAttributes.Builder
會建立一個包含版面配置屬性的物件:setSplitType
:定義可用的顯示區域如何分配至各個活動容器。比率分割類型會指定分配給主要容器的可用顯示區域比例;次要容器會占用可用顯示區域的剩餘部分。setLayoutDirection
:指定活動容器彼此間的版面配置方式,主要容器優先。
建構
SplitPairRule
:Kotlin
val splitPairRule = SplitPairRule.Builder(filterSet) .setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER) .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS) .setClearTop(false) .build()
Java
SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet) .setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER) .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS) .setClearTop(false) .build();
SplitPairRule.Builder
會建立並設定規則:filterSet
:包含分割畫面組的篩選器,可識別共用分割畫面的活動,決定套用規則的時機。setDefaultSplitAttributes
:將版面配置屬性套用至規則。setMinWidthDp
:設定會啟用分割作業的最小螢幕寬度 (以密度獨立像素 dp 為單位)。setMinSmallestWidthDp
:設定無論裝置螢幕方向為何,只要兩個顯示畫面尺寸中較小者達到這個下限 (以 dp 為單位),就必須啟用分割作業。setMaxAspectRatioInPortrait
:設定顯示活動分割畫面的直向顯示比例上限 (高度:寬度)。如果直向的顯示比例超過顯示比例上限,那麼無論螢幕寬度為何,系統都會停用分割作業。注意:預設值為 1.4,因此活動會占據大多數平板電腦的整個直向工作視窗。另請參閱SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
和setMaxAspectRatioInLandscape
。橫向的預設值是ALWAYS_ALLOW
。setFinishPrimaryWithSecondary
:設定若完成次要容器中的所有活動,對主要容器中的活動有何影響。NEVER
表示在次要容器中的所有活動完成時,系統應不會完成主要活動 (請參閱「完成活動」)。setFinishSecondaryWithPrimary
:設定若完成主要容器中的所有活動,對次要容器中的活動有何影響。ALWAYS
表示在主要容器中的所有活動完成時,系統應一律完成次要容器中的活動 (請參閱「完成活動」)。setClearTop
:指定在次要容器中啟動新活動時,是否會完成該容器中的所有活動。如設為 False,系統會指定新活動堆疊在次要容器中的現有活動之上。
取得 WindowManager
RuleController
的單例模式例項,然後新增規則:Kotlin
val ruleController = RuleController.getInstance(this) ruleController.addRule(splitPairRule)
Java
RuleController ruleController = RuleController.getInstance(this); ruleController.addRule(splitPairRule);
在沒有可用內容時,為次要容器建立預留位置:
建立
ActivityFilter
,用於識別活動的哪個預留位置共用工作視窗分割作業:Kotlin
val placeholderActivityFilter = ActivityFilter( ComponentName(this, ListActivity::class.java), null )
Java
ActivityFilter placeholderActivityFilter = new ActivityFilter( new ComponentName(this, ListActivity.class), null );
在篩選器組合中新增篩選器:
Kotlin
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
Java
Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>(); placeholderActivityFilterSet.add(placeholderActivityFilter);
-
Kotlin
val splitPlaceholderRule = SplitPlaceholderRule.Builder( placeholderActivityFilterSet, Intent(context, PlaceholderActivity::class.java) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build()
Java
SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder( placeholderActivityFilterSet, new Intent(context, PlaceholderActivity.class) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build();
SplitPlaceholderRule.Builder
會建立並設定規則:placeholderActivityFilterSet
:包含活動篩選器,可識別與預留位置活動相關聯的活動,決定套用規則的時機。Intent
:指定預留位置活動的啟動作業。setDefaultSplitAttributes
:將版面配置屬性套用至規則。setMinWidthDp
:設定可允許分割作業的最小螢幕寬度 (以密度獨立像素 dp 為單位)。setMinSmallestWidthDp
:設定無論裝置螢幕方向為何,只要兩個顯示畫面尺寸中較小者達到這個下限 (以 dp 為單位),就必須允許分割作業。setMaxAspectRatioInPortrait
:設定顯示活動分割畫面的直向顯示比例上限 (高度:寬度)。注意:預設值為 1.4,因此活動會填滿大多數平板電腦的直向工作視窗。另請參閱SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
和setMaxAspectRatioInLandscape
。橫向的預設值是ALWAYS_ALLOW
。setFinishPrimaryWithPlaceholder
:設定若完成預留位置活動,對主要容器中的活動有何影響。ALWAYS 表示在預留位置完成時,系統應一律完成主要容器中的活動 (請參閱「完成活動」)。setSticky
:決定一旦預留位置首次出現在寬度下限充足的分割畫面時,預留位置活動是否要顯示在小螢幕上的活動堆疊頂端。
將規則新增至 WindowManager
RuleController
:Kotlin
ruleController.addRule(splitPlaceholderRule)
Java
ruleController.addRule(splitPlaceholderRule);
指定一律不得納入分割作業的活動:
建立
ActivityFilter
,用於識別應一律占用整個工作顯示區域的活動:Kotlin
val expandedActivityFilter = ActivityFilter( ComponentName(this, ExpandedActivity::class.java), null )
Java
ActivityFilter expandedActivityFilter = new ActivityFilter( new ComponentName(this, ExpandedActivity.class), null );
在篩選器組合中新增篩選器:
Kotlin
val expandedActivityFilterSet = setOf(expandedActivityFilter)
Java
Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>(); expandedActivityFilterSet.add(expandedActivityFilter);
建立
ActivityRule
:Kotlin
val activityRule = ActivityRule.Builder(expandedActivityFilterSet) .setAlwaysExpand(true) .build()
Java
ActivityRule activityRule = new ActivityRule.Builder( expandedActivityFilterSet ).setAlwaysExpand(true) .build();
ActivityRule.Builder
會建立並設定規則:expandedActivityFilterSet
:包含活動篩選器,可識別要從分割作業中排除的活動,決定套用規則的時機。setAlwaysExpand
:指定活動是否應填滿整個工作視窗。
將規則新增至 WindowManager
RuleController
:Kotlin
ruleController.addRule(activityRule)
Java
ruleController.addRule(activityRule);
跨應用程式嵌入
在 Android 13 (API 級別 33) 以上版本中,應用程式可以嵌入其他應用程式的活動。跨應用程式 (或跨 UID) 活動嵌入功能會在視覺上整合多個 Android 應用程式的活動。系統會在螢幕上並排或上下顯示主機應用程式的活動與其他應用程式的嵌入活動,就像單一應用程式活動嵌入。
舉例來說,「設定」應用程式可嵌入 WallpaperPicker 應用程式的桌布選取器活動:
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/wallpaper_activity_embedded_in_settings_app.png?authuser=9&hl=zh-tw)
信任模式
嵌入其他應用程式活動的主機程序能夠重新定義嵌入活動的呈現方式,包括尺寸、位置、裁剪和透明度。惡意主機可以使用這項功能誤導使用者,並發起點閱綁架或其他 UI 阻礙攻擊。
如要防止濫用跨應用程式活動嵌入功能,Android 要求應用程式選擇允許嵌入其活動。應用程式可以將主機指定為信任的或不信任的主機。
信任的託管
如要允許其他應用程式嵌入並完整控制您應用程式的活動呈現方式,請在應用程式的資訊清單檔案 <activity>
或 <application>
元素的 android:knownActivityEmbeddingCerts
屬性中指定代管應用程式的 SHA-256 憑證。
將 android:knownActivityEmbeddingCerts
的值設為字串:
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
... />
或如要指定多個憑證,則可使用字串陣列:
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
... />
參照如下所示的資源:
<resources>
<string-array name="known_host_certificate_digests">
<item>cert1</item>
<item>cert2</item>
...
</string-array>
</resources>
應用程式擁有者可以執行 Gradle signingReport
工作,以取得 SHA 憑證摘要。憑證摘要是 SHA-256 指紋,沒有冒號分隔。詳情請參閱「執行簽署報告」和「驗證用戶端」。
不信任的主機
如要允許任何應用程式嵌入您應用程式的活動並控制其呈現方式,請在應用程式資訊清單的 <activity>
或 <application>
元素中指定 android:allowUntrustedActivityEmbedding
屬性,例如:
<activity
android:name=".MyEmbeddableActivity"
android:allowUntrustedActivityEmbedding="true"
... />
該屬性的預設值為 false,藉此防止跨應用程式活動嵌入。
自訂驗證
為了減輕嵌入不信任活動嵌入的風險,請建立自訂驗證機制來驗證主機身分。如果您知道主機憑證,請使用 androidx.security.app.authenticator
程式庫進行驗證。如果主機在嵌入您的活動後進行驗證,即可顯示實際內容。如果不是這樣,您可以通知使用者不允許該動作,並封鎖內容。
使用 Jetpack WindowManager 程式庫中的 ActivityEmbeddingController#isActivityEmbedded()
方法,檢查主機是否正在嵌入您的活動,例如:
Kotlin
fun isActivityEmbedded(activity: Activity): Boolean { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity) }
Java
boolean isActivityEmbedded(Activity activity) { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity); }
最小尺寸限制
Android 系統會將在應用程式資訊清單 <layout>
元素中指定的最小高度和寬度,套用至嵌入活動。如果應用程式未指定最小高度和寬度,系統會套用系統預設值 (sw220dp
)。
如果主機嘗試將嵌入容器的大小調整為低於最小值的尺寸,嵌入容器則會展開,占用整個工作界限。
<activity-alias>
如要搭配 <activity-alias>
元素使用信任或不信任的活動嵌入,android:knownActivityEmbeddingCerts
或 android:allowUntrustedActivityEmbedding
必須套用至目標活動 (而非別名)。驗證系統伺服器上安全性的政策是根據在目標上設定的旗標,而非別名。
託管應用程式
主機應用程式實作跨應用程式活動的方式,就跟實作單一應用程式活動嵌入的方式相同。SplitPairRule
和 SplitPairFilter
或 ActivityRule
和 ActivityFilter
物件指定內嵌活動和工作視窗分割 分割規則是 在 XML 中靜態靜態定義,或在執行階段使用 Jetpack WindowManager API 呼叫定義。
如果主機應用程式嘗試嵌入尚未選擇加入跨應用程式嵌入的活動,該活動則會占用整個工作界限。因此,主機應用程式必須知道目標活動是否允許跨應用程式嵌入。
如果嵌入活動在同一工作中啟動新活動,而新活動尚未選擇加入跨應用程式嵌入,該活動則會占用整個工作界限,而非重疊嵌入容器中的活動。
只要活動在同一工作中啟動,代管應用程式就可以不受限制地嵌入自己的活動。
分割範例
從整個視窗分割
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/a_to_a_b.png?authuser=9&hl=zh-tw)
不需重構。您可以在靜態時或在執行階段定義分割的設定,然後呼叫 Context#startActivity()
,且不需任何其他參數。
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
預設分割
如果應用程式的到達網頁設計為在大型螢幕上分割為兩個容器,則同時建立並顯示兩個活動便會給使用者帶來最佳體驗。不過,除非使用者與主要容器中的活動互動 (例如使用者從導覽選單中選取項目),否則分割視窗的次要容器可能無法提供內容。預留位置活動可以填充空白,直到內容可以顯示在分割的次要容器中(請參閱上文「預留位置」)。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/launcher_main_placeholder.png?authuser=9&hl=zh-tw)
如要建立含有預留位置的分割,請建立預留位置,並將其與主要活動建立關聯:
<SplitPlaceholderRule
window:placeholderActivityName=".PlaceholderActivity">
<ActivityFilter
window:activityName=".MainActivity"/>
</SplitPlaceholderRule>
深層連結分割
當應用程式收到意圖時,目標活動可能會顯示為活動分割視窗的次要部分;例如,要求顯示包含某個清單項目資訊的詳細資料畫面。在小螢幕上,詳細資料會顯示在完整工作視窗中;在較大裝置上,會顯示在清單一側。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/deep_link_split.png?authuser=9&hl=zh-tw)
啟動要求應轉送至主要活動,而目標詳細資料活動則應在分割視窗中啟動。系統會根據可用的螢幕寬度,自動選擇正確的呈現方式 (堆疊或並排)。
Kotlin
override fun onCreate(savedInstanceState Bundle?) { . . . RuleController.getInstance(this) .addRule(SplitPairRule.Builder(filterSet).build()) startActivity(Intent(this, DetailActivity::class.java)) }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { . . . RuleController.getInstance(this) .addRule(new SplitPairRule.Builder(filterSet).build()); startActivity(new Intent(this, DetailActivity.class)); }
深層連結目的地有可能會是使用者在返回瀏覽堆疊中可用的唯一活動,因此建議您避免關閉詳細活動,只留下主要活動:
您可使用 finishPrimaryWithSecondary
屬性同時完成這兩項活動:
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".ListActivity"
window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>
請參閱下方的設定屬性。
分割容器中的多項活動
在分割容器中堆疊多個活動,可讓使用者存取深度學習內容。舉例來說,使用清單/詳細資料分割畫面時,使用者可能需要前往子詳細資料,但主要活動保持不變:
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/list-detail_to_list-sub-detail.png?authuser=9&hl=zh-tw)
Kotlin
class DetailActivity { . . . fun onOpenSubDetail() { startActivity(Intent(this, SubDetailActivity::class.java)) } }
Java
public class DetailActivity { . . . void onOpenSubDetail() { startActivity(new Intent(this, SubDetailActivity.class)); } }
子細項活動放在詳細資料活動之上,將其隱藏:
接著,使用者只要依堆疊返回瀏覽,即可回到之前的詳細資料層級:
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/list-sub-detail_to_list-detail.png?authuser=9&hl=zh-tw)
根據預設,從同一個次要容器中啟動的活動會彼此堆疊。 在有效的分割視窗中,從主要容器啟動的活動,最終也會落在活動堆疊頂部的次要容器中。
新工作中的活動
當分割工作視窗中的活動在新工作中啟動活動時,新工作會與包含分割視窗的工作分離,並以完整的視窗顯示。近期存取的畫面會顯示兩項工作:分割中的工作和新工作。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/task_1_to_task_2.png?authuser=9&hl=zh-tw)
替換活動
您可以替換次要容器堆疊中的活動;舉例來說,如果主要活動用於頂層導覽,則次要活動就用於所選的目的地。從頂層導覽所做的每個選項,都必須在次要容器中啟動新活動,並移除之前在該位置的活動。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/menu_screen_a_to_menu_screen_b.png?authuser=9&hl=zh-tw)
如果導覽選項變更時應用程式未完成次要容器中的活動,則在收合分割視窗時 (當裝置收合時),返回瀏覽可能會令人感到困惑。舉例來說,如果主窗格有一個選單,而次要窗格中堆疊了螢幕 A 和 B,則當使用者摺疊手機時,B 位於 A 頂部,而 A 則位於選單頂部。當使用者從 B 返回瀏覽時,系統會顯示 A,而非選單。
這種情況下,就必須從返回堆疊中移除螢幕 A。
根據預設,在現有分割視窗上方的新容器側邊啟動時,系統會將新的次要容器置於上方,並在返回堆疊中保留舊容器。您可以設定分割,利用 clearTop
清除之前的次要容器,並照常啟動新活動。
<SplitPairRule
window:clearTop="true">
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenA"/>
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>
Kotlin
class MenuActivity { . . . fun onMenuItemSelected(selectedMenuItem: Int) { startActivity(Intent(this, classForItem(selectedMenuItem))) } }
Java
public class MenuActivity { . . . void onMenuItemSelected(int selectedMenuItem) { startActivity(new Intent(this, classForItem(selectedMenuItem))); } }
或者,您也可以使用相同的次要活動,從主要 (選單) 活動中傳送新意圖,這些意圖會解析為同一個例項,但會在次要容器中觸發狀態或 UI 更新。
多個分割
應用程式可以在側邊啟動其他活動,提供多層級的深度瀏覽功能。
當次要容器中的活動在側邊啟動新活動時,系統會在現有分割之上建立新的分割。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/a_b_to_b_c.png?authuser=9&hl=zh-tw)
返回堆疊包含了之前開啟的所有活動,因此使用者可以在完成 C 後瀏覽 A/B 分割畫面。
如要建立新的分割,請從現有的次要容器中在側邊啟動新活動。宣告 A/B 和 B/C 分割的設定,再從 B 正常啟動活動 C:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
<SplitPairFilter
window:primaryActivityName=".B"
window:secondaryActivityName=".C"/>
</SplitPairRule>
Kotlin
class B { . . . fun onOpenC() { startActivity(Intent(this, C::class.java)) } }
Java
public class B { . . . void onOpenC() { startActivity(new Intent(this, C.class)); } }
回應分割狀態變更
應用程式中的不同活動可能會有執行相同功能的 UI 元素,例如開啟一個具有帳戶設定的視窗控制項。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/main_detail_with_ui_element.png?authuser=9&hl=zh-tw)
如果分割的兩個活動含有共同的 UI 元素,那麼在兩個活動中呈現元素,可能會顯得多餘且可能造成混淆。
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/main_detail_with_ui_element_duplicated.png?authuser=9&hl=zh-tw)
如要瞭解活動是否位於分割畫面,請查看 SplitController.splitInfoList
流程,或透過 SplitControllerCallbackAdapter
註冊事件監聽器來監聽分割狀態的變化。然後,請據此調整 UI:
Kotlin
val layout = layoutInflater.inflate(R.layout.activity_main, null) val view = layout.findViewById<View>(R.id.infoButton) lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance. .collect { list -> view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE } } }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { . . . new SplitControllerCallbackAdapter(SplitController.getInstance(this)) .addSplitListener( this, Runnable::run, splitInfoList -> { View layout = getLayoutInflater().inflate(R.layout.activity_main, null); layout.findViewById(R.id.infoButton).setVisibility( splitInfoList.isEmpty() ? View.VISIBLE : View.GONE); }); }
協同程式可在任何生命週期狀態下啟動,但為了節省資源,一般會在 STARTED
狀態下啟動 (詳情請參閱「將 Kotlin 協同程式與生命週期感知元件搭配使用」)。
回呼可針對所有生命週期狀態進行,包括在活動停止時。事件監聽器應在 onStart()
中註冊,並在 onStop()
中取消註冊。
完整視窗互動畫面
有些活動會阻止使用者與應用程式互動,直到執行指定的操作為止;例如登入的螢幕活動、政策確認畫面或錯誤訊息。分割畫面中應防止出現模式活動。
您可以使用展開的設定,強制將活動一律填入工作視窗:
<ActivityRule
window:alwaysExpand="true">
<ActivityFilter
window:activityName=".FullWidthActivity"/>
</ActivityRule>
完成活動
使用者可以從螢幕邊緣滑動,完成任一側分割畫面的活動:
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/swipe_gesture_finish_b.png?authuser=9&hl=zh-tw)
![](https://developer.android.com/static/images/guide/topics/large-screens/activity-embedding/swipe_gesture_finish_a.png?authuser=9&hl=zh-tw)
如果將裝置設為使用返回按鈕,而非手勢操作,系統會將輸入內容傳送至焦點活動,也就是上次觸碰或啟動的活動。
完成容器中的所有活動會對相對的容器造成什麼影響,須視分割設定而定。
設定屬性
您可以指定分割配對規則屬性,以設定在分割畫面中一邊完成所有活動如何影響另一邊的活動。屬性包括:
window:finishPrimaryWithSecondary
:完成次要容器中的所有活動對主要容器中的活動有何影響window:finishSecondaryWithPrimary
:完成主要容器中的所有活動對次要容器中的活動有何影響
屬性可能的值包括:
always
:一律完成關聯容器中的活動never
:絕不完成關聯容器中的活動adjacent
:當兩個容器相鄰顯示時,完成相關聯容器中的活動,但不在兩個容器堆疊顯示時完成。
例如:
<SplitPairRule
<!-- Do not finish primary container activities when all secondary container activities finish. -->
window:finishPrimaryWithSecondary="never"
<!-- Finish secondary container activities when all primary container activities finish. -->
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
預設設定
在分割作業中,如果單一容器內的所有活動皆完成後,剩餘的容器會占滿整個視窗:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
一起完成多項活動
次要容器中的所有活動完成後,自動完成主要容器中的活動:
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
主要容器中的所有活動完成後,自動完成次要容器中的活動:
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
主要或次要容器中的所有活動完成後,一起完成多項活動:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
在容器中完成多項活動
如果分割容器中有多個活動堆疊,完成堆疊底部的活動並不會自動完成上方的活動。
舉例來說,如果有兩個活動位於次要容器中,C 位於 B 之上:
而分割的設定是依照活動 A 和 B 的設定所定義:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
完成上方的活動後,分割繼續保留。
完成次要容器的底部(根)活動並不會移除位於其上方的活動;因此仍須保留分割。
也會執行與完成活動相關的其他規則,例如在主要活動完成後結束次要活動:
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
當分割設定為同時完成主要與次要容器時:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
在執行階段變更分割屬性
無法變更目前使用中且可見的分割屬性。變更分割規則會影響其他活動的啟動和新容器,但不會影響現有使用中的分割。
如要變更現有分割的屬性,請完成分割視窗的側邊活動或活動,然後使用新設定再次啟動側邊視窗。
將活動從分割中擷取至完整視窗
建立新的設定,將側邊活動顯示為完整視窗,接著透過意圖重新啟動活動,該意圖會解析相同的例項。
在執行階段確認是否支援分割
Android 12L (API 級別 32) 以上版本皆支援活動嵌入功能,不過這項功能也適用於搭載舊版平台的部分裝置。如要在執行階段檢查這項功能的可用性,請使用 SplitController.splitSupportStatus
屬性或 SplitController.getSplitSupportStatus()
方法:
Kotlin
if (SplitController.getInstance(this).splitSupportStatus == SplitController.SplitSupportStatus.SPLIT_AVAILABLE) { // Device supports split activity features. }
Java
if (SplitController.getInstance(this).getSplitSupportStatus() == SplitController.SplitSupportStatus.SPLIT_AVAILABLE) { // Device supports split activity features. }
如果不支援分割,系統會 (按照非活動嵌入模型) 從活動堆疊上方啟動活動。
防止系統覆寫
Android 裝置的製造商 (原始設備製造商或 OEM) 可以將活動嵌入做為裝置系統功能實作。系統會針對多活動應用程式指定分割規則,並覆寫應用程式的視窗化行為。系統覆寫功能會強制多活動應用程式進入系統定義的活動嵌入模式。
系統活動嵌入可透過多窗格版面配置 (例如 list-detail) 增強應用程式的呈現方式,而不需要變更應用程式。然而,系統的活動嵌入也可能導致錯誤的應用程式版面配置、錯誤,或與應用程式實作的活動嵌入發生衝突。
應用程式可以透過在應用程式資訊清單檔案中設定屬性,防止或允許系統嵌入活動,例如:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<property
android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
android:value="true|false" />
</application>
</manifest>
屬性名稱是在 Jetpack WindowManager WindowProperties 物件中定義。如果您的應用程式實作活動嵌入,或者您想要防止系統將其活動嵌入規則套用至您的應用程式,請將此值設為 false
。將值設為 true
即可允許系統將系統定義的活動嵌入套用至應用程式。
限制及注意事項
- 只有執行工作的代管應用程式(確認為工作根活動的擁有者),才能在工作中整理並嵌入其他活動。如果可支援嵌入和分割的活動執行於屬於其他應用程式的工作,那麼這些活動將無法使用嵌入和分割。
- 只能將活動安排在一項工作中。在新工作中啟動活動時,系統一律將其放至現有分割外的新展開視窗。
- 只有同一個程序中的活動可以被歸類放在同一個分割中。
SplitInfo
回呼只會回報屬於相同程序的活動,因為無法得知不同程序中的活動。 - 只有在註冊規則後啟動的活動,才能套用每個配對或單獨活動規則。目前無法更新現有的分割項目或其視覺屬性。
- 分割配對篩選器設定必須符合在完整啟動活動後的使用意圖。應用程式程序開始新活動後會進行比對,因此,在使用隱含意圖的情況下,可能無法得知之後在系統程序中解析的元件名稱。如果在啟動時並不知道元件名稱,則可改用萬用字元 (「*/*」),並根據意圖動作進行篩選。
- 目前無法在容器之間移動活動,也無法在建立分割視窗後移入或移出活動。只有在啟動具有匹配規則的新活動時,WindowManager 程式庫才會建立分割,並且系統會在分割容器中的最後一個活動完成後刪除這些分割。
- 變更設定時,系統會重新啟動活動,因此當建立或移除分割並變更活動範圍時,活動會徹底刪除上一個例項,並建立新的例項。因此,應用程式開發人員在從生命週期回呼中啟動新活動時應謹慎處理。
- 裝置必須包含視窗擴充功能介面,才能支援活動嵌入功能。搭載 Android 12L (API 級別 32) 以上版本的大螢幕裝置幾乎都含有該介面。不過,無法執行多項活動的某些大螢幕裝置不含視窗擴充功能介面。如果大螢幕裝置不支援多視窗模式,可能就不支援活動嵌入功能。
其他資源
- 程式碼研究室:使用活動嵌入功能和 Material Design 建構清單/詳細資料版面配置
- 學習課程:活動嵌入
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 使用活動嵌入功能和 Material Design 建構清單/詳細資料版面配置
- 裝置相容性模式
- 支援多視窗模式