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

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

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

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

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

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

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

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

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

การฝังกิจกรรมจะแบ่งหน้าต่างงานของแอปออกเป็น 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 อยู่ด้านบน

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

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

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

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

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

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

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

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

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

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

ทําดังนี้

  1. เพิ่มการพึ่งพาไลบรารี 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 และตั้งค่าเป็น "จริง"

    นอกจากนี้ ผู้ผลิตอุปกรณ์ยังใช้การตั้งค่านี้เพื่อเปิดใช้ความสามารถที่กําหนดเองสําหรับแอปที่รองรับการฝังกิจกรรม เช่น อุปกรณ์อาจแสดงกิจกรรมในแนวตั้งเท่านั้นในจอแสดงผลแนวนอนเพื่อปรับแนวของกิจกรรมสำหรับการเปลี่ยนไปใช้เลย์เอาต์แบบ 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. เพิ่มการพึ่งพาไลบรารี 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> ใส่การอ้างอิงถึงการใช้งาน RuleController initializer 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 กับกิจกรรมเป้าหมายแทนการใช้แทน นโยบายที่ยืนยันความปลอดภัยในเซิร์ฟเวอร์ระบบจะอิงตาม Flag ที่กําหนดในเป้าหมาย ไม่ใช่ใน Alias

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

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

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

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

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

แยกตัวอย่าง

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

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

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

แอปสามารถป้องกันหรืออนุญาตการฝังกิจกรรมของระบบได้โดยการตั้งค่า PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE ในไฟล์ Manifest ของแอป เช่น

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <property
            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
            android:value="true|false" />
    </application>
</manifest>

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

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

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

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