การฝังกิจกรรมจะเพิ่มประสิทธิภาพแอปในอุปกรณ์หน้าจอขนาดใหญ่โดยการแบ่งหน้าต่างงานของแอปพลิเคชัน ระหว่าง 2 กิจกรรมหรือ 2 อินสแตนซ์ของกิจกรรมเดียวกัน
หากแอปประกอบด้วยกิจกรรมหลายอย่าง การฝังกิจกรรมจะช่วยให้คุณ มอบประสบการณ์การใช้งานที่ดียิ่งขึ้นบนแท็บเล็ต อุปกรณ์พับ และอุปกรณ์ ChromeOS
การฝังกิจกรรมไม่จำเป็นต้องปรับโครงสร้างโค้ด คุณกำหนดวิธีที่แอปแสดงกิจกรรม ไม่ว่าจะแสดงแบบเคียงข้างกันหรือซ้อนกันได้โดยการสร้างไฟล์การกำหนดค่า XML หรือโดยการเรียก API ของ Jetpack WindowManager
ระบบจะดูแลการรองรับหน้าจอขนาดเล็กโดยอัตโนมัติ เมื่อแอปของคุณอยู่ใน อุปกรณ์ที่มีหน้าจอขนาดเล็ก กิจกรรมจะซ้อนกัน ในหน้าจอขนาดใหญ่ กิจกรรมจะแสดงแบบเคียงข้างกัน ระบบจะกำหนดการนำเสนอตามการกำหนดค่าที่คุณสร้างขึ้น โดยไม่จำเป็นต้องมีตรรกะการแยกสาขา
การฝังกิจกรรมจะรองรับการเปลี่ยนแปลงการวางแนวอุปกรณ์และทำงานได้อย่างราบรื่น ในอุปกรณ์แบบพับได้ โดยจะซ้อนและยกเลิกการซ้อนกิจกรรมเมื่ออุปกรณ์พับและ กางออก
การฝังกิจกรรมใช้ได้ในอุปกรณ์หน้าจอขนาดใหญ่ส่วนใหญ่ที่ใช้ Android 12L (API ระดับ 32) ขึ้นไป
แยกหน้าต่างงาน
การฝังกิจกรรมจะแบ่งหน้าต่างงานของแอปออกเป็น 2 คอนเทนเนอร์ ได้แก่ คอนเทนเนอร์หลักและ คอนเทนเนอร์รอง คอนเทนเนอร์จะเก็บกิจกรรมที่เปิดจากกิจกรรมหลักหรือจากกิจกรรมอื่นๆ ที่อยู่ในคอนเทนเนอร์อยู่แล้ว
กิจกรรมจะซ้อนกันในคอนเทนเนอร์รองเมื่อเปิดใช้งาน และ คอนเทนเนอร์รองจะซ้อนอยู่เหนือคอนเทนเนอร์หลักในหน้าจอขนาดเล็ก ดังนั้นการซ้อนกิจกรรมและการไปยังส่วนต่างๆ ด้วยปุ่มย้อนกลับจึงสอดคล้องกับการจัดลำดับ กิจกรรมที่สร้างไว้ในแอปอยู่แล้ว
การฝังกิจกรรมช่วยให้คุณแสดงกิจกรรมได้หลากหลายวิธี แอปของคุณสามารถแยกหน้าต่างงานได้โดยการเปิด 2 กิจกรรมพร้อมกันแบบเคียงข้างกันหรือเปิดกิจกรรมหนึ่งเหนืออีกกิจกรรมหนึ่ง ดังนี้
กิจกรรมที่ครอบครองทั้งหน้าต่างงานจะสร้างการแยกหน้าจอได้โดย เปิดกิจกรรมใหม่ควบคู่กัน ดังนี้
กิจกรรมที่อยู่ในโหมดแยกหน้าจอและแชร์หน้าต่างงานจะเปิดกิจกรรมอื่นๆ ได้ด้วยวิธีต่อไปนี้
หากต้องการวางซ้อนกิจกรรมอื่น ให้ทำดังนี้
รูปที่ 4 กิจกรรม A เริ่มกิจกรรม C ไปด้านข้างกิจกรรม B ไปด้านข้างและเลื่อนการแยกหน้าจอไปด้านข้างเพื่อซ่อนกิจกรรมหลักก่อนหน้า
รูปที่ 5 กิจกรรม B เริ่มกิจกรรม C ไปด้านข้างและเลื่อน การแยกไปด้านข้าง เปิดกิจกรรมในตำแหน่งเดิมที่ด้านบน ซึ่งก็คือในสแต็กกิจกรรมเดียวกัน
รูปที่ 6 กิจกรรม B เริ่มกิจกรรม C โดยไม่มีค่าสถานะ Intent เพิ่มเติม เปิดกิจกรรมแบบเต็มหน้าต่างในงานเดียวกัน
รูปที่ 7 กิจกรรม A หรือกิจกรรม B จะเริ่มกิจกรรม C ซึ่งจะเติม หน้าต่างงาน
การนำทางย้อนกลับ
แอปพลิเคชันประเภทต่างๆ อาจมีกฎการนำทางย้อนกลับที่แตกต่างกันในสถานะหน้าต่างงานที่แยกกัน โดยขึ้นอยู่กับความสัมพันธ์ระหว่างกิจกรรมหรือวิธีที่ผู้ใช้ทริกเกอร์เหตุการณ์ย้อนกลับ เช่น
- การดำเนินการร่วมกัน: หากกิจกรรมมีความเกี่ยวข้องและไม่ควรแสดงกิจกรรมหนึ่งโดยไม่มีอีกกิจกรรมหนึ่ง คุณจะกำหนดค่าการนำทางด้วยท่าทางสัมผัสย้อนกลับให้เสร็จสิ้นทั้ง 2 กิจกรรมได้
- การทำงานแบบแยกกัน: หากกิจกรรมเป็นอิสระอย่างสมบูรณ์ การนำทางย้อนกลับในกิจกรรมจะไม่ส่งผลต่อสถานะของกิจกรรมอื่นในหน้าต่างงาน
ระบบจะส่งเหตุการณ์ย้อนกลับไปยังกิจกรรมที่โฟกัสล่าสุดเมื่อใช้การนำทางด้วยปุ่ม
สำหรับการไปยังส่วนต่างๆ ด้วยท่าทางสัมผัส ให้ทำดังนี้
Android 14 (API ระดับ 34) และต่ำกว่า - ระบบจะส่งเหตุการณ์ย้อนกลับไปยัง กิจกรรมที่เกิดท่าทางสัมผัส เมื่อผู้ใช้ปัดจากด้านซ้ายของหน้าจอ ระบบจะส่งเหตุการณ์ย้อนกลับไปยังกิจกรรมในแผงด้านซ้ายของหน้าต่างที่แยก เมื่อผู้ใช้ปัดจากด้านขวาของหน้าจอ ระบบจะส่งเหตุการณ์ย้อนกลับไปยังกิจกรรมในแผงด้านขวา
Android 15 (API ระดับ 35) ขึ้นไป
เมื่อจัดการกับกิจกรรมหลายอย่างจากแอปเดียวกัน ท่าทางสัมผัสจะ สิ้นสุดกิจกรรมที่ด้านบนสุดโดยไม่คำนึงถึงทิศทางการปัด ซึ่งจะมอบประสบการณ์การใช้งานที่ เป็นหนึ่งเดียวกันมากขึ้น
ในสถานการณ์ที่มีกิจกรรม 2 รายการจากแอปต่างๆ (การซ้อนทับ) ระบบจะส่ง เหตุการณ์ย้อนกลับไปยังกิจกรรมล่าสุดที่โฟกัส ซึ่งสอดคล้องกับ ลักษณะการทำงานของการนำทางด้วยปุ่ม
เลย์เอาต์แบบหลายบานหน้าต่าง
Jetpack WindowManager ช่วยให้คุณสร้างเลย์เอาต์แบบหลายบานหน้าต่างที่ฝังกิจกรรมในอุปกรณ์หน้าจอขนาดใหญ่ที่ใช้ Android 12L (API ระดับ 32) ขึ้นไป และในอุปกรณ์บางรุ่นที่ใช้แพลตฟอร์มเวอร์ชันก่อนหน้า แอปที่มีอยู่ซึ่งอิงตาม
กิจกรรมหลายรายการแทนที่จะเป็น Fragment หรือเลย์เอาต์ที่อิงตามมุมมอง เช่น
SlidingPaneLayout สามารถมอบประสบการณ์การใช้งานหน้าจอขนาดใหญ่ที่ดีขึ้น
โดยไม่ต้องปรับโครงสร้างซอร์สโค้ด
ตัวอย่างที่พบบ่อยคือการแยกรายการ-รายละเอียด เพื่อให้การนำเสนอมีคุณภาพสูง ระบบจะเริ่มกิจกรรมรายการ จากนั้นแอปพลิเคชัน จะเริ่มกิจกรรมรายละเอียดทันที ระบบการเปลี่ยนภาพจะรอจนกว่าจะวาดทั้ง 2 กิจกรรม แล้วจึงแสดงพร้อมกัน สำหรับผู้ใช้ กิจกรรมทั้ง 2 รายการจะเปิดขึ้นพร้อมกัน
แอตทริบิวต์ที่แยก
คุณระบุสัดส่วนของหน้าต่างงานระหว่างคอนเทนเนอร์ที่แยกกัน และวิธีจัดวางคอนเทนเนอร์ที่สัมพันธ์กันได้
สำหรับกฎที่กำหนดไว้ในไฟล์การกำหนดค่า XML ให้ตั้งค่าแอตทริบิวต์ต่อไปนี้
splitRatio: กำหนดสัดส่วนของคอนเทนเนอร์ ค่าคือตัวเลขทศนิยม ในช่วงเปิด (0.0, 1.0)splitLayoutDirection: ระบุวิธีจัดวางคอนเทนเนอร์ที่แยก เทียบกับคอนเทนเนอร์อื่นๆ ค่าต่างๆ มีดังนี้ltr: จากซ้ายไปขวาrtl: ขวาไปซ้ายlocale: ระบบจะกำหนดltrหรือrtlจากการตั้งค่าภาษา
ดูตัวอย่างได้ที่ส่วนการกำหนดค่า XML
สำหรับกฎที่สร้างโดยใช้ WindowManager API ให้สร้างออบเจ็กต์ SplitAttributes
ด้วย SplitAttributes.Builder และเรียกใช้เมธอด Builder ต่อไปนี้
setSplitType(): กำหนดสัดส่วนของคอนเทนเนอร์ที่แยก ดูSplitAttributes.SplitTypeสำหรับอาร์กิวเมนต์ที่ถูกต้อง รวมถึงเมธอดSplitAttributes.SplitType.ratio()
ไม่รองรับแอตทริบิวต์ XML ที่เทียบเท่าsetLayoutDirection(): กำหนดเลย์เอาต์ของคอนเทนเนอร์ ดูค่าที่เป็นไปได้ในSplitAttributes.LayoutDirection
ดูตัวอย่างได้ที่ส่วน WindowManager API
การวางแนวแยก
ขนาดและสัดส่วนภาพของจอแสดงผลจะเป็นตัวกำหนดตำแหน่งของ กิจกรรมในการแยกการฝังกิจกรรม ในจอแสดงผลแนวนอนขนาดใหญ่ กิจกรรมจะแสดงเคียงข้างกัน ส่วนในจอแสดงผลแนวตั้งสูงหรือการวางบนโต๊ะในอุปกรณ์พับได้ กิจกรรมจะแสดงซ้อนกัน
คุณระบุการวางแนวการแยกได้ด้วยเครื่องคำนวณ SplitController
SplitAttributes เครื่องคำนวณจะคำนวณ SplitAttributes สำหรับ
SplitRule ที่ใช้งานอยู่
ใช้เครื่องคิดเลขเพื่อแยกคอนเทนเนอร์หลักในทิศทางต่างๆ สำหรับ สถานะอุปกรณ์ที่แตกต่างกัน เช่น
Kotlin
if (WindowSdkExtensions.getInstance().extensionVersion >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator { params -> val parentConfiguration = params.parentConfiguration val builder = SplitAttributes.Builder() return@setSplitAttributesCalculator if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build() } else if (parentConfiguration.screenHeightDp >= 600) { // Horizontal split for tall displays. builder .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP) .build() } else { // Fallback to expand the secondary container. builder .setSplitType(SPLIT_TYPE_EXPAND) .build() } } }
Java
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator(params -> { Configuration parentConfiguration = params.getParentConfiguration(); SplitAttributes.Builder builder = new SplitAttributes.Builder(); if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. return builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build(); } else if (parentConfiguration.screenHeightDp >= 600) { // Horizontal split for tall displays. return builder .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP) .build(); } else { // Fallback to expand the secondary container. return builder .setSplitType(SplitType.SPLIT_TYPE_EXPAND) .build(); } }); }
ในอุปกรณ์แบบพับได้ คุณจะแยกหน้าจอในแนวตั้งได้หากอุปกรณ์อยู่ในแนวนอน แสดงกิจกรรมเดียวได้หากอุปกรณ์อยู่ในแนวตั้ง และแยกหน้าจอในแนวนอนได้หากอุปกรณ์อยู่ในท่าตั้งบนโต๊ะ
Kotlin
if (WindowSdkExtensions.getInstance().extensionVersion >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator { params -> val tag = params.splitRuleTag val parentWindowMetrics = params.parentWindowMetrics val parentConfiguration = params.parentConfiguration val foldingFeatures = params.parentWindowLayoutInfo.displayFeatures.filterIsInstance<FoldingFeature>() val feature = if (foldingFeatures.size == 1) foldingFeatures[0] else null val builder = SplitAttributes.Builder() builder.setSplitType(SPLIT_TYPE_HINGE) return@setSplitAttributesCalculator if (feature?.isSeparating == true) { // Horizontal split for tabletop posture. builder .setSplitType(SPLIT_TYPE_HINGE) .setLayoutDirection( if (feature.orientation == FoldingFeature.Orientation.HORIZONTAL) { SplitAttributes.LayoutDirection.BOTTOM_TO_TOP } else { SplitAttributes.LayoutDirection.LOCALE } ) .build() } else if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build() } else { // No split for tall displays. builder .setSplitType(SPLIT_TYPE_EXPAND) .build() } } }
Java
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator(params -> { String tag = params.getSplitRuleTag(); WindowMetrics parentWindowMetrics = params.getParentWindowMetrics(); Configuration parentConfiguration = params.getParentConfiguration(); List<FoldingFeature> foldingFeatures = params.getParentWindowLayoutInfo().getDisplayFeatures().stream().filter( item -> item instanceof FoldingFeature) .map(item -> (FoldingFeature) item) .collect(Collectors.toList()); FoldingFeature feature = foldingFeatures.size() == 1 ? foldingFeatures.get(0) : null; SplitAttributes.Builder builder = new SplitAttributes.Builder(); builder.setSplitType(SplitType.SPLIT_TYPE_HINGE); if (feature != null && feature.isSeparating()) { // Horizontal slit for tabletop posture. return builder .setSplitType(SplitType.SPLIT_TYPE_HINGE) .setLayoutDirection( feature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL ? SplitAttributes.LayoutDirection.BOTTOM_TO_TOP : SplitAttributes.LayoutDirection.LOCALE) .build(); } else if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. return builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build(); } else { // No split for tall displays. return builder .setSplitType(SplitType.SPLIT_TYPE_EXPAND) .build(); } }); }
ตัวยึดตำแหน่ง
กิจกรรมตัวยึดตำแหน่งคือกิจกรรมรองที่ว่างเปล่าซึ่งใช้พื้นที่ใน การแยกกิจกรรม ในท้ายที่สุดแล้ว กิจกรรมเหล่านี้จะถูกแทนที่ด้วยกิจกรรมอื่น ที่มีเนื้อหา ตัวอย่างเช่น กิจกรรมตัวยึดตำแหน่งอาจอยู่ใน ด้านรองของการแยกกิจกรรมในเลย์เอาต์รายการ-รายละเอียดจนกว่าจะมีการเลือกรายการจาก รายการ เมื่อถึงจุดนั้น กิจกรรมที่มีข้อมูลรายละเอียดสำหรับรายการที่เลือกจะ แทนที่ตัวยึดตำแหน่ง
โดยค่าเริ่มต้น ระบบจะแสดงตัวยึดตำแหน่งเมื่อมีพื้นที่เพียงพอสำหรับ การแยกกิจกรรมเท่านั้น ตัวยึดตำแหน่งจะสิ้นสุดโดยอัตโนมัติเมื่อขนาดการแสดงผลเปลี่ยนเป็นความกว้างหรือความสูงที่เล็กเกินกว่าจะแสดงการแยก เมื่อมีพื้นที่ว่าง ระบบจะเปิดตัวตัวยึดตำแหน่งอีกครั้งโดยมีสถานะเริ่มต้นใหม่
อย่างไรก็ตาม แอตทริบิวต์ stickyPlaceholder ของเมธอด SplitPlaceholderRule หรือ setSticky() ของ SplitPlaceholder.Builder สามารถลบล้างลักษณะการทำงานเริ่มต้นได้ เมื่อแอตทริบิวต์หรือเมธอดระบุค่า true ระบบจะแสดงตัวยึดตำแหน่งเป็นกิจกรรมบนสุดในหน้าต่างงานเมื่อมีการปรับขนาดจอแสดงผลจากจอแสดงผลแบบ 2 บานหน้าต่างเป็นจอแสดงผลแบบบานหน้าต่างเดียว (ดูตัวอย่างได้ที่การกำหนดค่าการแยก)
การเปลี่ยนแปลงขนาดหน้าต่าง
เมื่อการเปลี่ยนแปลงการกำหนดค่าอุปกรณ์ลดความกว้างของหน้าต่างงานจนไม่ ใหญ่พอสำหรับการจัดวางแบบหลายบานหน้าต่าง (เช่น เมื่ออุปกรณ์พับได้หน้าจอขนาดใหญ่ พับจากขนาดแท็บเล็ตเป็นขนาดโทรศัพท์ หรือเมื่อมีการปรับขนาดหน้าต่างแอปใน โหมดหลายหน้าต่าง) ระบบจะซ้อนกิจกรรมที่ไม่ใช่ตัวยึดตำแหน่งในบานหน้าต่างรองของ หน้าต่างงานไว้ด้านบนของกิจกรรมในบานหน้าต่างหลัก
กิจกรรมตัวยึดตำแหน่งจะแสดงเมื่อมีความกว้างของจอแสดงผลเพียงพอสำหรับการ แยกเท่านั้น ในหน้าจอขนาดเล็ก ระบบจะปิดพื้นที่ที่ใช้แทนโดยอัตโนมัติ เมื่อ พื้นที่แสดงผลมีขนาดใหญ่พออีกครั้ง ระบบจะสร้างตัวยึดตำแหน่งขึ้นมาใหม่ (ดูส่วนตัวยึดตำแหน่ง)
การซ้อนกิจกรรมเป็นไปได้เนื่องจาก WindowManager จะจัดลำดับ Z ของกิจกรรม ในแผงรองเหนือกิจกรรมในแผงหลัก
กิจกรรมหลายรายการในแผงรอง
กิจกรรม B เริ่มกิจกรรม C ในที่เดิมโดยไม่มีค่าสถานะ Intent เพิ่มเติม

ซึ่งส่งผลให้กิจกรรมในงานเดียวกันมีลำดับ Z ดังนี้

ดังนั้นในหน้าต่างงานที่เล็กลง แอปพลิเคชันจะหดเหลือเพียงกิจกรรมเดียวโดยมี C อยู่ที่ด้านบนของสแต็ก

การไปยังส่วนต่างๆ กลับในหน้าต่างขนาดเล็กจะนำคุณไปยังกิจกรรมที่ซ้อนกัน
หากมีการคืนค่าการกำหนดค่าหน้าต่างงานเป็นขนาดที่ใหญ่ขึ้นซึ่งรองรับหลายแผงได้ กิจกรรมจะแสดงแบบคู่ขนานอีกครั้ง
การแยกแบบซ้อน
กิจกรรม B จะเริ่มกิจกรรม C ไปด้านข้างและเลื่อนการแยกหน้าจอไปด้านข้าง

ผลลัพธ์คือลำดับ Z ของกิจกรรมต่อไปนี้ในงานเดียวกัน

ในหน้าต่างงานขนาดเล็ก แอปพลิเคชันจะย่อเหลือกิจกรรมเดียวโดยมี C อยู่ด้านบน

การวางแนวตั้งแบบคงที่
การตั้งค่าไฟล์ Manifest android:screenOrientation ช่วยให้แอปจำกัด กิจกรรมให้เป็นการวางแนวตั้งหรือแนวนอนได้ เพื่อปรับปรุงประสบการณ์ของผู้ใช้ ในอุปกรณ์หน้าจอขนาดใหญ่ เช่น แท็บเล็ตและอุปกรณ์แบบพับได้ ผู้ผลิตอุปกรณ์ (OEM) สามารถไม่สนใจคำขอการวางแนวหน้าจอและแสดงแอปในรูปแบบจดหมาย ในแนวนอนบนจอแสดงผลแนวนอน หรือในแนวนอนบนจอแสดงผลแนวตั้ง
ในทำนองเดียวกัน เมื่อเปิดใช้การฝังกิจกรรมแล้ว OEM จะปรับแต่งอุปกรณ์เพื่อ แสดงกิจกรรมแนวตั้งแบบคงที่ในแนวนอนบนหน้าจอขนาดใหญ่ (ความกว้าง ≥ 600dp) ได้ เมื่อกิจกรรมแนวตั้งคงที่เปิดใช้กิจกรรมที่ 2 อุปกรณ์จะแสดงกิจกรรมทั้ง 2 อย่างแบบเคียงข้างกันในจอแสดงผลแบบ 2 บานหน้าต่าง
เพิ่มพร็อพเพอร์ตี้ android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
ลงในไฟล์ Manifest ของแอปเสมอเพื่อแจ้งให้อุปกรณ์ทราบว่าแอปของคุณรองรับ
การฝังกิจกรรม (ดูส่วนการกำหนดค่าการแยก
) จากนั้นอุปกรณ์ที่ OEM ปรับแต่งจะพิจารณาได้ว่าจะใส่ขอบดำ
กิจกรรมแนวตั้งแบบคงที่หรือไม่
การกำหนดค่าการทดสอบ A/B
กฎการแยกจะกำหนดค่าการแยกกิจกรรม คุณกำหนดกฎการแยกในไฟล์การกำหนดค่า XML หรือโดยการเรียกใช้ API ของ WindowManager ของ Jetpack
ไม่ว่าจะในกรณีใดก็ตาม แอปต้องเข้าถึงไลบรารี WindowManager และต้องแจ้งให้ระบบทราบว่าแอปได้ใช้การฝังกิจกรรมแล้ว
โดยทำดังนี้
เพิ่มทรัพยากร Dependency ของไลบรารี WindowManager ล่าสุดลงในไฟล์
build.gradleระดับโมดูลของแอป เช่นimplementation 'androidx.window:window:1.1.0-beta02'ไลบรารี WindowManager มีคอมโพเนนต์ทั้งหมดที่จำเป็นสำหรับการฝังกิจกรรม
แจ้งให้ระบบทราบว่าแอปของคุณได้ฝังกิจกรรมแล้ว
เพิ่มพร็อพเพอร์ตี้
android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLEDไปยังองค์ประกอบ <application> ของไฟล์ Manifest ของแอป และตั้งค่า เป็น 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 ขึ้นไป ระบบจะปิดใช้การแยกการฝังกิจกรรม เว้นแต่จะเพิ่มพร็อพเพอร์ตี้ลงในไฟล์ Manifest และตั้งค่าเป็น true
นอกจากนี้ ผู้ผลิตอุปกรณ์ยังใช้การตั้งค่านี้เพื่อเปิดใช้ความสามารถที่กำหนดเองสำหรับ แอปที่รองรับการฝังกิจกรรมด้วย เช่น อุปกรณ์สามารถแสดงกิจกรรมที่ใช้ได้เฉพาะในแนวตั้งบนจอแสดงผลแนวนอนโดยมีแถบดำด้านบนและด้านล่างเพื่อปรับทิศทางกิจกรรมสำหรับการเปลี่ยนไปใช้เลย์เอาต์แบบ 2 บานหน้าต่างเมื่อกิจกรรมที่ 2 เริ่มต้น (ดูการวางแนวแบบคงที่ในแนวตั้ง)
การกำหนดค่า 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>สร้างตัวเริ่มต้น
คอมโพเนนต์ WindowManager
RuleControllerจะแยกวิเคราะห์ไฟล์การกำหนดค่า XML และทำให้กฎพร้อมใช้งานในระบบ ไลบรารี Startup ของ JetpackInitializerทำให้ไฟล์ XML พร้อมใช้งานกับRuleControllerเมื่อแอปเริ่มต้นทำงาน เพื่อให้กฎมีผลเมื่อกิจกรรมใดก็ตามเริ่มขึ้นหากต้องการสร้างตัวเริ่มต้น ให้ทำดังนี้
เพิ่มทรัพยากร Dependency ของไลบรารี Jetpack Startup ล่าสุดลงในไฟล์
build.gradleระดับโมดูล เช่นimplementation 'androidx.startup:startup-runtime:1.1.1'สร้างคลาสที่ใช้
Initializerอินเทอร์เฟซตัวเริ่มต้นจะทำให้กฎการแยกพร้อมใช้งานสำหรับ
RuleControllerโดย ส่งรหัสของไฟล์การกำหนดค่า XML (main_split_config.xml) ไปยังเมธอดRuleController.parseRules()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ลงในไฟล์ Manifest ของแอป เป็น<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>InitializationProviderจะค้นหาและเริ่มต้นSplitInitializerก่อนที่จะเรียกใช้เมธอดonCreate()ของแอป ด้วยเหตุนี้ กฎการแยกจึงมีผลเมื่อกิจกรรมหลักของแอปเริ่มต้น
WindowManager API
คุณสามารถฝังกิจกรรมโดยใช้โปรแกรมด้วยการเรียก API
เพียงไม่กี่ครั้ง เรียกใช้ในเมธอด onCreate() ของคลาสย่อยของ
Application เพื่อให้แน่ใจว่ากฎมีผลก่อนที่กิจกรรมใดๆ
จะเปิดตัว
หากต้องการสร้างการแยกกิจกรรมโดยอัตโนมัติ ให้ทำดังนี้
สร้างกฎการแยก
สร้าง
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
SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build();
SplitAttributes.Builderจะสร้างออบเจ็กต์ที่มีแอตทริบิวต์เลย์เอาต์setSplitType(): กำหนดวิธีจัดสรรพื้นที่แสดงผลที่มีอยู่ให้กับคอนเทนเนอร์กิจกรรมแต่ละรายการ ประเภทการแยกสัดส่วนจะระบุ สัดส่วนของพื้นที่แสดงผลที่ใช้ได้ซึ่งจัดสรรให้กับ คอนเทนเนอร์หลัก ส่วนคอนเทนเนอร์รองจะใช้พื้นที่แสดงผลที่เหลือ ที่ใช้ได้setLayoutDirection(): ระบุวิธีจัดวางคอนเทนเนอร์กิจกรรม เมื่อเทียบกับคอนเทนเนอร์อื่นๆ โดยให้คอนเทนเนอร์หลักอยู่ก่อน
สร้าง
SplitPairRuleKotlin
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) ที่ขนาดการแสดงผลที่เล็กกว่าใน 2 ขนาดต้องมีเพื่อเปิดใช้การแยกหน้าจอโดยไม่คำนึงถึงการวางแนวของอุปกรณ์setMaxAspectRatioInPortrait(): ตั้งค่าอัตราส่วนภาพสูงสุดที่แสดง (สูง:กว้าง) ในการวางแนวตั้งซึ่งจะแสดงการแยกกิจกรรม หากสัดส่วนภาพของจอแสดงผลแนวตั้ง เกินสัดส่วนภาพสูงสุด ระบบจะปิดใช้การแยกจอแสดงผลไม่ว่าจอแสดงผลจะมีความกว้างเท่าใดก็ตาม หมายเหตุ: ค่าเริ่มต้นคือ 1.4 ซึ่ง จะทําให้กิจกรรมครอบครองทั้งหน้าต่างงานในแนวตั้ง บนแท็บเล็ตส่วนใหญ่ ดูเพิ่มเติมSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTและsetMaxAspectRatioInLandscape()ค่าเริ่มต้นสำหรับ แนวนอนคือALWAYS_ALLOWsetFinishPrimaryWithSecondary(): กำหนดว่าการทำกิจกรรมทั้งหมดในคอนเทนเนอร์รองจะส่งผลต่อกิจกรรมในคอนเทนเนอร์หลักอย่างไรNEVERหมายความว่าระบบไม่ควรทำกิจกรรมหลักให้เสร็จ เมื่อกิจกรรมทั้งหมดในคอนเทนเนอร์รองเสร็จสิ้น (ดูทำกิจกรรมให้เสร็จ)setFinishSecondaryWithPrimary(): กำหนดวิธีที่การทำกิจกรรมทั้งหมดในคอนเทนเนอร์หลักให้เสร็จสมบูรณ์จะส่งผลต่อกิจกรรมในคอนเทนเนอร์รองALWAYSระบุว่าระบบควรทำกิจกรรมในคอนเทนเนอร์รองให้เสร็จเสมอเมื่อกิจกรรมทั้งหมดในคอนเทนเนอร์หลักเสร็จสิ้น (ดูทำกิจกรรมให้เสร็จ)setClearTop(): ระบุว่ากิจกรรมทั้งหมดใน คอนเทนเนอร์รองเสร็จสิ้นเมื่อมีการเปิดใช้กิจกรรมใหม่ใน คอนเทนเนอร์หรือไม่ ค่าfalseระบุว่ากิจกรรมใหม่จะ ซ้อนอยู่ด้านบนของกิจกรรมที่อยู่ในคอนเทนเนอร์รองอยู่แล้ว
รับอินสแตนซ์ Singleton ของ 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);
สร้าง
SplitPlaceholderRuleKotlin
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(this, 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) ที่ขนาดการแสดงผลที่เล็กกว่าใน 2 ขนาด ต้องมีเพื่อให้แยกได้โดยไม่คำนึงถึงการวางแนวของอุปกรณ์setMaxAspectRatioInPortrait(): กำหนดอัตราส่วนภาพสูงสุด (ความสูง:ความกว้าง) ในการวางแนวตั้ง ซึ่งจะแสดงการแยกกิจกรรม หมายเหตุ: ค่าเริ่มต้นคือ 1.4 ซึ่งจะทําให้กิจกรรมเติมหน้าต่างงานในแนวนอนบนแท็บเล็ตส่วนใหญ่ ดูเพิ่มเติมSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTและsetMaxAspectRatioInLandscape()ค่าเริ่มต้นสําหรับแนวนอนคือALWAYS_ALLOWsetFinishPrimaryWithPlaceholder(): กำหนดวิธีที่การสิ้นสุดกิจกรรมตัวยึดตำแหน่งส่งผลต่อกิจกรรม ในคอนเทนเนอร์หลัก 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 ดังนี้
โมเดลความน่าเชื่อถือ
กระบวนการโฮสต์ที่ฝังกิจกรรมจากแอปอื่นๆ จะสามารถกำหนดการนำเสนอของกิจกรรมที่ฝังใหม่ได้ ซึ่งรวมถึงขนาด ตำแหน่ง การครอบตัด และความโปร่งใส โฮสต์ที่เป็นอันตรายอาจใช้ความสามารถนี้เพื่อหลอกลวงผู้ใช้และ สร้างการคลิกแจ็กกิ้งหรือการโจมตีอื่นๆ ที่แก้ไข UI
Android กำหนดให้แอปเลือกใช้เพื่ออนุญาตการฝังกิจกรรมของตนเองเพื่อป้องกันการใช้การฝังกิจกรรมข้ามแอปในทางที่ผิด แอปสามารถกำหนดให้โฮสต์เป็นเชื่อถือได้ หรือไม่เชื่อถือได้
โฮสต์ที่เชื่อถือได้
หากต้องการอนุญาตให้แอปพลิเคชันอื่นๆ ฝังและควบคุมการนำเสนอกิจกรรมจากแอปของคุณได้อย่างเต็มที่ ให้ระบุใบรับรอง SHA-256 ของแอปพลิเคชันโฮสต์ในแอตทริบิวต์ android:knownActivityEmbeddingCerts ขององค์ประกอบ <activity> หรือ <application> ในไฟล์ Manifest ของแอป
ตั้งค่า 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>
เจ้าของแอปจะรับค่าแฮชของใบรับรอง SHA ได้โดยเรียกใช้ทาสก์ Gradle
signingReport ไดเจสต์ของใบรับรองคือลายนิ้วมือ SHA-256 ที่ไม่มี
เครื่องหมายโคลอนคั่น ดูข้อมูลเพิ่มเติมได้ที่หัวข้อเรียกใช้รายงานการลงนามและ
การตรวจสอบสิทธิ์ไคลเอ็นต์
โฮสต์ที่ไม่น่าเชื่อถือ
หากต้องการอนุญาตให้แอปใดก็ตามฝังกิจกรรมของแอปและควบคุมการนำเสนอ
ให้ระบุแอตทริบิวต์ android:allowUntrustedActivityEmbedding ในองค์ประกอบ
<activity> หรือ <application> ในไฟล์ Manifest ของแอป เช่น
<activity
android:name=".MyEmbeddableActivity"
android:allowUntrustedActivityEmbedding="true"
... />
ค่าเริ่มต้นของแอตทริบิวต์คือ false ซึ่งจะป้องกันการฝังกิจกรรมข้ามแอป
การตรวจสอบสิทธิ์ที่กำหนดเอง
หากต้องการลดความเสี่ยงของการฝังกิจกรรมที่ไม่น่าเชื่อถือ ให้สร้างกลไกการตรวจสอบสิทธิ์ที่กำหนดเองซึ่งจะยืนยันตัวตนของโฮสต์ หากคุณทราบใบรับรองโฮสต์ ให้ใช้ไลบรารี androidx.security.app.authenticator เพื่อตรวจสอบสิทธิ์ หากผู้จัดทำการตรวจสอบสิทธิ์หลังจากฝังกิจกรรม คุณจะ
แสดงเนื้อหาจริงได้ หากไม่เป็นไปตามนโยบาย คุณสามารถแจ้งให้ผู้ใช้ทราบว่าระบบ
ไม่อนุญาตให้ดำเนินการดังกล่าวและบล็อกเนื้อหาได้
ใช้เมธอด ActivityEmbeddingController#isActivityEmbedded() จากไลบรารี Jetpack WindowManager เพื่อตรวจสอบว่าโฮสต์ฝังกิจกรรมของคุณหรือไม่ เช่น
Kotlin
fun isActivityEmbedded(activity: Activity): Boolean { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity) }
Java
boolean isActivityEmbedded(Activity activity) { return ActivityEmbeddingController.getInstance(context).isActivityEmbedded(activity); }
ข้อจำกัดเกี่ยวกับขนาดขั้นต่ำ
ระบบ Android จะใช้ความสูงและความกว้างขั้นต่ำที่ระบุไว้ในองค์ประกอบ <layout> ของไฟล์ Manifest ของแอปกับกิจกรรมที่ฝังไว้ หากแอปพลิเคชันไม่ได้ระบุความสูงและความกว้างขั้นต่ำ ระบบจะใช้ค่าเริ่มต้น
(sw220dp)
หากโฮสต์พยายามปรับขนาดคอนเทนเนอร์ที่ฝังให้มีขนาดเล็กกว่าขนาดขั้นต่ำ คอนเทนเนอร์ที่ฝังจะขยายเพื่อครอบคลุมขอบเขตของงานทั้งหมด
<activity-alias>
หากต้องการให้การฝังกิจกรรมที่เชื่อถือได้หรือไม่น่าเชื่อถือทํางานร่วมกับองค์ประกอบ
<activity-alias> คุณต้องใช้ android:knownActivityEmbeddingCerts หรือ
android:allowUntrustedActivityEmbedding กับกิจกรรมเป้าหมาย
แทนที่จะใช้นามแฝง นโยบายที่ยืนยันความปลอดภัยในเซิร์ฟเวอร์ของระบบจะ
อิงตามค่าสถานะที่ตั้งค่าไว้ในเป้าหมาย ไม่ใช่นามแฝง
แอปพลิเคชันโฮสต์
แอปพลิเคชันโฮสต์จะใช้การฝังกิจกรรมข้ามแอปในลักษณะเดียวกับที่ใช้การฝังกิจกรรมแอปเดียว
ออบเจ็กต์ SplitPairRule และ SplitPairFilter หรือ ActivityRule และ ActivityFilter
ระบุกิจกรรมที่ฝังและการแยกหน้าต่างงาน คุณกำหนดกฎการแยกได้แบบคงที่ใน XML หรือที่รันไทม์โดยใช้การเรียก API ของ Jetpack
WindowManager
หากแอปพลิเคชันโฮสต์พยายามฝังกิจกรรมที่ไม่ได้เลือกใช้การฝังข้ามแอป กิจกรรมจะครอบครองขอบเขตของงานทั้งหมด ด้วยเหตุนี้ แอปพลิเคชันโฮสต์จึงต้องทราบว่ากิจกรรมเป้าหมายอนุญาตการฝังข้ามแอปหรือไม่
หากกิจกรรมที่ฝังเริ่มกิจกรรมใหม่ในงานเดียวกันและกิจกรรมใหม่ไม่ได้เลือกใช้การฝังข้ามแอป กิจกรรมจะครอบครองขอบเขตของงานทั้งหมดแทนที่จะซ้อนทับกิจกรรมในคอนเทนเนอร์ที่ฝัง
แอปพลิเคชันโฮสต์สามารถฝังกิจกรรมของตัวเองได้โดยไม่มีข้อจำกัด ตราบใดที่ กิจกรรมเปิดตัวในงานเดียวกัน
ตัวอย่างการแยก
แยกจากหน้าต่างแบบเต็ม
ไม่ต้องทำการปรับโครงสร้างโค้ด คุณกำหนดค่าสำหรับการแยกได้แบบคงที่หรือที่รันไทม์ แล้วเรียกใช้ Context#startActivity() โดยไม่ต้องมีพารามิเตอร์เพิ่มเติม
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
แยกโดยค่าเริ่มต้น
เมื่อออกแบบหน้า Landing Page ของแอปพลิเคชันให้แบ่งออกเป็น 2 คอนเทนเนอร์ในหน้าจอขนาดใหญ่ ประสบการณ์ของผู้ใช้จะดีที่สุดเมื่อสร้างและนำเสนอกิจกรรมทั้ง 2 อย่างพร้อมกัน อย่างไรก็ตาม เนื้อหาอาจไม่พร้อมใช้งานสำหรับคอนเทนเนอร์รองของการแยกจนกว่าผู้ใช้จะโต้ตอบกับกิจกรรมในคอนเทนเนอร์หลัก (เช่น ผู้ใช้เลือกรายการจากเมนูการนำทาง) กิจกรรมตัวยึดตำแหน่งจะช่วยเติมเต็มช่องว่างจนกว่าจะแสดงเนื้อหาในคอนเทนเนอร์รองของการแยกหน้าจอได้ (ดูส่วนตัวยึดตำแหน่ง)
หากต้องการสร้างการแยกที่มีตัวยึดตำแหน่ง ให้สร้างตัวยึดตำแหน่งและเชื่อมโยงกับ กิจกรรมหลักโดยทำดังนี้
<SplitPlaceholderRule
window:placeholderActivityName=".PlaceholderActivity">
<ActivityFilter
window:activityName=".MainActivity"/>
</SplitPlaceholderRule>
การแยก Deep Link
เมื่อแอปได้รับ Intent ระบบจะแสดงกิจกรรมเป้าหมายเป็น ส่วนรองของการแยกกิจกรรมได้ เช่น คำขอให้แสดงหน้าจอรายละเอียดที่มีข้อมูลเกี่ยวกับรายการจากรายการ ในจอแสดงผลขนาดเล็ก รายละเอียด จะแสดงในหน้าต่างงานแบบเต็ม ส่วนในอุปกรณ์ขนาดใหญ่ รายละเอียดจะแสดงข้างรายการ
คำขอเปิดตัวควรส่งไปยังกิจกรรมหลัก และควรเปิดตัวกิจกรรมรายละเอียดเป้าหมาย ในโหมดแยก ระบบจะเลือกการนำเสนอที่ถูกต้องโดยอัตโนมัติ ไม่ว่าจะเป็นแบบซ้อนกันหรือแบบเคียงข้างกัน โดยอิงตามความกว้างของจอแสดงผลที่มี
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) RuleController.getInstance(this) .addRule(SplitPairRule.Builder(filterSet).build()) startActivity(Intent(this, DetailActivity::class.java)) }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); RuleController.getInstance(this) .addRule(new SplitPairRule.Builder(filterSet).build()); startActivity(new Intent(this, DetailActivity.class)); }
ปลายทางของ Deep Link อาจเป็นกิจกรรมเดียวที่ควรพร้อมใช้งานสำหรับ ผู้ใช้ในสแต็กการนำทางย้อนกลับ และคุณอาจต้องการหลีกเลี่ยงการปิด กิจกรรมรายละเอียดและเหลือเพียงกิจกรรมหลัก


แต่คุณสามารถทำกิจกรรมทั้ง 2 อย่างให้เสร็จพร้อมกันได้โดยใช้แอตทริบิวต์
finishPrimaryWithSecondary
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".ListActivity"
window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>
ดูส่วนแอตทริบิวต์การกำหนดค่า
กิจกรรมหลายรายการในคอนเทนเนอร์ที่แยก
การซ้อนกิจกรรมหลายอย่างในคอนเทนเนอร์แบบแยกช่วยให้ผู้ใช้เข้าถึงเนื้อหาเชิงลึกได้ ตัวอย่างเช่น เมื่อใช้การแยกรายการ-รายละเอียด ผู้ใช้อาจต้องไปที่ส่วนรายละเอียดย่อย แต่ยังคงกิจกรรมหลักไว้
Kotlin
class DetailActivity : AppCompatActivity() { fun onOpenSubdetail() { startActivity(Intent(this, SubdetailActivity::class.java)) } }
Java
public class DetailActivity extends AppCompatActivity { void onOpenSubdetail() { startActivity(new Intent(this, SubdetailActivity.class)); } }
กิจกรรมย่อยจะอยู่เหนือกิจกรรมหลักและซ่อนกิจกรรมหลักไว้

จากนั้นผู้ใช้จะกลับไปยังระดับรายละเอียดก่อนหน้าได้โดยการย้อนกลับ ผ่านสแต็ก
การซ้อนกิจกรรมทับกันเป็นลักษณะการทำงานเริ่มต้นเมื่อเปิดกิจกรรมจากกิจกรรมในคอนเทนเนอร์รองเดียวกัน กิจกรรม ที่เปิดจากคอนเทนเนอร์หลักภายในสปลิตที่ใช้งานอยู่จะไปอยู่ใน คอนเทนเนอร์รองที่ด้านบนของสแต็กกิจกรรมด้วย
กิจกรรมในงานใหม่
เมื่อกิจกรรมในหน้าต่างงานที่แยกเริ่มต้นกิจกรรมในงานใหม่ งานใหม่จะแยกจากงานที่มีการแยกและแสดงแบบเต็มหน้าต่าง หน้าจอ "ล่าสุด" จะแสดง 2 งาน ได้แก่ งานในโหมดแยกหน้าจอและงานใหม่
การแทนที่กิจกรรม
สามารถแทนที่กิจกรรมในสแต็กคอนเทนเนอร์รองได้ เช่น เมื่อใช้กิจกรรมหลักสำหรับการนำทางระดับบนสุดและกิจกรรมรองเป็นปลายทางที่เลือก แต่ละรายการที่เลือกจากการนำทางระดับบนสุดควร เริ่มกิจกรรมใหม่ในคอนเทนเนอร์รอง และนำกิจกรรมหรือ กิจกรรมที่อยู่ในนั้นก่อนหน้านี้ออก
หากแอปทำกิจกรรมในคอนเทนเนอร์รองไม่เสร็จเมื่อการเลือกการนำทางเปลี่ยนแปลง การนำทางย้อนกลับอาจทำให้สับสนเมื่อมีการยุบการแยก (เมื่อพับอุปกรณ์) เช่น หากคุณมีเมนูใน แผงหลัก และหน้าจอ A กับ B ซ้อนกันในแผงรอง เมื่อผู้ใช้ พับโทรศัพท์ B จะอยู่เหนือ A และ A จะอยู่เหนือเมนู เมื่อผู้ใช้ ย้อนกลับจาก B หน้า A จะปรากฏแทนเมนู
ในกรณีดังกล่าว ต้องนำหน้าจอ ก. ออกจาก Back Stack

ลักษณะการทำงานเริ่มต้นเมื่อเปิดใช้ไปด้านข้างในคอนเทนเนอร์ใหม่เหนือการแยกที่มีอยู่คือการวางคอนเทนเนอร์รองใหม่ไว้ด้านบนและเก็บคอนเทนเนอร์เก่าไว้ในสแต็กย้อนกลับ คุณสามารถกำหนดค่าการแยกเพื่อล้างคอนเทนเนอร์รองก่อนหน้าด้วย clearTop และเปิดกิจกรรมใหม่ตามปกติ
<SplitPairRule
window:clearTop="true">
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenA"/>
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>
Kotlin
inner class MenuActivity : AppCompatActivity() { fun onMenuItemSelected(selectedMenuItem: Int) { startActivity(Intent(this, classForItem(selectedMenuItem))) } }
Java
public class MenuActivity extends AppCompatActivity{ void onMenuItemSelected(int selectedMenuItem) { startActivity(new Intent(this, classForItem(selectedMenuItem))); } }
หรือจะใช้กิจกรรมรองเดียวกันก็ได้ และจากกิจกรรมหลัก (เมนู) ให้ส่ง Intent ใหม่ที่ไปยังอินสแตนซ์เดียวกัน แต่ทริกเกอร์การอัปเดตสถานะหรือ UI ในคอนเทนเนอร์รอง
การแยกหลายครั้ง
แอปสามารถให้การนำทางแบบหลายระดับได้โดยการเปิดใช้งานกิจกรรมเพิ่มเติม ที่ด้านข้าง
เมื่อกิจกรรมในคอนเทนเนอร์รองเปิดกิจกรรมใหม่ไปด้านข้าง ระบบจะ สร้างการแยกหน้าจอใหม่ทับการแยกหน้าจอที่มีอยู่
Back Stack จะมีกิจกรรมทั้งหมดที่เปิดก่อนหน้านี้ เพื่อให้ผู้ใช้ไปยังการแยก A/B ได้หลังจากทำ C เสร็จแล้ว

หากต้องการสร้างการแยกหน้าจอใหม่ ให้เปิดกิจกรรมใหม่ไปด้านข้างจากคอนเทนเนอร์รองที่มีอยู่ ประกาศการกำหนดค่าสำหรับการแยก A/B และ B/C และเปิดใช้กิจกรรม C จาก B ตามปกติ
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
<SplitPairFilter
window:primaryActivityName=".B"
window:secondaryActivityName=".C"/>
</SplitPairRule>
Kotlin
class B : AppCompatActivity() { fun onOpenC() { startActivity(Intent(this, C::class.java)) } }
Java
public class B extends AppCompatActivity{ void onOpenC() { startActivity(new Intent(this, C.class)); } }
ตอบสนองต่อการเปลี่ยนแปลงสถานะการแยก
กิจกรรมต่างๆ ในแอปอาจมีองค์ประกอบ UI ที่ทําหน้าที่เดียวกัน เช่น ตัวควบคุมที่เปิดหน้าต่างที่มีการตั้งค่าบัญชี
หากกิจกรรม 2 รายการที่มีองค์ประกอบ UI ร่วมกันอยู่ในโหมดแยกหน้าจอ การแสดงองค์ประกอบในทั้ง 2 กิจกรรมจะซ้ำซ้อนและอาจทำให้เกิดความสับสน
หากต้องการทราบว่ากิจกรรมใดอยู่ในสปลิต ให้ตรวจสอบโฟลว์ SplitController.splitInfoList หรือลงทะเบียน Listener ด้วย SplitControllerCallbackAdapter เพื่อดูการเปลี่ยนแปลงสถานะสปลิต จากนั้น
ปรับ UI ตามนั้น
Kotlin
val layout = layoutInflater.inflate(R.layout.activity_main, null) val view = layout.findViewById<View>(R.id.infoButton) lifecycleScope.launch { 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) { super.onCreate(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>
ทำกิจกรรมให้เสร็จ
ผู้ใช้สามารถทำกิจกรรมให้เสร็จได้ทั้ง 2 ด้านของการแยกหน้าจอโดยปัดจากขอบ ของจอแสดงผล
หากตั้งค่าอุปกรณ์ให้ใช้ปุ่มย้อนกลับแทนการนำทางด้วยท่าทางสัมผัส ระบบจะส่งข้อมูลไปยังกิจกรรมที่โฟกัส ซึ่งเป็นกิจกรรมที่แตะหรือ เปิดใช้ล่าสุด
ผลกระทบที่การทำกิจกรรมทั้งหมดในคอนเทนเนอร์มีต่อคอนเทนเนอร์ตรงข้าม จะขึ้นอยู่กับการกำหนดค่าการแยก
แอตทริบิวต์การกำหนดค่า
คุณสามารถระบุแอตทริบิวต์ของกฎคู่ที่แยกเพื่อกำหนดค่าวิธีที่การทำกิจกรรมทั้งหมดในด้านหนึ่งของการแยกส่งผลต่อกิจกรรมในอีกด้านหนึ่งของการแยก แอตทริบิวต์ดังกล่าวมีดังนี้
window:finishPrimaryWithSecondary— วิธีที่การทำกิจกรรมทั้งหมดในคอนเทนเนอร์รองให้เสร็จสมบูรณ์ส่งผลต่อกิจกรรมในคอนเทนเนอร์หลักwindow:finishSecondaryWithPrimary— วิธีที่การทำกิจกรรมทั้งหมดในคอนเทนเนอร์หลักให้เสร็จสมบูรณ์ส่งผลต่อกิจกรรมในคอนเทนเนอร์รอง
ค่าที่เป็นไปได้ของแอตทริบิวต์ ได้แก่
always- ทำกิจกรรมในคอนเทนเนอร์ที่เชื่อมโยงให้เสร็จเสมอnever— ไม่เคยทำกิจกรรมในคอนเทนเนอร์ที่เชื่อมโยงให้เสร็จสมบูรณ์adjacent— ทำกิจกรรมในคอนเทนเนอร์ที่เชื่อมโยงให้เสร็จเมื่อคอนเทนเนอร์ 2 รายการแสดงอยู่ติดกัน แต่จะไม่ทำเมื่อคอนเทนเนอร์ 2 รายการซ้อนกัน
เช่น
<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>


ทำกิจกรรมหลายอย่างในคอนเทนเนอร์ให้เสร็จ
หากมีกิจกรรมหลายรายการซ้อนกันในคอนเทนเนอร์แบบแยก เมื่อสิ้นสุดกิจกรรมที่อยู่ด้านล่างของสแต็ก กิจกรรมที่อยู่ด้านบนจะไม่สิ้นสุดโดยอัตโนมัติ
เช่น หากกิจกรรม 2 รายการอยู่ในคอนเทนเนอร์รอง โดยมี 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 15 (API ระดับ 35) ขึ้นไปที่รองรับโดย Jetpack WindowManager 1.4 ขึ้นไปมีฟีเจอร์แบบไดนามิกที่ช่วยให้กำหนดค่าการแยกการฝังกิจกรรมได้ ซึ่งรวมถึง
- การขยายแผง: เส้นแบ่งแบบอินเทอร์แอกทีฟที่ลากได้ช่วยให้ผู้ใช้ ปรับขนาดแผงในการนำเสนอแบบแยก
- การปักหมุดสแต็กกิจกรรม: ผู้ใช้สามารถปักหมุดเนื้อหาในคอนเทนเนอร์หนึ่งและ แยกการนำทางในคอนเทนเนอร์จากการนำทางในคอนเทนเนอร์อื่น
- การหรี่แสงแบบเต็มหน้าจอของกล่องโต้ตอบ: เมื่อแสดงกล่องโต้ตอบ แอปจะระบุได้ ว่าจะหรี่แสงทั้งหน้าต่างงานหรือเฉพาะคอนเทนเนอร์ที่เปิด กล่องโต้ตอบ
การขยายแผง
การขยายแผงช่วยให้ผู้ใช้ปรับขนาดพื้นที่หน้าจอที่จัดสรรให้กับ กิจกรรมทั้ง 2 รายการในเลย์เอาต์แบบ 2 แผงได้
หากต้องการปรับแต่งลักษณะที่ปรากฏของตัวแบ่งหน้าต่างและตั้งค่าช่วงที่ลากได้ของตัวแบ่ง ให้ทำดังนี้
สร้างอินสแตนซ์ของ
DividerAttributesปรับแต่งแอตทริบิวต์ตัวคั่น
color: สีของตัวคั่นบานหน้าต่างที่ลากได้widthDp: ความกว้างของตัวคั่นบานหน้าต่างที่ลากได้ ตั้งค่าเป็นWIDTH_SYSTEM_DEFAULTเพื่อให้ระบบกำหนดความกว้างของตัวคั่นช่วงการลาก: เปอร์เซ็นต์ขั้นต่ำของหน้าจอที่บานหน้าต่างใดบานหน้าต่างหนึ่งสามารถ ใช้ได้ อยู่ในช่วง 0.33 ถึง 0.66 ตั้งค่าเป็น
DRAG_RANGE_SYSTEM_DEFAULTเพื่อให้ระบบกำหนดช่วงการลาก
Kotlin
val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) if (WindowSdkExtensions.getInstance().extensionVersion >= 6) { splitAttributesBuilder.setDividerAttributes( DividerAttributes.DraggableDividerAttributes.Builder() .setColor(getColor(R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ) } val splitAttributes: SplitAttributes = splitAttributesBuilder.build()
Java
SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT); if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) { splitAttributesBuilder.setDividerAttributes( new DividerAttributes.DraggableDividerAttributes.Builder() .setColor(ContextCompat.getColor(this, R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ); } SplitAttributes _splitAttributes = splitAttributesBuilder.build();
การปักหมุดสแต็กกิจกรรม
การปักหมุดสแต็กกิจกรรมช่วยให้ผู้ใช้ปักหมุดหน้าต่างที่แยกไว้หน้าต่างหนึ่งได้ เพื่อให้กิจกรรมยังคงอยู่เช่นเดิมขณะที่ผู้ใช้ไปยังส่วนต่างๆ ในหน้าต่างอื่น การปักหมุดสแต็กกิจกรรม ช่วยให้ประสบการณ์การทำงานแบบมัลติทาสก์ดียิ่งขึ้น
หากต้องการเปิดใช้การปักหมุดสแต็กกิจกรรมในแอป ให้ทำดังนี้
เพิ่มปุ่มลงในไฟล์เลย์เอาต์ของกิจกรรมที่ต้องการปักหมุด เช่น กิจกรรมรายละเอียดของเลย์เอาต์รายการ-รายละเอียด
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/detailActivity" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" tools:context=".DetailActivity"> <TextView android:id="@+id/textViewItemDetail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="36sp" android:textColor="@color/obsidian" app:layout_constraintBottom_toTopOf="@id/pinButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.appcompat.widget.AppCompatButton android:id="@+id/pinButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/pin_this_activity" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/> </androidx.constraintlayout.widget.ConstraintLayout>ใน
onCreate()เมธอดของกิจกรรม ให้ตั้งค่าเครื่องมือฟัง onclick บนปุ่มKotlin
val pinButton: Button = findViewById(R.id.pinButton) pinButton.setOnClickListener { val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.66f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build() val pinSplitRule = SplitPinRule.Builder() .setSticky(true) .setDefaultSplitAttributes(splitAttributes) .build() SplitController.getInstance(applicationContext) .pinTopActivityStack(taskId, pinSplitRule) }
Java
Button pinButton = findViewById(R.id.pinButton); pinButton.setOnClickListener( (view) -> { SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.66f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build(); SplitPinRule pinSplitRule = new SplitPinRule.Builder() .setSticky(true) .setDefaultSplitAttributes(splitAttributes) .build(); SplitController.getInstance(getApplicationContext()) .pinTopActivityStack(getTaskId(), pinSplitRule); });
กล่องโต้ตอบแบบเต็มหน้าจอ
โดยปกติแล้ว กิจกรรมจะหรี่จอแสดงผลเพื่อดึงดูดความสนใจไปยังกล่องโต้ตอบ ใน การฝังกิจกรรม บานหน้าต่างทั้ง 2 ของจอแสดงผลแบบ 2 บานหน้าต่างควรหรี่แสง ไม่ใช่ เฉพาะบานหน้าต่างที่มีกิจกรรมที่เปิดกล่องโต้ตอบ เพื่อให้ได้ประสบการณ์ UI ที่เป็นหนึ่งเดียว
เมื่อใช้ WindowManager 1.4 ขึ้นไป หน้าต่างแอปทั้งหมดจะหรี่ลงโดยค่าเริ่มต้นเมื่อ
เปิดกล่องโต้ตอบ (ดู EmbeddingConfiguration.DimAreaBehavior.ON_TASK)
หากต้องการหรี่เฉพาะคอนเทนเนอร์ของกิจกรรมที่เปิดกล่องโต้ตอบ ให้ใช้
EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK
แยกกิจกรรมจากหน้าต่างที่แยกเป็นหน้าต่างเต็ม
สร้างการกำหนดค่าใหม่ที่แสดงกิจกรรมด้านข้างแบบเต็มหน้าต่าง แล้ว เปิดกิจกรรมอีกครั้งด้วย Intent ที่จะแสดงผลเป็นอินสแตนซ์เดียวกัน
ตรวจสอบการรองรับการแยกที่รันไทม์
การฝังกิจกรรมใช้ได้ใน 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) สามารถฝังกิจกรรมเป็นฟังก์ชันของระบบอุปกรณ์ได้ ระบบจะระบุกฎการแยกสำหรับแอปแบบหลายกิจกรรม ซึ่งจะลบล้างลักษณะการทำงานของ การจัดหน้าต่างของแอป การลบล้างของระบบจะบังคับให้แอปแบบหลายกิจกรรมเข้าสู่โหมดการฝังกิจกรรมที่ระบบกำหนด
การฝังกิจกรรมของระบบช่วยปรับปรุงการนำเสนอแอปผ่านเลย์เอาต์แบบหลายบานหน้าต่าง เช่น รายการ-รายละเอียด โดยไม่ต้องเปลี่ยนแปลงแอป อย่างไรก็ตาม การฝังกิจกรรมของระบบ อาจทำให้เลย์เอาต์ของแอปไม่ถูกต้อง มีข้อบกพร่อง หรือ ขัดแย้งกับการฝังกิจกรรมที่แอปใช้
แอปของคุณสามารถป้องกันหรืออนุญาตการฝังกิจกรรมของระบบได้โดยการตั้งค่า
PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE ในไฟล์ Manifest ของแอป เช่น
<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>
ชื่อพร็อพเพอร์ตี้กำหนดไว้ในออบเจ็กต์ WindowProperties
ของ Jetpack WindowManager ตั้งค่าเป็น false หากแอปของคุณใช้การฝังกิจกรรม หรือหากคุณต้องการป้องกันไม่ให้ระบบใช้กฎการฝังกิจกรรมกับแอปของคุณ ตั้งค่าเป็น true เพื่ออนุญาตให้ระบบใช้การฝังกิจกรรมที่ระบบกำหนดกับแอปของคุณ
ข้อจำกัดและคำเตือน
- เฉพาะแอปโฮสต์ของงานเท่านั้น ซึ่งระบุว่าเป็นเจ้าของกิจกรรมรูท ในงาน จึงจะจัดระเบียบและฝังกิจกรรมอื่นๆ ในงานได้ หากกิจกรรมที่รองรับการฝังและการแยกทำงานในงานที่เป็นของแอปพลิเคชันอื่น การฝังและการแยกจะไม่ทำงานสำหรับกิจกรรมเหล่านั้น
- คุณจัดระเบียบกิจกรรมได้ภายในงานเดียวเท่านั้น การเปิดกิจกรรม ในงานใหม่จะทำให้กิจกรรมนั้นอยู่ในหน้าต่างใหม่ที่ขยายออกนอกการแยกหน้าจอที่มีอยู่เสมอ
- จัดระเบียบและวางกิจกรรมในโหมดแยกได้เฉพาะกิจกรรมในกระบวนการเดียวกัน
SplitInfoจะรายงานเฉพาะกิจกรรมที่อยู่ใน กระบวนการเดียวกันเท่านั้น เนื่องจากไม่มีวิธีทราบกิจกรรมใน กระบวนการอื่น - กฎกิจกรรมแต่ละคู่หรือกฎกิจกรรมเดียวจะมีผลกับการเปิดตัวกิจกรรมที่เกิดขึ้นหลังจากลงทะเบียนกฎแล้วเท่านั้น ขณะนี้ยังไม่มีวิธี อัปเดตการแยกเพลงที่มีอยู่หรือพร็อพเพอร์ตี้ภาพของเพลง
- การกำหนดค่าตัวกรองคู่ที่แยกต้องตรงกับ Intent ที่ใช้เมื่อ เปิดกิจกรรมจนเสร็จสมบูรณ์ การจับคู่จะเกิดขึ้นเมื่อมีการเริ่มกิจกรรมใหม่จากกระบวนการของแอปพลิเคชัน ดังนั้นจึงอาจไม่ทราบชื่อคอมโพเนนต์ที่ได้รับการแก้ไขในภายหลังในกระบวนการของระบบเมื่อใช้ Intent โดยนัย หากไม่ทราบชื่อคอมโพเนนต์ในเวลาที่เปิดตัว คุณสามารถใช้ อักขระไวด์การ์ดแทนได้ ("*/*") และทำการกรองตาม การดำเนินการของ Intent ได้
- ปัจจุบันยังไม่มีวิธีในการย้ายกิจกรรมระหว่างคอนเทนเนอร์หรือเข้าและออกจากสปลิตหลังจากที่สร้างแล้ว ไลบรารี WindowManager จะสร้างการแยกเมื่อมีการเปิดใช้งานกิจกรรมใหม่ที่มีกฎที่ตรงกันเท่านั้น และจะทำลายการแยกเมื่อกิจกรรมสุดท้ายในคอนเทนเนอร์การแยกเสร็จสิ้น
- กิจกรรมจะเปิดตัวอีกครั้งได้เมื่อมีการเปลี่ยนแปลงการกำหนดค่า ดังนั้นเมื่อมีการสร้างหรือนำการแยกออก และขอบเขตของกิจกรรมเปลี่ยนแปลง กิจกรรมจะถูกทำลายอินสแตนซ์ก่อนหน้าอย่างสมบูรณ์และสร้างอินสแตนซ์ใหม่ ด้วยเหตุนี้ นักพัฒนาแอปจึงควรระมัดระวังเรื่องต่างๆ เช่น การเปิดตัวกิจกรรมใหม่จากโค้ดเรียกกลับของวงจร
- อุปกรณ์ต้องมีอินเทอร์เฟซส่วนขยายหน้าต่างเพื่อรองรับการฝังกิจกรรม อุปกรณ์หน้าจอขนาดใหญ่เกือบทั้งหมดที่ใช้ Android 12L (API ระดับ 32) ขึ้นไปจะมีอินเทอร์เฟซนี้ อย่างไรก็ตาม อุปกรณ์หน้าจอขนาดใหญ่บางรุ่นที่ ไม่สามารถเรียกใช้กิจกรรมหลายอย่างพร้อมกันได้จะไม่มีอินเทอร์เฟซส่วนขยายหน้าต่าง หากอุปกรณ์หน้าจอขนาดใหญ่ไม่รองรับโหมดหลายหน้าต่าง ก็อาจไม่รองรับการฝังกิจกรรม
แหล่งข้อมูลเพิ่มเติม
- Codelabs
- เส้นทางการเรียนรู้ - การฝังกิจกรรม
- แอปตัวอย่าง - activity-embedding