การฝังกิจกรรม

การฝังกิจกรรมจะเพิ่มประสิทธิภาพแอปในอุปกรณ์หน้าจอขนาดใหญ่โดยการแยกหน้าต่างงานของแอปพลิเคชันระหว่างกิจกรรม 2 รายการหรือ 2 อินสแตนซ์ของกิจกรรมเดียวกัน

รูปที่ 1 แอปการตั้งค่าที่มีกิจกรรมแสดงอยู่เคียงข้างกัน

หากแอปประกอบด้วยหลายกิจกรรม การฝังกิจกรรมจะช่วยให้คุณทำสิ่งต่อไปนี้ได้ เพื่อมอบประสบการณ์ของผู้ใช้ที่ดียิ่งขึ้นบนแท็บเล็ต อุปกรณ์แบบพับได้ และอุปกรณ์ ChromeOS

การฝังกิจกรรมไม่จําเป็นต้องเปลี่ยนรูปแบบโค้ด คุณกำหนดวิธีที่แอปแสดงกิจกรรมได้ ไม่ว่าจะเป็นแสดงควบคู่กันหรือซ้อนกัน โดยการสร้างไฟล์การกำหนดค่า XML หรือการเรียกใช้ Jetpack WindowManager API

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

การฝังกิจกรรมรองรับการเปลี่ยนการวางแนวอุปกรณ์และทำงานได้อย่างราบรื่น บนอุปกรณ์แบบพับได้ การซ้อนและไม่เก็บกิจกรรมใดๆ ขณะอุปกรณ์พับ กางออก

การฝังกิจกรรมใช้ได้ในอุปกรณ์หน้าจอขนาดใหญ่ส่วนใหญ่ที่ใช้ Android 12L (API ระดับ 32) ขึ้นไป

หน้าต่างแยกงาน

กิจกรรมที่ฝังจะแยกหน้าต่างงานของแอปออกเป็น 2 คอนเทนเนอร์ ได้แก่ หลักและ รอง คอนเทนเนอร์มีกิจกรรมที่เปิดจากกิจกรรมหลักหรือ จากกิจกรรมอื่นๆ ที่มีอยู่แล้วในคอนเทนเนอร์

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

การฝังกิจกรรมช่วยให้คุณแสดงกิจกรรมได้หลายวิธี บัญชี แอปสามารถแยกหน้าต่างงานด้วยการเปิดกิจกรรม 2 รายการพร้อมกัน พร้อมกัน:

รูปที่ 2 กิจกรรม 2 รายการแสดงคู่กัน

หรือกิจกรรมที่ครอบครองหน้าต่างงานทั้งหน้าต่างอาจสร้างการแยกโดยการเปิดกิจกรรมใหม่ควบคู่ไปด้วย

รูปที่ 3 กิจกรรม ก เริ่มกิจกรรม ข ไว้ด้านข้าง

กิจกรรมที่แยกอยู่และแชร์หน้าต่างงานอยู่แล้วสามารถเปิดกิจกรรมอื่นๆ ได้ดังนี้

  • ที่ด้านข้างของกิจกรรมอื่น ให้ทำดังนี้

    รูปที่ 4 กิจกรรม ก เริ่มต้นกิจกรรม C ที่ด้านข้างกิจกรรม ข
  • ไปด้านข้าง แล้วเลื่อนการแยกไปด้านข้างเพื่อซ่อนกิจกรรมหลักก่อนหน้า

    รูปที่ 5 กิจกรรม ข. เริ่มกิจกรรม ค. ทางด้านข้างและเลื่อนการแยกไปด้านข้าง
  • เปิดกิจกรรมที่ด้านบน ซึ่งก็คือในสแต็กกิจกรรมเดียวกัน

    รูปที่ 6 กิจกรรม ข เริ่มกิจกรรม ค โดยไม่มี Flag Intent เพิ่มเติม
  • เปิดกิจกรรมแบบเต็มหน้าต่างในงานเดียวกัน

    รูปที่ 7 กิจกรรม A หรือกิจกรรม B เริ่มต้นกิจกรรม C ที่เติม หน้าต่างงาน

การนำทางย้อนกลับ

แอปพลิเคชันประเภทต่างๆ อาจมีกฎการนำทางย้อนกลับที่แตกต่างกันใน สถานะของหน้าต่างงานแยกโดยขึ้นอยู่กับทรัพยากร Dependency ระหว่างกิจกรรมหรือวิธีการ ผู้ใช้จะทำให้เกิดเหตุการณ์การย้อนกลับ เช่น

  • แสดงร่วมกัน: หากกิจกรรมมีความเกี่ยวข้องกันและไม่ควรแสดงกิจกรรมหนึ่งโดยไม่มีอีกกิจกรรมหนึ่ง คุณจะกำหนดค่าการไปยังส่วนหลังเพื่อให้ดูทั้ง 2 กิจกรรมจนจบได้
  • ไปคนเดียว: หากกิจกรรมเป็นอิสระอย่างสิ้นเชิง ให้ย้อนกลับบน กิจกรรมจะไม่ส่งผลต่อสถานะของกิจกรรมอื่นในหน้าต่างงาน

ระบบจะส่งเหตุการณ์ย้อนกลับไปยังกิจกรรมที่โฟกัสล่าสุดเมื่อใช้ปุ่ม การนำทาง

สำหรับการนำทางด้วยท่าทางสัมผัส ให้ทำดังนี้

  • Android 14 (API ระดับ 34) และต่ำกว่า — ระบบจะส่งเหตุการณ์ "กลับ" ไปยังกิจกรรมที่เกิดท่าทางสัมผัส เมื่อผู้ใช้ปัดจากด้านซ้ายของหน้าจอ ระบบจะส่งเหตุการณ์ "กลับ" ไปยังกิจกรรมในแผงด้านซ้ายของหน้าต่างที่แยก เมื่อผู้ใช้ปัดจากด้านขวาของ เหตุการณ์ย้อนกลับจะส่งไปยังกิจกรรมในแผงด้านขวา

  • Android 15 (API ระดับ 35) ขึ้นไป

    • เมื่อจัดการกับกิจกรรมหลายอย่างจากแอปเดียวกัน ท่าทางสัมผัส สิ้นสุดกิจกรรมยอดนิยมไม่ว่าจะอยู่ในทิศทางใด ประสบการณ์ที่เป็นหนึ่งเดียวมากขึ้น

    • ในสถานการณ์ที่เกี่ยวข้องกับ 2 กิจกรรมจากแอปที่ต่างกัน (โฆษณาซ้อนทับ) เหตุการณ์ "ย้อนกลับ" จะไปยังกิจกรรมล่าสุดในโฟกัสโดยสอดคล้องกับ ลักษณะการทำงานของการนำทางด้วยปุ่ม

เลย์เอาต์แบบหลายหน้าต่าง

Jetpack WindowManager ช่วยให้คุณสร้างกิจกรรมที่ฝังเลย์เอาต์หลายแผงบนอุปกรณ์หน้าจอขนาดใหญ่ที่ใช้ Android 12L (API ระดับ 32) ขึ้นไป และในอุปกรณ์บางรุ่นที่ใช้แพลตฟอร์มเวอร์ชันเก่า แอปที่มีอยู่ซึ่งอิงตาม กิจกรรมหลายอย่าง แทนที่จะเป็นส่วนย่อยหรือเลย์เอาต์ที่อิงตามมุมมอง เช่น SlidingPaneLayout สามารถช่วยปรับปรุงประสบการณ์ของผู้ใช้ที่มีหน้าจอขนาดใหญ่ โดยไม่ต้องเปลี่ยนโครงสร้างภายในโค้ดต้นฉบับ

