จากที่เคยอธิบายเกี่ยวกับการทำงานของ Task ไปในบทความเรื่อง Task บนแอนดรอยด์ และมีการพูดถึงเรื่อง Activity Launch Mode นิดหน่อย ดังนั้นในบทความนี้ก็จะมาพูดถึงเรื่องนี้กันต่อ

บทความในชุดนี้

ถึงแม้ว่านักพัฒนาจะทำอะไรกับ Task โดยตรงไม่ได้ แต่ระบบแอนดรอยด์ก็เปิดให้นักพัฒนากำหนดได้ว่าอยากจะให้เปิด Activity บน Task ในลักษณะแบบไหนผ่านการกำหนดค่าที่เรียกว่า Activity Launch Mode

ขอเรียก Activity Launch Mode สั้น ๆ ว่า Launch Mode

โดยบทความนี้จะแบ่งออกเป็น 2 บทความย่อยด้วยกัน เนื่องจากเนื้อหาในเรื่องนี้มีรายละเอียดเยอะมาก และในการอธิบายการทำงานของ Launch Mode จะขอยกตัวอย่างด้วยแอปที่มี Activity อยู่ทั้งหมด 3 แบบคือ Home, Detail, และ List เพื่อใช้อธิบายการทำงานของ Task และ Back Stack

Launch Mode มีไว้ทำอะไรนะ?

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

Activity ก็เป็น Instance ตัวนึงที่สามารถสร้างซ้ำได้

ถึงแม้ว่าการใช้งานส่วนใหญ่ของนักพัฒนามักจะออกแบบให้ Activity แต่ละตัวมีอยู่แค่ตัวเดียวในแอป

แต่ในความเป็นจริงนั้น นักพัฒนาสามารถสร้าง Activity ซ้ำได้ เพราะเบื้องหลังของ Activity ก็เป็น Instance ตัวหนึ่ง และ Activity ตัวนั้นก็จะถูกเพิ่มเข้าไปใน Back Stack ด้วยเช่นกัน

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

Activity จะอยู่ใน Task ไหนก็ได้

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

ในกรณีที่ต้องการรูปแบบในการสร้าง Activity ในแต่ละ Task ที่แตกต่างออกไปจากนี้ นักพัฒนาก็จะต้องกำหนด Launch Mode ให้กับ Activity เพิ่มเข้าไป

นักพัฒนาไม่สามารถแก้ไขการทำงานของ Task ได้โดยตรง จะต้องกำหนดรูปแบบที่ต้องการผ่าน Launch Mode ของ Activity เท่านั้น

Task Affinity ส่งผลต่อการทำงาน Launch Mode ด้วย

การกำหนด Launch Mode ก็จะให้ผลลัพธ์แตกต่างกันออกไปตามการกำหนดค่าของ Task Affinity ในแต่ละ Activity ซึ่งเจ้าของบล็อกจะไม่ได้อธิบายทั้ง 2 อย่างนี้รวมกัน เพื่อให้ง่ายต่อการทำความเข้าใจ (มั้งนะ)

เจ้าของบล็อกจะเริ่มจากการอธิบายเกี่ยวกับ Launch Mode ก่อน แล้วตามด้วย Task Affinity ในภายหลัง ดังนั้นถ้าเป็นไปได้ นอกจากจะอ่านบทความเรื่อง Launch Mode แล้ว ก็อย่าลืมอ่านบทความเรื่อง Task Affinity เพิ่มด้วย เพื่อให้เข้าใจการทำงานของ Task ในแอนดรอยด์มากขึ้นด้วยล่ะ

การกำหนดค่า Launch Mode ให้กับ Activity

นักพัฒนาสามารถกำหนดค่า Launch Mode ให้กับ Activity ได้ทั้งหมด 2 วิธีด้วยกัน โดยแต่ละวิธีจะกำหนดค่าได้ไม่เหมือนกันทั้งหมด เพราะมีเงื่อนไขในการใช้งานต่างกัน

  • กำหนดผ่าน Attribute ของ Activity ใน Android Manifest
  • กำหนดผ่าน Flag ใน Intent

กำหนดผ่าน Attribute ของ Activity ใน Android Manifest

