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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • ที่ด้านข้างบนกิจกรรมอื่น

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

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

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

    รูปที่ 7 กิจกรรม ก หรือ ข เริ่มกิจกรรม ค ซึ่งจะแสดงเต็มหน้าต่างงาน

การนำทางกลับ

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

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

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

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

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

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

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

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

เลย์เอาต์แบบหลายแผง

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

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

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

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

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

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

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

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

กิจกรรม ข เริ่มกิจกรรม ค ทางด้านข้างและเลื่อนการแยกไปด้านข้าง

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

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

กิจกรรม ก. ข. และ ค. ในกองเดียว กิจกรรมจะซ้อนกันจากบนลงล่างตามลําดับ 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 ลงในองค์ประกอบ <application> ของไฟล์ 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 ใช้กฎการแยกได้ด้วยการส่งรหัสของไฟล์การกําหนดค่า 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> ใส่การอ้างอิงถึงการใช้งาน RuleControllerinitializer 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 เพื่อให้แน่ใจว่ากฎมีผลบังคับใช้ก่อนกิจกรรมใดๆ จะเริ่มต้น

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

  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 หลายรายการในลักษณะภาพ ระบบจะแสดงกิจกรรมของแอปโฮสต์และกิจกรรมที่ฝังจากแอปอื่นบนหน้าจอควบคู่กันหรือด้านบนและด้านล่าง เช่นเดียวกับการฝังกิจกรรมในแอปเดียว

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

รูปที่ 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"
    ... />

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

การตรวจสอบสิทธิ์ที่กำหนดเอง

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

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

<activity-alias>

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

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

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

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

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

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

แยกตัวอย่าง

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

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

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

กิจกรรม ก. ข. และ ค. ในสแต็ก กิจกรรมจะซ้อนกันตามลําดับจากบนลงล่างดังนี้ 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 กิจกรรมจะซ้ำซ้อนและอาจทำให้เกิดความสับสน

รูปที่ 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);
            });
}

โคโริวทีนสามารถเปิดใช้งานได้ในทุกสถานะวงจร แต่โดยทั่วไปจะเปิดขึ้นในสถานะ 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 จะซ้อนอยู่ด้านบนสแต็กกิจกรรมหลักที่มีกิจกรรม A

และการกําหนดค่าของการแยกจะกําหนดโดยการกำหนดค่าของกิจกรรม A และ 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 และ C เสร็จสิ้นด้วย

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

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

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

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

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 หน้าต่างได้เพื่อให้กิจกรรมคงอยู่เหมือนเดิมขณะที่ผู้ใช้ไปยังส่วนต่างๆ ในหน้าต่างอีกบาน การปักหมุดกองงานช่วยให้คุณทำงานหลายอย่างพร้อมกันได้ดีขึ้น

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

  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 เพื่ออนุญาตให้ระบบใช้การฝังกิจกรรมที่ระบบกําหนดกับแอปของคุณ

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

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

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