ตัวอย่างที่พบบ่อยอย่างหนึ่งคือการแยกรายการเป็นรายละเอียด ระบบจะเริ่มกิจกรรมรายการ จากนั้นแอปพลิเคชันจะเริ่มกิจกรรมแบบละเอียดทันทีเพื่อให้การนำเสนอมีคุณภาพสูง ระบบการเปลี่ยนจะรอจนกว่า กิจกรรมจะถูกวาด แล้วแสดงพร้อมกัน สำหรับผู้ใช้ มีทั้ง 2 ประเภท รวมเป็นหนึ่ง

รูปที่ 8 กิจกรรม 2 อย่างเริ่มต้นพร้อมกันในหลายหน้าต่าง เลย์เอาต์

แอตทริบิวต์แยก

คุณสามารถระบุสัดส่วนของหน้าต่างงานระหว่างคอนเทนเนอร์ที่แบ่งได้ และการจัดวางคอนเทนเนอร์ให้สัมพันธ์กัน

สำหรับกฎที่กําหนดไว้ในไฟล์การกําหนดค่า XML ให้ตั้งค่าแอตทริบิวต์ต่อไปนี้

  • splitRatio: กำหนดสัดส่วนคอนเทนเนอร์ ค่าคือตัวเลขทศนิยมในช่วงเปิด (0.0, 1.0)
  • splitLayoutDirection: ระบุวิธีจัดเลย์เอาต์คอนเทนเนอร์แบบแยก ที่เกี่ยวข้องกัน ค่าต่างๆ ได้แก่
    • ltr: จากซ้ายไปขวา
    • rtl: จากขวาไปซ้าย
    • locale: ระบุ ltr หรือ rtl จากการตั้งค่าภาษา

ดูตัวอย่างได้ที่ส่วนการกําหนดค่า XML

สําหรับกฎที่สร้างโดยใช้ WindowManager API ให้สร้างออบเจ็กต์ SplitAttributes ด้วย SplitAttributes.Builder แล้วเรียกใช้เมธอดผู้สร้างต่อไปนี้

  • setSplitType(): กำหนดสัดส่วนของคอนเทนเนอร์ที่แยก ดูอาร์กิวเมนต์ที่ถูกต้อง รวมถึงเมธอด SplitAttributes.SplitType.ratio() ใน SplitAttributes.SplitType
  • setLayoutDirection(): ตั้งค่าเลย์เอาต์ของคอนเทนเนอร์ โปรดดูค่าที่เป็นไปได้ใน SplitAttributes.LayoutDirection

โปรดดูตัวอย่างในส่วน WindowManager API

รูปที่ 9 การแบ่งกิจกรรม 2 แบบวางจากซ้ายไปขวาแต่มีอัตราส่วนการแบ่งแตกต่างกัน

ตัวยึดตําแหน่ง

กิจกรรมตัวยึดตำแหน่งคือกิจกรรมรองที่ว่างเปล่าซึ่งใช้พื้นที่ของ แยกกิจกรรม ในท้ายที่สุด กิจกรรมเหล่านี้จะแทนที่ด้วยกิจกรรมอื่น ที่มีเนื้อหา ตัวอย่างเช่น กิจกรรมตัวยึดตําแหน่งอาจอยู่ด้านรองของกิจกรรมที่แยกในเลย์เอาต์รายการแบบละเอียดจนกว่าจะมีการเลือกรายการจากรายการ เมื่อถึงจุดนั้น กิจกรรมที่มีข้อมูลรายละเอียดสําหรับรายการในรายการที่เลือกจะแทนที่ตัวยึดตําแหน่ง

โดยค่าเริ่มต้น ระบบจะแสดงตัวยึดตำแหน่งเฉพาะเมื่อมีพื้นที่เพียงพอสำหรับ การแยกกิจกรรม ตัวยึดตําแหน่งจะสิ้นสุดโดยอัตโนมัติเมื่อขนาดการแสดงผลเปลี่ยนไปเป็นความกว้างหรือความสูงที่เล็กเกินกว่าที่จะแสดงการแยก เมื่อมีพื้นที่เพียงพอ ระบบจะเปิดใช้งานตัวยึดตำแหน่งอีกครั้งโดยมีสถานะเริ่มต้นอีกครั้ง

รูปที่ 10 อุปกรณ์แบบพับได้กำลังพับและกางออก กิจกรรมตัวยึดตําแหน่งเสร็จสิ้นและสร้างขึ้นใหม่เมื่อขนาดการแสดงผลเปลี่ยนแปลง

อย่างไรก็ตาม แอตทริบิวต์ stickyPlaceholder ของเมธอด SplitPlaceholderRule หรือ setSticky() ของ SplitPlaceholder.Builder สามารถลบล้างลักษณะการทำงานเริ่มต้นได้ เมื่อแอตทริบิวต์หรือเมธอดระบุค่าเป็น true ระบบจะแสดงตัวยึดตําแหน่งเป็นกิจกรรมบนสุดในหน้าต่างงานเมื่อปรับขนาดการแสดงผลเป็นหน้าจอเดียวจากหน้าจอแบบ 2 แผง (ดูตัวอย่างที่การกําหนดค่าแบบแยก)

รูปที่ 11 อุปกรณ์แบบพับได้กำลังพับและกางออก กิจกรรมตัวยึดตําแหน่งเกาะติดอยู่

การเปลี่ยนแปลงขนาดหน้าต่าง

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

กิจกรรมตัวยึดตําแหน่งจะแสดงเฉพาะเมื่อมีการแสดงผลกว้างพอสําหรับการแยก ในหน้าจอขนาดเล็ก ตัวยึดตำแหน่งจะปิดโดยอัตโนมัติ เมื่อ พื้นที่แสดงผลมีขนาดใหญ่พออีกครั้ง ตัวยึดตำแหน่งจะได้รับการสร้างขึ้นใหม่ (โปรดดู ตัวยึดตำแหน่ง)

การวางซ้อนกิจกรรมเป็นไปได้เนื่องจาก WindowManager จัดลําดับกิจกรรมในแผงรองเหนือกิจกรรมในแผงหลักตามลําดับ Z

กิจกรรมหลายรายการในแผงรอง

กิจกรรม ข เริ่มกิจกรรม ค แทนโดยไม่มี Flag Intent เพิ่มเติม

การแยกกิจกรรมที่มีกิจกรรม ก ข และ ค โดยที่ ค วางซ้อนอยู่ด้านบนของ ข

ส่งผลให้เกิด z-order ต่อไปนี้ของกิจกรรมในงานเดียวกัน

สแต็กกิจกรรมรองที่มีกิจกรรม ค. ซ้อนอยู่ด้านบนของ ข.
          สแต็กรองซ้อนอยู่ด้านบนของสแต็กกิจกรรมชั่วคราว
          มีกิจกรรม A

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

หน้าต่างขนาดเล็กแสดงเฉพาะกิจกรรม C

การย้อนกลับในหน้าต่างขนาดเล็กจะไปยังส่วนต่างๆ ของกิจกรรมที่ซ้อนกันอยู่ ทับกัน

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

การแยกส่วนแบบซ้อน

กิจกรรม ข เริ่มต้นกิจกรรม C ไปด้านข้าง และเปลี่ยนส่วนที่แยกออกด้านข้าง

หน้าต่างงานแสดงกิจกรรม ก และ ข ตามด้วยกิจกรรม ข และ ค

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

กิจกรรม A, B และ C ในกลุ่มเดียว กิจกรรมจะเรียงซ้อนกัน
          ตามลำดับต่อไปนี้จากบนลงล่าง: C, B และ A

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

หน้าต่างขนาดเล็กที่แสดงเฉพาะกิจกรรม C

การวางแนวตั้งแบบคงที่