นักพัฒนาสามารถกำหนด Launch Mode ด้วยวิธีนี้ผ่าน Attribute ที่ชื่อ android:launchMode ใน <activity> แบบนี้ได้เลย

<!-- AndroidManifest.xml -->
<manifest ...>
    <application ...>
        <activity ...
            android:launchMode="standard" />
        <!-- ... -->
    </application>
</manifest>

การกำหนดค่าด้วยวิธีแบบนี้จะมีผลทันทีในตอนที่ Activity ถูกสร้างขึ้นมา

กำหนดผ่าน Flag ใน Intent

เวลาสร้าง Explicit Intent เพื่อเปิด Activity ตัวใหม่ขึ้นมา นักพัฒนาสามารถกำหนด Flag สำหรับ Launch Mode ให้กับ Intent แบบนี้ได้เลย

val activity: Activity = /* ... */
val intent = Intent(/* ... */).apply {
    flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
activity.startActivity(intent)
จะใช้ setFlag หรือ addFlag ก็ได้ ขึ้นอยู่กับว่าต้องการกำหนดค่าแบบไหน

การกำหนด Launch Mode ด้วยวิธีนี้จะช่วยให้ Activity ตัวนั้น ๆ มี Launch Mode แตกต่างกันตามเงื่อนไขที่ต้องการได้

การกำหนดค่าทั้ง 2 แบบมีจุดประสงค์ที่ไม่เหมือนกัน

ที่ระบบแอนดรอยด์ออกแบบให้นักพัฒนาสามารถกำหนด Launch Mode ได้ 2 วิธี เพราะว่าแต่ละวิธีจะใช้ในสถานการณ์ที่แตกต่างกัน

โดยการกำหนดผ่าน android:launchMode จะเป็นการกำหนดแบบเจาะจง ดังนั้นไม่ว่า Activity จะถูกเรียกจากที่ไหน ก็จะมี Launch Mode ตามที่กำหนดไว้เสมอ (ถ้าไม่ถูก Override ด้วย Launch Mode Flag ใน Intent)

ในขณะที่การกำหนด Launch Mode ผ่าน Flag ใน Intent จะเป็นการกำหนดให้ Activity ใด ๆ ก็ได้มี Launch Mode ตามนั้น โดยไม่สนใจว่า Activity ปลายทางจะกำหนด Launch Mode แบบไหนไว้อยู่

แล้ว Activity ที่กำหนด Launch Mode ทั้ง 2 วิธีล่ะ ?

ระบบแอนดรอยด์จะใช้ Launch Mode ที่กำหนดไว้ใน Flag ของ Intent แทน และที่กำหนดไว้ใน android:launchMode ก็จะไม่มีผลทันที

Launch Mode ใน Android Manifest

การกำหนดค่า Launch Mode ผ่าน android:launchMode ใน <activity> จะมีให้เลือกทั้งหมดดังนี้

  • standard
  • singleTop
  • singleTask
  • singleTaskInstance
  • singleTaskInstancePerTask

standard

Activity ที่กำหนด Launch Mode เป็น Standard จะทำให้ Activity ถูกสร้างขึ้นมาตามปกติ คือสามารถมี Activity หลายตัวได้ และสามารถอยู่ใน Task ใดก็ได้

โดย Standard จะเหมาะกับ Activity ทั่ว ๆ ไปที่ไม่ต้องการทำอะไรเป็นพิเศษ

singleTop

Activity ที่กำหนด Launch Mode เป็น Single Top จะคล้ายกับ Standard แทบทั้งหมด แต่จุดที่แตกต่างคือตอนที่สร้าง Intent เพื่อเปิด Activity ตัวเดิมขึ้นมา

ตอนที่สร้าง Intent เพื่อเปิด Activity ขึ้นมา ถ้า Activity ตัวดังกล่าวมีอยู่แล้วใน Back Stack แต่ไม่ได้อยู่ข้างบนสุด ระบบแอนดรอยด์ก็จะสร้าง Activity ตัวนั้นขึ้นมาใหม่และเพิ่มเข้าไปใน Back Stack ตามปกติ

แต่ถ้า Activity ตัวนั้นมีอยู่แล้วใน Back Stack และอยู่บนสุดด้วย ถ้า Launch Mode เป็น Standard ก็จะสร้าง Activity ขึ้นมาตามปกติ แต่ถ้าเป็น Single Top จะไม่สร้าง Activity ขึ้นมาใหม่ แต่จะส่ง Intent เข้าไปที่คำสั่ง onNewIntent ใน Activity ตัวเดิมแทน

ด้วยเหตุนี้จึงทำให้ Activity ที่มี Launch Mode เป็น Single Top ควรจะประกาศ onNewIntent ไว้ด้วยเสมอ เพราะมีโอกาสที่จะทำงานได้ทั้งจาก onCreate และ onNewInten

// DetailActivity.kt
class DetailActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val intent = getIntent()
        // Do something
    }
    
    override fun onNewIntent(intent: Intent?) {
        // Do something
    }
}

และโดยปกติแล้วนักพัฒนามักจะกำหนดให้ทั้ง onCreate และ onNewIntent ทำคำสั่งเดียวกัน

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

ยกตัวอย่างเช่น แอปมีปุ่มที่กดแล้วจะส่งข้อมูลไปหน้า Checkout โดยอัตโนมัติ โดยปุ่มที่ว่านี้สามารถอยู่ได้หลาย ๆ ที่ (ด้วยเจตจำนงของทีม Business) ทำให้ปุ่มดังกล่าวก็สามารถอยู่ในหน้า Checkout ได้เช่นกัน

เพื่อป้องกันไม่ให้หน้า Checkout เปิดขึ้นมาซ้ำและกดปุ่ม Back เพื่อกลับไปหน้า Home ได้เลย ในกรณีนี้นักพัฒนาสามารถกำหนดให้หน้า Checkout มี Launch Mode เป็น Single Top จะตอบโจทย์กว่า

แต่มาถึงตรงนี้บางคนอาจจะสงสัยว่า ทำไมต้องใช้ Single Top ให้ยุ่งยากด้วยล่ะ​? แค่ให้ทำให้โค้ดในหน้า Checkout ทำงานคนละแบบกับไปหน้า Home ก็ได้นี่?

ถ้าเราทำให้ UI ในส่วนนั้นอยู่ใน Fragment และมีโค้ดเพียงรูปแบบเดียว ก็จะช่วยลดความซับซ้อนของโค้ดได้ อีกทั้งยัง Reuse เพื่อใช้ในหน้า Home และ Checkout ได้อีกด้วย โดยไม่ต้องเช็คว่า Fragment ถูกเรียกใช้งานจาก Activity ใด

จึงเป็นที่มาว่าทำไมการใช้ Single Top จึงเหมาะกับสถานการณ์แบบนี้ยิ่งนัก

singleTask

Activity ที่กำหนด Launch Mode เป็น Single Task จะทำให้ Activity ตัวนั้นมีได้แค่เพียงตัวเดียวในแอปเท่านั้น โดยจะอยู่ใน Task ใดก็ได้ แต่ได้แค่เพียง Task เดียวเท่านั้น

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

นั่นหมายความว่า Activity ที่กำหนด Launch Mode เป็น Single Task ก็จะต้องประกาศ onNewIntent ไว้ด้วยเช่นกัน และด้วยเหตุนี้ จึงทำให้ Single Task เหมาะกับ Activity ที่ต้องการให้มีแค่ตัวเดียวเท่านั้น และมักจะใช้กับ Activity หลักของแอป

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

ในกรณีที่แอปมีหลาย Task ถ้า Activity ที่เป็น Single Task เคยถูกสร้างไว้ใน Task ใด เมื่อมีการเรียก Activity ตัวดังกล่าวจาก Task อื่น ระบบแอนดรอยด์ก็จะสลับกลับไปเป็น Task ที่มี Activity ตัวนั้นแทน

และนอกจากนี้ เวลาที่นักพัฒนาต้องการให้แอปเปิดจากแอปตัวอื่น ๆ ได้ แต่ไม่ต้องการให้ Activity ไปอยู่ใน Activity Stack ของ Task ในแอปอื่น ก็สามารถใช้ Single Task เพื่อช่วยแก้ปัญหานี้ได้เช่นกัน

singleInstance