การตั้งค่าไฟล์ Manifest android:screenOrientation ช่วยให้แอปจำกัดกิจกรรมให้เป็นแนวตั้งหรือแนวนอนได้ ผู้ผลิตอุปกรณ์ (OEM) สามารถละเว้นคำขอการวางแนวหน้าจอและแสดงแอปในแนวตั้งบนจอแสดงผลแนวนอนหรือแนวนอนบนจอแสดงผลแนวตั้งได้ เพื่อปรับปรุงประสบการณ์ของผู้ใช้บนอุปกรณ์หน้าจอขนาดใหญ่ เช่น แท็บเล็ตและอุปกรณ์แบบพับได้

รูปที่ 12 กิจกรรมที่มีแถบดำด้านบน-ล่างของภาพ: แนวตั้งคงที่ในอุปกรณ์แนวนอน (ซ้าย), แนวนอนแบบคงที่ในอุปกรณ์แนวตั้ง (ขวา)

ในทำนองเดียวกัน เมื่อเปิดใช้การฝังกิจกรรม OEM จะปรับแต่งอุปกรณ์ให้แสดงกิจกรรมในแนวตั้งแบบ Letterbox แบบคงที่ในแนวนอนบนหน้าจอขนาดใหญ่ได้ (ความกว้าง ≥ 600dp) เมื่อกิจกรรมแนวตั้งแบบคงที่เปิดกิจกรรมที่ 2 อุปกรณ์จะแสดงกิจกรรมทั้ง 2 รายการควบคู่กันในการแสดงผลแบบ 2 แผง

รูปที่ 13 กิจกรรม A แบบภาพแนวตั้งแบบคงที่เริ่มกิจกรรม B ไว้ด้านข้าง

เพิ่มพร็อพเพอร์ตี้ android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED ลงในไฟล์ Manifest ของแอปเสมอเพื่อแจ้งให้อุปกรณ์ทราบว่าแอปของคุณรองรับการฝังกิจกรรม (ดูส่วนการกำหนดค่าแบบแยก) จากนั้นอุปกรณ์ที่ OEM ออกแบบเองก็สามารถกำหนดได้ว่าจะใช้แถบดำด้านบน-ล่างของภาพหรือไม่ กิจกรรมภาพบุคคลแบบตายตัว

การกำหนดค่าการแยก

กฎการแยกจะกำหนดค่าการแยกกิจกรรม คุณกำหนดกฎการแยกในไฟล์การกำหนดค่า XML หรือโดยการเรียกใช้ WindowManager API ของ Jetpack

ไม่ว่าในกรณีใด แอปของคุณต้องเข้าถึงไลบรารี WindowManager และต้องแจ้งให้ระบบทราบว่าแอปได้ติดตั้งใช้งานการฝังกิจกรรมแล้ว

โดยทำดังนี้

  1. เพิ่มทรัพยากร Dependency ของไลบรารี WindowManager เวอร์ชันล่าสุดลงในไฟล์ build.gradle ระดับโมดูลของแอป เช่น

    implementation 'androidx.window:window:1.1.0-beta02'

    ไลบรารี WindowManager มีคอมโพเนนต์ทั้งหมดที่จําเป็นสําหรับการฝังกิจกรรม

  2. แจ้งให้ระบบทราบว่าแอปของคุณใช้การฝังกิจกรรม

    เพิ่มพร็อพเพอร์ตี้ android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED ใน<แอปพลิเคชัน> ของไฟล์ Manifest ของแอป และตั้งค่า เป็นจริง เช่น

    <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 ให้ทําตามขั้นตอนต่อไปนี้

  1. สร้างไฟล์ทรัพยากร 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>
    
  2. สร้างตัวเริ่มต้น

    คอมโพเนนต์ WindowManager RuleController จะแยกวิเคราะห์ XML ไฟล์การกำหนดค่าเซิร์ฟเวอร์ และทำให้กฎพร้อมใช้งานกับระบบ ไลบรารี Startup ของ Jetpack Initializer จะทำให้ไฟล์ XML พร้อมใช้งานสำหรับ RuleController เมื่อแอปเริ่มต้นขึ้นเพื่อให้กฎมีผลเมื่อกิจกรรมใดๆ เริ่มต้นขึ้น

    หากต้องการสร้างโปรแกรมเริ่มต้น ให้ทำดังนี้

    1. เพิ่มทรัพยากร Dependency ของไลบรารี Jetpack Startup เวอร์ชันล่าสุดลงในไฟล์ build.gradle ระดับโมดูล เช่น

      implementation 'androidx.startup:startup-runtime:1.1.1'

    2. สร้างคลาสที่ใช้อินเทอร์เฟซ Initializer

      โปรแกรมเริ่มต้นทำให้ RuleController ใช้กฎการแยกได้โดย การส่ง ID ของไฟล์การกำหนดค่า 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();
           }
      }
  3. สร้างผู้ให้บริการเนื้อหาสําหรับคําจํากัดความของกฎ

    เพิ่ม 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() ของแอป ดังนั้น กฎการแยกจะอยู่ใน จะมีผลเมื่อกิจกรรมหลักของแอปเริ่มขึ้น

API ของ WindowManager

คุณสามารถใช้การฝังกิจกรรมแบบเป็นโปรแกรมด้วย API จำนวนไม่มาก เรียกใช้ในเมธอด onCreate() ของคลาสย่อยของ Application เพื่อตรวจสอบว่ากฎมีผลก่อนกิจกรรมใดๆ เปิดใช้งาน