การทำงานของ Activity ที่มี Launch Mode เป็น Single Instance จะคล้ายกับ Single Task ตรงที่ Activity จะมีได้แค่ตัวเดียวเท่านั้น แต่จุดที่แตกต่างกันคือระบบแอนดรอยด์จะสร้าง Task สำหรับ Activity ตัวนั้นให้โดยเฉพาะ โดยที่ Task นั้นก็จะมีแค่ Activity ดังกล่าวแค่เพียงตัวเดียวด้วย

ในระหว่างนี้ถ้ามีการกด Back ระบบแอนดรอยด์ก็จะทำลาย Activity และ Task ทิ้ง ทำให้ Task และ Activity ก่อนหน้ากลับมาทำงานต่อ ซึ่งเป็นรูปแบบปกติของแอปที่มีหลาย Task

และจากการที่ Task นั้นมี Activity ที่เป็น Single Instance อยู่แค่ตัวเดียวเท่านั้น ถ้ามีการสร้าง Activity ขึ้นมาใหม่ ก็จะกลับไปสร้างใน Task ก่อนหน้าแทน

ถ้าในระหว่างนั้นมีการสร้าง Intent สำหรับ Activity ที่เป็น Single Instance อีกครั้ง ระบบแอนดรอยด์ก็จะสลับไปที่ Task นั้นพร้อมกับเรียก Activity ตัวเดิมให้ทำงานต่อ และส่งข้อมูล Intent ให้ทาง onNewIntent (รูปแบบเดียวกับ Single Task)

แต่สิ่งหนึ่งที่ทำให้ Single Instance มีรูปแบบการทำงานที่แตกต่างไปจาก Launch Mode ตัวอื่น ๆ ก็คือถ้าผู้ใช้กด Back ในระหว่างที่เปิด Activity ตัวอื่นที่ถูกสร้างมาจาก Activity ที่เป็น Single Instance

แทนที่ระบบแอนดรอยด์จะสลับ Task เพื่อกลับไปหา Activity ที่เป็น Single Instance ตัวนั้นให้ กลับกลายเป็นว่า Activity ตัวถัดไปใน Back Stack ของ Task นั้นจะกลับขึ้นมาทำงานต่อแทน

สำหรับตัวอย่างของ Activity ที่จำเป็นต้องใช้ Launch Mode เป็น Single Instance นั้นยังไม่มีอันที่ชัดเจน ด้วยรูปแบบการทำงานที่เฉพาะเจาะจงและแตกต่างจาก Launch Mode ตัวอื่น ๆ จึงทำให้นักพัฒนาแอปส่วนใหญ่ไม่ได้ใช้ Launch Mode ตัวนี้ (และในหลาย ๆ กรณีก็สามารถใช้ Single Task แทนได้)

singleInstancePerTask

สำหรับ Activity ที่กำหนด Launch Mode เป็น Single Instance Per Task จะคล้ายกับ Single Task เกือบทั้งหมด แต่จุดแตกต่างคือเรื่องของ Task

สำหรับ Activity ที่เป็น Single Task จะอยู่ได้แค่ใน Task ใด Task หนึ่งเท่านั้น แต่สำหรับ Single Instance Per Task จะอนุญาตให้ Activity สามารถมีหลายตัวได้ ขอแค่อยู่คนละ Task กัน

ยกตัวอย่างเช่น แอปจำพวก Web Brower ที่รองรับ Multiple Tab Group ได้ และต้องการให้แต่ละ Tab Group มีหน้า Home เป็นของตัวเอง และเมื่อกดออกจาก Tab Group ใดก็จะให้ทำลาย Task นั้น ๆ ทิ้งด้วย

ดังนั้น Single Instance Per Task จึงเหมาะกับแอปที่มี Task ได้มากกว่าหนึ่งตัว และต้องการให้มีหน้าหลักหรือหน้า Home สำหรับแต่ละ Task แยกกันด้วย

โปรดอ่านบทความต่อไปเพื่อความต่อเนื่อง

อย่างที่บอกไปในตอนแรกว่าหัวข้อเรื่อง Activity Launch Mode นั้นมีรายละเอียดค่อนข้างเยอะ ทำให้เจ้าของบล็อกแบ่งออกเป็น 2 บทความด้วยกัน ดังนั้นเพื่อให้เข้าใจ Activity Launch Mode มากขึ้น ก็อย่าลืมอ่านบทความถัดไปด้วยล่ะ

แหล่งข้อมูลอ้างอิง