หากต้องการสร้างการแยกกิจกรรมแบบเป็นโปรแกรม ให้ทําดังนี้

  1. สร้างกฎการแยก

    1. สร้าง 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
      );
    2. เพิ่มตัวกรองลงในชุดตัวกรอง

      Kotlin

      val filterSet = setOf(splitPairFilter)

      Java

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
    3. สร้างแอตทริบิวต์เลย์เอาต์สำหรับการแยก

      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(): ระบุวิธีที่คอนเทนเนอร์กิจกรรม วางเรียงๆ กัน โดยแสดงคอนเทนเนอร์หลักก่อน
    4. สร้าง 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) ที่ ขนาดเล็กจากขนาดที่แสดง 2 ขนาดต้องเปิดการใช้งาน แยกโดยไม่คำนึงถึงการวางแนวของอุปกรณ์
      • setMaxAspectRatioInPortrait(): ตั้งค่าสัดส่วนการแสดงผลสูงสุด (ความสูง:ความกว้าง) ในแนวตั้งที่จะแสดงการแยกกิจกรรม หากสัดส่วนภาพของการแสดงผลแนวตั้ง เกินสัดส่วนภาพสูงสุด การแยกจะปิดใช้งานโดยไม่คำนึงถึง ความกว้างของการแสดงผล หมายเหตุ: ค่าเริ่มต้นคือ 1.4 ซึ่ง ส่งผลให้เกิดกิจกรรมที่ใช้เวลาทั้งหน้าต่างงานในแนวตั้ง ของแท็บเล็ตส่วนใหญ่ โปรดดูSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT และsetMaxAspectRatioInLandscape() ด้วย ค่าเริ่มต้นสำหรับแนวนอนคือ ALWAYS_ALLOW
      • setFinishPrimaryWithSecondary(): ตั้งค่าว่าการทำกิจกรรมทั้งหมดในคอนเทนเนอร์รองส่งผลต่อกิจกรรมในคอนเทนเนอร์หลักอย่างไร NEVER บ่งบอกว่าระบบไม่ควรสิ้นสุดกิจกรรมหลักเมื่อกิจกรรมทั้งหมดในคอนเทนเนอร์รองสิ้นสุด (ดูสิ้นสุดกิจกรรม)
      • setFinishSecondaryWithPrimary(): ตั้งค่าว่าการทำกิจกรรมทั้งหมดในคอนเทนเนอร์หลักส่งผลต่อกิจกรรมในคอนเทนเนอร์รองอย่างไร ALWAYS บ่งบอกว่าระบบควรทำกิจกรรมในคอนเทนเนอร์รองให้เสร็จสิ้นเสมอเมื่อกิจกรรมทั้งหมดในคอนเทนเนอร์หลักเสร็จสิ้น (ดูทำกิจกรรมให้เสร็จสิ้น)
      • setClearTop(): ระบุว่ากิจกรรมทั้งหมดในคอนเทนเนอร์รองจะสิ้นสุดลงหรือไม่เมื่อมีการเริ่มกิจกรรมใหม่ในคอนเทนเนอร์ ค่า false ระบุว่ากิจกรรมใหม่ วางซ้อนบนกิจกรรมที่มีอยู่แล้วในคอนเทนเนอร์รอง
    5. รับอินสแตนซ์แบบ Singleton ของ WindowManager RuleController และเพิ่มกฎ

      Kotlin

        val ruleController = RuleController.getInstance(this)
        ruleController.addRule(splitPairRule)
        

      Java

        RuleController ruleController = RuleController.getInstance(this);
        ruleController.addRule(splitPairRule);
        
  2. สร้างตัวยึดตำแหน่งสำหรับคอนเทนเนอร์รองเมื่อไม่มีเนื้อหา

    1. สร้าง ActivityFilter ที่ระบุกิจกรรมที่มี ตัวยึดตำแหน่งจะแชร์การแยกหน้าต่างงาน ดังนี้

      Kotlin

      val placeholderActivityFilter = ActivityFilter(
          ComponentName(this, ListActivity::class.java),
          null
      )

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
    2. เพิ่มตัวกรองลงในชุดตัวกรอง

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
    3. สร้าง SplitPlaceholderRule

      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) ที่ขนาดการแสดงผลที่เล็กกว่าของ 2 ขนาดต้องมีขนาดเท่าใดจึงจะแยกหน้าจอได้ ไม่ว่าจะปรับแนวของอุปกรณ์เป็นแนวตั้งหรือแนวนอน
      • setMaxAspectRatioInPortrait(): ตั้งค่าสัดส่วนการแสดงผลสูงสุด (ความสูง:ความกว้าง) ในแนวตั้งสำหรับการแสดงการแยกกิจกรรม หมายเหตุ: ค่าเริ่มต้นคือ 1.4 ซึ่งจะทำให้กิจกรรมเต็มหน้าต่างงานในแนวตั้งบนแท็บเล็ตส่วนใหญ่ โปรดดูSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT และsetMaxAspectRatioInLandscape() ด้วย ค่าเริ่มต้นสำหรับแนวนอนคือ ALWAYS_ALLOW
      • setFinishPrimaryWithPlaceholder(): ตั้งค่าว่าการดำเนินการตัวยึดตำแหน่งให้เสร็จสิ้นส่งผลต่อกิจกรรมอย่างไร ในคอนเทนเนอร์หลัก "ALWAYS" เป็นการระบุว่าระบบควร ทำกิจกรรมในคอนเทนเนอร์หลักให้เสร็จเมื่อตัวยึดตำแหน่ง เสร็จสิ้น (ดูสิ้นสุดกิจกรรม)
      • setSticky(): กำหนดว่ากิจกรรมตัวยึดตำแหน่งจะปรากฏที่ด้านบนของกองกิจกรรมบนจอแสดงผลขนาดเล็กหรือไม่ เมื่อตัวยึดตำแหน่งปรากฏขึ้นครั้งแรกในการแยกที่มีความกว้างขั้นต่ำเพียงพอ
    4. เพิ่มกฎใน WindowManager RuleController:

      Kotlin

      ruleController.addRule(splitPlaceholderRule)

      Java

      ruleController.addRule(splitPlaceholderRule);
  3. ระบุกิจกรรมที่ไม่ควรเป็นส่วนหนึ่งของการแยก

    1. สร้าง ActivityFilter ที่ระบุกิจกรรมที่ควรใช้พื้นที่แสดงงานทั้งหมดเสมอ โดยทำดังนี้

      Kotlin

      val expandedActivityFilter = ActivityFilter(
        ComponentName(this, ExpandedActivity::class.java),
        null
      )

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
    2. เพิ่มตัวกรองลงในชุดตัวกรอง

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
    3. สร้าง ActivityRule:

      Kotlin

      val activityRule = ActivityRule.Builder(expandedActivityFilterSet)
          .setAlwaysExpand(true)
          .build()

      Java

      ActivityRule activityRule = new ActivityRule.Builder(
          expandedActivityFilterSet
      ).setAlwaysExpand(true)
       .build();

      ActivityRule.Builder สร้างและกําหนดค่ากฎ

      • expandedActivityFilterSet: มีตัวกรองกิจกรรมที่กําหนดเวลาที่จะใช้กฎโดยระบุกิจกรรมที่คุณต้องการยกเว้นจากการแยก
      • setAlwaysExpand(): ระบุว่ากิจกรรมควรเติมโฆษณาหรือไม่ หน้าต่างงานทั้งหมด
    4. เพิ่มกฎลงใน WindowManager RuleController

      Kotlin

      ruleController.addRule(activityRule)

      Java

      ruleController.addRule(activityRule);

การฝังข้ามแอปพลิเคชัน

ใน Android 13 (API ระดับ 33) ขึ้นไป แอปจะฝังกิจกรรมจากแอปอื่นๆ ได้ การฝังกิจกรรมข้ามแอปหรือข้าม UID ช่วยในการผสานรวมภาพกิจกรรมจากแอปพลิเคชัน Android หลายรายการ ระบบจะแสดงกิจกรรมของแอปโฮสต์และกิจกรรมที่ฝังจาก แอปอื่นที่อยู่บนหน้าจอข้างๆ หรือด้านบนและด้านล่างเช่นเดียวกับในแอปเดียว กิจกรรมที่ฝัง

ตัวอย่างเช่น แอปการตั้งค่าอาจฝังกิจกรรมตัวเลือกวอลเปเปอร์จาก แอป Wallpaper Picker

รูปที่ 14 แอปการตั้งค่า (เมนูด้านซ้าย) ที่มีตัวเลือกวอลเปเปอร์เป็นกิจกรรมที่ฝัง (ด้านขวา)

รูปแบบความน่าเชื่อถือ

กระบวนการโฮสต์ที่ฝังกิจกรรมจากแอปอื่นๆ สามารถกำหนดการแสดงกิจกรรมที่ฝังใหม่ได้ ซึ่งรวมถึงขนาด ตำแหน่ง การครอบตัด และความโปร่งใส โฮสต์ที่เป็นอันตรายอาจใช้ความสามารถนี้ทำให้ผู้ใช้เข้าใจผิด และ สร้างการหลอกให้คลิกหรือการโจมตีอื่นๆ เพื่อแก้ไข 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() จากไลบรารี WindowManager ของ Jetpack เพื่อตรวจสอบว่าโฮสต์ฝังกิจกรรมของคุณอยู่หรือไม่ เช่น

Kotlin

fun isActivityEmbedded(activity: Activity): Boolean {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity)
}

Java

boolean isActivityEmbedded(Activity activity) {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity);
}

ข้อจำกัดด้านขนาดขั้นต่ำ

ระบบ Android จะใช้ความสูงและความกว้างขั้นต่ำที่ระบุไว้ในแอป ไฟล์ Manifest <layout> ลงในกิจกรรมที่ฝัง หากแอปพลิเคชันไม่ได้ระบุความสูงและความกว้างขั้นต่ำ ระบบจะใช้ค่าเริ่มต้น (sw220dp)

หากโฮสต์พยายามปรับขนาดคอนเทนเนอร์ที่ฝังให้เล็กกว่าขนาดขั้นต่ำ คอนเทนเนอร์ที่ฝังจะขยายขนาดให้เต็มขอบเขตของงาน

<ชื่อแทนกิจกรรม>

หากต้องการให้การฝังกิจกรรมที่เชื่อถือได้หรือไม่เชื่อถือได้ทํางานกับองค์ประกอบ <activity-alias> คุณต้องใส่ android:knownActivityEmbeddingCerts หรือ android:allowUntrustedActivityEmbedding ลงในกิจกรรมเป้าหมายแทนที่จะเป็นอีเมลแทน นโยบายที่ยืนยันความปลอดภัยในเซิร์ฟเวอร์ระบบจะอิงตาม Flag ที่กําหนดในเป้าหมาย ไม่ใช่ Alias

แอปพลิเคชันโฮสต์

แอปพลิเคชันโฮสต์ใช้การฝังกิจกรรมข้ามแอปในลักษณะเดียวกับที่ใช้การฝังกิจกรรมในแอปเดียว ออบเจ็กต์ SplitPairRule และ SplitPairFilter หรือ ActivityRule และ ActivityFilter จะระบุกิจกรรมที่ฝังไว้และการแยกหน้าต่างงาน กำหนดกฎการแยกแล้ว แบบคงที่ใน XML หรือขณะรันไทม์โดยใช้ Jetpack การเรียก WindowManager API

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

หากกิจกรรมที่ฝังเริ่มกิจกรรมใหม่ในแท็บงานเดียวกัน และกิจกรรมใหม่ไม่ได้เลือกใช้การฝังข้ามแอป กิจกรรมดังกล่าวจะครอบครองขอบเขตแท็บงานทั้งหมดแทนการวางซ้อนกิจกรรมในคอนเทนเนอร์ที่ฝัง

แอปพลิเคชันโฮสต์สามารถฝังกิจกรรมของตัวเองได้โดยไม่มีข้อจำกัด กิจกรรมจะเปิดในงานเดียวกัน

แยกตัวอย่าง

แยกจากหน้าต่างแบบเต็ม

รูปที่ 15 กิจกรรม A เริ่มกิจกรรม B ที่ด้านข้าง

ไม่ต้องมีการปรับโครงสร้าง คุณสามารถกําหนดค่าสําหรับการแยกแบบคงที่หรือขณะรันไทม์ แล้วเรียกใช้ Context#startActivity() โดยไม่ต้องมีพารามิเตอร์เพิ่มเติม

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

แยกโดยค่าเริ่มต้น

เมื่อหน้า Landing Page ของแอปพลิเคชันออกแบบมาให้แบ่งออกเป็นคอนเทนเนอร์ 2 รายการบนหน้าจอขนาดใหญ่ ประสบการณ์ของผู้ใช้จะดีที่สุดเมื่อสร้างและแสดงกิจกรรมทั้ง 2 รายการพร้อมกัน อย่างไรก็ตาม เนื้อหาอาจไม่พร้อมใช้งานสําหรับคอนเทนเนอร์รองของการแยกจนกว่าผู้ใช้จะโต้ตอบกับกิจกรรมในคอนเทนเนอร์หลัก (เช่น ผู้ใช้เลือกรายการจากเมนูการนำทาง) กิจกรรมตัวยึดตําแหน่งสามารถแสดงแทนที่เนื้อหาได้จนกว่าเนื้อหาจะแสดงในคอนเทนเนอร์รองของการแยก (ดูส่วนตัวยึดตําแหน่ง)

รูปที่ 16 การแยกสร้างขึ้นโดยการเปิดกิจกรรม 2 รายการพร้อมกัน กิจกรรม 1 รายการคือตัวยึดตำแหน่ง

หากต้องการสร้างการแยกกับตัวยึดตำแหน่ง ให้สร้างตัวยึดตำแหน่งและเชื่อมโยงกับ กิจกรรมหลัก:

<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity">
    <ActivityFilter
        window:activityName=".MainActivity"/>
</SplitPlaceholderRule>

เมื่อแอปได้รับ Intent กิจกรรมเป้าหมายจะแสดงเป็นส่วนรองของการแยกกิจกรรม เช่น คําขอแสดงหน้าจอรายละเอียดที่มีข้อมูลเกี่ยวกับรายการจากรายการ ในจอแสดงผลขนาดเล็ก รายละเอียดจะแสดงในหน้าต่างงานแบบเต็ม ส่วนในอุปกรณ์ขนาดใหญ่ รายละเอียดจะแสดงข้างรายการ

รูปที่ 17 กิจกรรมรายละเอียดของ Deep Link ที่แสดงเดี่ยวๆ ในหน้าจอขนาดเล็ก แต่แสดงร่วมกับกิจกรรมรายการในหน้าจอขนาดใหญ่

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

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));
}

ปลายทางของ Deep Link อาจเป็นกิจกรรมเดียวที่ผู้ใช้ควรเข้าถึงได้ในกองการนำทางด้านหลัง และคุณอาจไม่ต้องการปิดกิจกรรมรายละเอียดและเหลือไว้เฉพาะกิจกรรมหลัก

จอแสดงผลขนาดใหญ่พร้อมกิจกรรมรายการและกิจกรรมรายละเอียดแสดงคู่กัน
          การย้อนกลับไม่สามารถปิดกิจกรรมรายละเอียดและออกจากรายการได้
          กิจกรรมบนหน้าจอ

จอแสดงผลขนาดเล็กที่มีกิจกรรมแบบละเอียดเท่านั้น การนําทางกลับปิดกิจกรรมแบบละเอียดและแสดงกิจกรรมรายการไม่ได้

แต่คุณสามารถทำกิจกรรมทั้ง 2 อย่างให้เสร็จสิ้นในเวลาเดียวกันได้โดยใช้ แอตทริบิวต์ finishPrimaryWithSecondary:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".ListActivity"
        window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

ดูส่วนแอตทริบิวต์การกำหนดค่า

กิจกรรมหลายรายการในคอนเทนเนอร์แบบแยกส่วน

การวางซ้อนกิจกรรมหลายรายการในคอนเทนเนอร์ที่แยกส่วนช่วยให้ผู้ใช้เข้าถึง เนื้อหา ตัวอย่างเช่น เมื่อแบ่งรายละเอียดรายการ ผู้ใช้อาจต้องไปที่ รายละเอียดย่อย แต่เก็บกิจกรรมหลักไว้ตามเดิม:

รูปที่ 18 กิจกรรมเปิดขึ้นที่เดิมในแผงรองของหน้าต่างงาน

Kotlin

class DetailActivity {
    . . .
    fun onOpenSubDetail() {
      startActivity(Intent(this, SubDetailActivity::class.java))
    }
}

Java

public class DetailActivity {
    . . .
    void onOpenSubDetail() {
        startActivity(new Intent(this, SubDetailActivity.class));
    }
}

กิจกรรมรายละเอียดย่อยจะแสดงอยู่ด้านบนของกิจกรรมรายละเอียดโดยปกปิดไว้

จากนั้นผู้ใช้จะสามารถกลับไปยังระดับรายละเอียดก่อนหน้าได้โดยการย้อนกลับ ผ่านสแต็กด้วย

รูปที่ 19 นำกิจกรรมออกจากด้านบนของกองแล้ว

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

กิจกรรมในงานใหม่

เมื่อกิจกรรมในหน้าต่างงานแบบแยกเริ่มกิจกรรมในงานใหม่ งานใหม่จะแยกจากงานที่มีการแยกและแสดงเต็มหน้าต่าง หน้าจอล่าสุดจะแสดงงาน 2 รายการ ได้แก่ งานในหน้าจอแยกและงานใหม่

รูปที่ 20 เริ่มกิจกรรม ค ในงานใหม่จากกิจกรรม ข

การเปลี่ยนกิจกรรม

กิจกรรมสามารถแทนที่ในกองคอนเทนเนอร์รองได้ เช่น เมื่อใช้กิจกรรมหลักสำหรับการนําทางระดับบนสุดและกิจกรรมรองเป็นปลายทางที่เลือก การเลือกแต่ละรายการจากการนำทางระดับบนสุดควร เริ่มกิจกรรมใหม่ในคอนเทนเนอร์รอง และนำกิจกรรมนั้นออกหรือ กิจกรรมที่เคยอยู่ที่นั่น

รูปที่ 21 กิจกรรมการนำทางระดับบนสุดในแผงหลัก แทนที่กิจกรรมปลายทางในแผงรอง

หากแอปไม่ทำกิจกรรมในคอนเทนเนอร์รองให้เสร็จสิ้นเมื่อตัวเลือกการไปยังส่วนต่างๆ เปลี่ยนแปลง การนำทางกลับอาจทำให้เกิดความสับสนเมื่อยุบการแยก (เมื่อพับอุปกรณ์) เช่น หากคุณมีเมนูในแผงหลักและหน้าจอ A และ B ซ้อนกันในแผงรอง เมื่อผู้ใช้พับโทรศัพท์ หน้าจอ B จะวางอยู่ด้านบนของหน้าจอ A และหน้าจอ A จะวางอยู่ด้านบนของเมนู เมื่อผู้ใช้กลับไปจาก B ตัวเลือก 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)));
    }
}

อีกวิธีหนึ่งคือใช้กิจกรรมรองเดียวกัน และจาก (เมนู) หลัก กิจกรรมจะส่ง Intent ใหม่ที่แก้ไขเป็นอินสแตนซ์เดียวกันแต่ทริกเกอร์สถานะ หรือการอัปเดต UI ในคอนเทนเนอร์รอง

การแยกหลายรายการ

แอปสามารถให้บริการนำทางแบบลึกหลายระดับได้ด้วยการเปิดตัวกิจกรรมเพิ่มเติม อยู่ด้านข้าง

เมื่อกิจกรรมในคอนเทนเนอร์รองเปิดกิจกรรมใหม่ด้านข้าง มีการสร้างการแยกใหม่ทับบนการแบ่งที่มีอยู่

รูปที่ 22 กิจกรรม ข เริ่มกิจกรรม ค ไว้ด้านข้าง

กองซ้อนที่ย้อนกลับจะมีกิจกรรมทั้งหมดที่เปิดไว้ก่อนหน้านี้ เพื่อให้ผู้ใช้ไปยังการแยกกลุ่ม A/B ได้หลังจากทำ C เสร็จแล้ว

กิจกรรม A, B และ C ในกลุ่ม กิจกรรมจะซ้อนกันใน
          เรียงลำดับจากบนลงล่าง: C, B, A

หากต้องการสร้างการแยกใหม่ ให้เปิดตัวกิจกรรมใหม่ต่อจากกิจกรรมที่มีอยู่ คอนเทนเนอร์รอง ประกาศการกําหนดค่าสําหรับทั้งกลุ่ม 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 {
    . . .
    fun onOpenC() {
        startActivity(Intent(this, C::class.java))
    }
}

Java

public class B {
    . . .
    void onOpenC() {
        startActivity(new Intent(this, C.class));
    }
}

ตอบสนองต่อการเปลี่ยนแปลงสถานะแบบแยก

กิจกรรมต่างๆ ในแอปอาจมีองค์ประกอบ UI ที่ทํางานแบบเดียวกัน เช่น การควบคุมที่เปิดหน้าต่างที่มีการตั้งค่าบัญชี

รูปที่ 23 กิจกรรมต่างๆ ที่มีองค์ประกอบ UI เหมือนกันในการทำงาน

หากกิจกรรม 2 รายการที่มีองค์ประกอบ UI เหมือนกันอยู่ 2 อย่าง ซ้ำซ้อนและอาจทำให้สับสนในการแสดงองค์ประกอบในทั้ง 2 กิจกรรม

รูปที่ 24 องค์ประกอบ UI ซ้ำกันในการแยกกิจกรรม

หากต้องการทราบว่ากิจกรรมอยู่ในสถานะแยกหรือไม่ ให้ตรวจสอบโฟลว์ 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);
            });
}

เปิดตัว Coroutine ได้ในสภาวะของวงจรชีวิตทุกแบบ แต่โดยทั่วไปจะเปิดตัวใน สถานะ STARTED เพื่ออนุรักษ์ทรัพยากร (ดูใช้โครูทีน Kotlin กับ หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับคอมโพเนนต์ที่รับรู้ทุกวงจร)

คุณสามารถเรียกใช้การเรียกกลับได้ในสถานะวงจรใดก็ได้ รวมถึงเมื่อกิจกรรมหยุดลง โดยปกติแล้ว ผู้ฟังควรจดทะเบียนใน onStart() และไม่ได้จดทะเบียนใน onStop()

โมดัลหน้าต่างเต็ม

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

คุณสามารถบังคับให้กิจกรรมเติมหน้าต่างงานเสมอด้วยการใช้ปุ่มขยาย การกำหนดค่า:

<ActivityRule
    window:alwaysExpand="true">
    <ActivityFilter
        window:activityName=".FullWidthActivity"/>
</ActivityRule>

กิจกรรมเสร็จสิ้น

ผู้ใช้สามารถทำงานให้เสร็จสมบูรณ์ในแต่ละด้านของการแยกหน้าจอโดยการปัดจากขอบของจอแสดงผล ดังนี้

รูปที่ 25 ท่าทางสัมผัสการปัดที่สิ้นสุดกิจกรรม ข.
รูปที่ 26 ท่าทางสัมผัสการปัดสิ้นสุดกิจกรรม ก.

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

ผลจากการสิ้นสุดกิจกรรมทั้งหมดในคอนเทนเนอร์มีต่อฝ่ายตรงข้าม ขึ้นอยู่กับการกำหนดค่าการแยก

แอตทริบิวต์การกําหนดค่า

คุณสามารถระบุแอตทริบิวต์กฎคู่แยกเพื่อกําหนดค่าผลที่การสิ้นสุดกิจกรรมทั้งหมดในฝั่งหนึ่งของการแยกมีผลต่อกิจกรรมในฝั่งอื่นของการแยก แอตทริบิวต์ดังกล่าวมีดังนี้

  • window:finishPrimaryWithSecondary — ผลกระทบที่การทํากิจกรรมทั้งหมดในคอนเทนเนอร์รองต่อกิจกรรมในคอนเทนเนอร์หลัก
  • window:finishSecondaryWithPrimary — สิ้นสุดกิจกรรมทั้งหมดใน คอนเทนเนอร์หลักส่งผลต่อกิจกรรมในคอนเทนเนอร์รอง

ค่าที่เป็นไปได้ของแอตทริบิวต์ ได้แก่

  • always — ทำกิจกรรมในคอนเทนเนอร์ที่เกี่ยวข้องให้เสร็จสิ้นเสมอ
  • never — ไม่เคยทำกิจกรรมในคอนเทนเนอร์ที่เกี่ยวข้องให้เสร็จ
  • adjacent — ดำเนินกิจกรรมในคอนเทนเนอร์ที่เชื่อมโยงให้เสร็จเมื่อ คอนเทนเนอร์ 2 รายการจะแสดงติดกัน แต่ไม่แสดงเมื่อ คอนเทนเนอร์ 2 รายการซ้อนกัน

เช่น

<SplitPairRule
    &lt;!-- Do not finish primary container activities when all secondary container activities finish. --&gt;
    window:finishPrimaryWithSecondary="never"
    &lt;!-- Finish secondary container activities when all primary container activities finish. --&gt;
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

การกำหนดค่าเริ่มต้น

เมื่อกิจกรรมทั้งหมดในคอนเทนเนอร์หนึ่งของการแยกเสร็จสิ้น คอนเทนเนอร์ที่เหลือ กินพื้นที่หน้าต่างทั้งหมด:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

การแบ่งที่มีกิจกรรม A และ B A เสร็จแล้ว เหลือเพียง B ที่ครอบครองทั้งหน้าต่าง

แยกที่มีกิจกรรม A และ B B เสร็จแล้ว เหลือเพียง A ที่ครอบครองทั้งหน้าต่าง

ทำกิจกรรมร่วมกันให้เสร็จ

ทำกิจกรรมในคอนเทนเนอร์หลักให้เสร็จโดยอัตโนมัติเมื่อกิจกรรมทั้งหมด ในคอนเทนเนอร์รองเสร็จสิ้น:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

แยกที่มีกิจกรรม A และ B B เสร็จแล้ว ซึ่งทำให้ A เสร็จด้วย ทำให้หน้าต่างงานว่างเปล่า

การแบ่งที่มีกิจกรรม A และ B A เสร็จแล้ว เหลือเพียง B คนเดียวในหน้าต่างงาน

ทำกิจกรรมในคอนเทนเนอร์รองให้เสร็จโดยอัตโนมัติเมื่อทั้งหมด กิจกรรมในคอนเทนเนอร์หลักเสร็จสิ้น:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

แยกที่มีกิจกรรม A และ B A เสร็จสิ้น ซึ่งมัก
          ดำเนินการ B จนเสร็จ และปล่อยหน้าต่างงานว่างไว้

การแบ่งที่มีกิจกรรม A และ B B เสร็จสิ้นแล้ว และเหลือ A ไว้ตามเดิม
          ในหน้าต่างงาน

ทำกิจกรรมให้เสร็จร่วมกันเมื่อกิจกรรมทั้งหมดในกิจกรรมหลักหรือ ทำให้คอนเทนเนอร์รองเสร็จสิ้น:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

การแบ่งที่มีกิจกรรม A และ B A เสร็จสิ้น ซึ่งมัก
          ดำเนินการ B จนเสร็จ และปล่อยหน้าต่างงานว่างไว้

แยกที่มีกิจกรรม A และ B B เสร็จแล้ว ซึ่งทำให้ A เสร็จด้วย ทำให้หน้าต่างงานว่างเปล่า

ทํากิจกรรมหลายอย่างในคอนเทนเนอร์ให้เสร็จ

หากมีกิจกรรมหลายรายการซ้อนกันในคอนเทนเนอร์แบบแยก การทำกิจกรรมที่ด้านล่างของกองให้เสร็จสมบูรณ์จะไม่ทำให้กิจกรรมที่ด้านบนเสร็จสมบูรณ์โดยอัตโนมัติ

ตัวอย่างเช่น หากมีกิจกรรม 2 รายการอยู่ในคอนเทนเนอร์รอง โดย C อยู่ด้านบนของ B

สแต็กกิจกรรมรองที่มีกิจกรรม C วางซ้อนอยู่ด้านบน B
          ซ้อนอยู่ด้านบนของสแต็กกิจกรรมส่วนตัวซึ่งมีกิจกรรม
          ต.

และการกำหนดค่าการแยกจะกำหนดโดยการกำหนดค่ากิจกรรม ก และ ข:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

การสิ้นสุดกิจกรรมยอดนิยมจะเก็บการแยกไว้

แยกโดยมีกิจกรรม A ในคอนเทนเนอร์หลัก และกิจกรรม B และ C ในคอนเทนเนอร์รอง โดย C วางซ้อนบน B C เสร็จสิ้นแล้ว โดยปล่อยให้ A และ B อยู่ใน
          แยกกิจกรรม

การดำเนินการเสร็จสิ้นกิจกรรมด้านล่าง (ราก) ของคอนเทนเนอร์รองจะไม่นำออก กิจกรรมนอกเหนือจากนั้น และจะยังเก็บฝั่งที่แยกไว้

แบ่งกิจกรรม A ในคอนเทนเนอร์หลักและกิจกรรม B และ C ใน
          รอง, C ซ้อนอยู่ด้านบน B B เสร็จสิ้นแล้ว เหลือ A และ C อยู่ในกิจกรรมแยก

ระบบจะดำเนินการตามกฎเพิ่มเติมสำหรับการสิ้นสุดกิจกรรมร่วมกัน เช่น การสิ้นสุดกิจกรรมรองด้วยกิจกรรมหลัก

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

แยกโดยมีกิจกรรม A ในคอนเทนเนอร์หลัก และกิจกรรม B และ C ในคอนเทนเนอร์รอง โดย C วางซ้อนบน B A เสร็จสิ้นแล้ว และทำให้ B และ C เสร็จสิ้นด้วย

และเมื่อกำหนดค่าการแยกให้จบทั้งประถมศึกษาและรองพร้อมกันแล้ว

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

แยกโดยมีกิจกรรม A ในคอนเทนเนอร์หลัก และกิจกรรม B และ C ในคอนเทนเนอร์รอง โดย C วางซ้อนบน B C เสร็จสิ้นแล้ว เหลือ A และ B อยู่ในกิจกรรมแยก

แยกโดยมีกิจกรรม A ในคอนเทนเนอร์หลัก และกิจกรรม B และ C ในคอนเทนเนอร์รอง โดย C วางซ้อนบน B B เสร็จสิ้นแล้ว เหลือ A และ C อยู่ในกิจกรรมแยก

แยกโดยมีกิจกรรม A ในคอนเทนเนอร์หลัก และกิจกรรม B และ C ในคอนเทนเนอร์รอง โดย C วางซ้อนบน B เรียนจบ A จบหลักสูตร B
          ค.

เปลี่ยนพร็อพเพอร์ตี้การแยกขณะรันไทม์

คุณจะเปลี่ยนพร็อพเพอร์ตี้ของการแยกที่ใช้งานอยู่และมองเห็นได้ไม่ได้ การเปลี่ยน กฎการแยกมีผลต่อการเรียกใช้กิจกรรมเพิ่มเติมและคอนเทนเนอร์ใหม่ แต่ไม่มีผลกับ การแยกที่มีอยู่และใช้งานอยู่

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

พร็อพเพอร์ตี้การแยกแบบไดนามิก

Android 15 (API ระดับ 35) ขึ้นไปที่ Jetpack รองรับ WindowManager 1.4 และที่สูงกว่านำเสนอฟีเจอร์แบบไดนามิก ที่ช่วยให้กำหนดค่ากิจกรรมได้ การแยกการฝัง รวมถึง

  • การขยายแพ็กเกจ: ตัวแบ่งแบบอินเทอร์แอกทีฟที่ลากได้ช่วยให้ผู้ใช้ทำสิ่งต่างๆ ต่อไปนี้ได้ ปรับขนาดแผงในงานนำเสนอแบบแยก
  • การปักหมุดกิจกรรม: ผู้ใช้สามารถปักหมุดเนื้อหาในคอนเทนเนอร์หนึ่งและแยกการไปยังส่วนต่างๆ ในคอนเทนเนอร์นั้นจากการไปยังส่วนต่างๆ ในคอนเทนเนอร์อื่น
  • การหรี่แสงกล่องโต้ตอบแบบเต็มหน้าจอ: เมื่อแสดงกล่องโต้ตอบ แอปสามารถระบุ จะหรี่แสงหน้าต่างงานทั้งหมด หรือเฉพาะคอนเทนเนอร์ที่เปิดหน้าต่างงาน กล่องโต้ตอบ

การขยายแผง

การขยายแผงช่วยให้ผู้ใช้ปรับจำนวนพื้นที่หน้าจอที่จัดสรรให้ กิจกรรมทั้ง 2 รายการในเค้าโครงแบบ 2 แผง

หากต้องการปรับแต่งลักษณะที่ปรากฏของตัวแบ่งหน้าต่างและตั้งค่าช่วงการลากของตัวแบ่ง ให้ทำดังนี้

  1. สร้างอินสแตนซ์ของ DividerAttributes

  2. ปรับแต่งแอตทริบิวต์ตัวแบ่ง ดังนี้

    • 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(context, 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(context, R.color.divider_color))
        .setWidthDp(4)
        .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
        .build()
    );
}
SplitAttributes splitAttributes = splitAttributesBuilder.build();

การปักหมุดกิจกรรม

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

หากต้องการเปิดใช้การปักหมุดกิจกรรมในแอป ให้ทำดังนี้

  1. เพิ่มปุ่มลงในไฟล์เลย์เอาต์ของกิจกรรมที่ต้องการปักหมุด ตัวอย่างเช่น กิจกรรมรายละเอียดของเลย์เอาต์รายละเอียดรายการ

    <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>
    
  2. ในเมธอด onCreate() ของกิจกรรม ให้ตั้งค่า Listener ของ onclick ในปุ่ม ดังนี้

    Kotlin

    pinButton = 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 แผงควรหรี่แสง ไม่ใช่ เฉพาะแผงที่มีกิจกรรมที่เปิดกล่องโต้ตอบ สำหรับ 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) สามารถใช้การฝังกิจกรรมเป็นฟังก์ชันของระบบอุปกรณ์ได้ ระบบระบุกฎการแยกสำหรับแอปที่มีกิจกรรมหลายรายการ โดยจะลบล้างกรอบเวลา พฤติกรรมของแอป การลบล้างของระบบจะบังคับให้แอปหลายกิจกรรมเข้าสู่โหมดการฝังกิจกรรมที่ระบบกําหนด

การฝังกิจกรรมของระบบจะช่วยปรับปรุงการนำเสนอแอปผ่านหลายหน้าต่าง เช่น list-detail โดยไม่ต้องเปลี่ยนแปลงแอป อย่างไรก็ตาม การฝังกิจกรรมของระบบยังอาจทำให้เกิดการจัดวางแอป ข้อบกพร่อง หรือ ขัดแย้งกับการฝังกิจกรรมที่แอปใช้

แอปของคุณสามารถป้องกันหรืออนุญาตการฝังกิจกรรมของระบบโดยการตั้งค่า พร็อพเพอร์ตี้ในไฟล์ 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>

ชื่อพร็อพเพอร์ตี้จะกำหนดไว้ใน Jetpack WindowManager WindowProperties ออบเจ็กต์ ตั้งค่าเป็น false หากแอปของคุณใช้การฝังกิจกรรม หรือหากต้องการป้องกันไม่ให้ระบบใช้กฎการฝังกิจกรรมกับแอปของคุณ ให้ตั้งค่าเป็น true เพื่ออนุญาตให้ระบบใช้การฝังกิจกรรมที่ระบบกําหนดกับแอปของคุณ

ข้อจำกัด ข้อจํากัด และข้อควรระวัง

  • เฉพาะแอปโฮสต์ของงานซึ่งระบุว่าเป็นเจ้าของกิจกรรมรูทในงานนี้เท่านั้นที่จะจัดระเบียบและฝังกิจกรรมอื่นๆ ในงานนี้ได้ หากกิจกรรมที่รองรับการฝังและการแยกทำงานในงานที่เป็น ไปยังแอปพลิเคชันอื่น การฝังและการแยกจะไม่ทำงาน กิจกรรมเหล่านั้น
  • คุณจะจัดระเบียบกิจกรรมได้ภายในงานเดียวเท่านั้น การเริ่มกิจกรรมในแท็บงานใหม่จะวางกิจกรรมนั้นในหน้าต่างใหม่ที่ขยายออกเสมอ นอกเหนือไปจากการแยกที่มีอยู่
  • คุณจะจัดระเบียบและแยกเฉพาะกิจกรรมที่อยู่ในกระบวนการเดียวกันได้ Callback ของ SplitInfo จะรายงานเฉพาะกิจกรรมที่เป็นของบัญชีเดียวกัน เนื่องจากไม่มีทางทราบเกี่ยวกับกิจกรรมต่างๆ ใน กระบวนการ
  • กฎกิจกรรมแต่ละคู่หรือกฎเดียวจะมีผลเฉพาะกับการเปิดตัวกิจกรรมที่ จะเกิดขึ้นหลังจากที่ลงทะเบียนกฎแล้ว ปัจจุบันยังไม่มีวิธี อัปเดตการแยกที่มีอยู่หรือคุณสมบัติการแสดงผล
  • การกําหนดค่าตัวกรองคู่ที่แยกต้องตรงกับ Intent ที่ใช้เมื่อเปิดใช้งานกิจกรรมโดยสมบูรณ์ การจับคู่จะเกิดขึ้น ณ เวลาที่ กิจกรรมนั้นเริ่มต้นจากขั้นตอนการสมัคร จึงอาจไม่ทราบเกี่ยวกับ ชื่อคอมโพเนนต์ที่ได้รับการแก้ไขในภายหลังในกระบวนการของระบบเมื่อใช้ Intent แบบไม่เจาะจงปลายทาง หากไม่ทราบชื่อคอมโพเนนต์เมื่อเปิดตัว สามารถใช้ไวลด์การ์ดแทน ("*/*") และดำเนินการกรองได้ตาม เกี่ยวกับการดำเนินการผ่าน Intent
  • ปัจจุบันยังไม่มีวิธีย้ายกิจกรรมระหว่างคอนเทนเนอร์ หรือย้ายกิจกรรมเข้าและออกจากการแยกหลังจากที่สร้างแล้ว การแยกถูกสร้างขึ้นโดย ไลบรารี WindowManager เมื่อมีการเปิดตัวกิจกรรมใหม่ที่มีกฎที่ตรงกัน และการแยกจะถูกทำลายเมื่อกิจกรรมล่าสุดในคอนเทนเนอร์ที่แบ่งเป็น เสร็จสิ้นแล้ว
  • กิจกรรมจะเปิดอีกครั้งได้เมื่อการกำหนดค่ามีการเปลี่ยนแปลง มีการสร้างหรือนำออก และขอบเขตของกิจกรรมมีการเปลี่ยนแปลง ผ่านการทำลายอินสแตนซ์ก่อนหน้าโดยสมบูรณ์และการสร้าง อันใหม่นะ ดังนั้น นักพัฒนาแอปควรระมัดระวังเรื่องต่างๆ เช่น การเริ่มกิจกรรมใหม่จากการเรียกกลับของวงจร
  • อุปกรณ์ต้องมีอินเทอร์เฟซส่วนขยายหน้าต่างเพื่อรองรับการฝังกิจกรรม อุปกรณ์หน้าจอขนาดใหญ่เกือบทั้งหมดที่ใช้ Android 12L (API ระดับ 32) ขึ้นไปจะมีอินเทอร์เฟซนี้ อย่างไรก็ตาม อุปกรณ์หน้าจอขนาดใหญ่บางรุ่นที่ไม่สามารถเรียกใช้กิจกรรมหลายรายการจะไม่มีอินเทอร์เฟซส่วนขยายหน้าต่าง หากอุปกรณ์ที่มีหน้าจอขนาดใหญ่ไม่รองรับหลายหน้าต่าง แสดงว่าอาจไม่รองรับการฝังกิจกรรม

แหล่งข้อมูลเพิ่มเติม