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

ดังนั้นบทความนี้จะพามาทำความรู้จักกับคลาสทั้ง 2 ตัวนี้กันครับ

Intent – เห็นกันตั้งแต่สมัยหัดเขียนใหม่ ๆ

นักพัฒนาคุ้นเคยกับ Intent กันด้วยความสามารถที่ทำให้เราสามารถเปิด Activity ตัวอื่นภายในแอปตามที่ต้องการ ซึ่งเป็นพื้นฐานของการทำแอปที่จำเป็นต้องมีหลาย Activity หรือหลายหน้านั่นเอง

แต่ถ้าว่ากันตามนิยามจริง ๆ ของ Intent แล้วคือ มาตรฐานของรูปแบบการสื่อสารที่ใช้ในแอนดรอยด์ (Standard Messaging Mechanism) เพื่อทำให้ App Component บนแอนดรอยด์สามารถรับส่งข้อมูลระหว่างกันได้

โดย App Component ที่ว่าก็คือ

  • Activity
  • Broadcast Receiver
  • Service
val intent: Intent = /* ... */

startActivity(intent)
sendBroadcast(intent)
startService(intent)

และจะมีแค่ App Component เท่านั้นที่สามารถเรียกคำสั่งต่าง ๆ เพื่อนำ Intent ไปใช้งานได้ เพราะคำสั่งเหล่านั้นจะต้องอยู่ภายใต้ Context เท่านั้น และจะเรียกภายในแอปเดียวกันหรือเรียกข้ามแอปก็ได้ โดยขึ้นอยู่กับจุดประสงค์ในการนำไปใช้งาน

ทุกครั้งที่นักพัฒนาสร้าง Intent ขึ้นมาและส่งให้กับปลายทางใด ๆ ก็ตาม ข้อมูลของ Intent จะถูกส่งไปให้ระบบแอนดรอยด์ (Android Platform) เพื่อตรวจสอบความถูกต้องของข้อมูลก่อน แล้วจะทำการสร้างหรือเรียก App Component นั้นตามรูปแบบที่กำหนดไว้ใน Intent พร้อมกับส่ง Intent นั้น ๆ ไปให้ปลายทางด้วย

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

โดย Intent นั้นถูกแบ่งการทำงานออกแบบ 2 แบบด้วยกันคือ

  • Explicit Intent
  • Implicit Intent

Explicit Intent

Explicit Intent คือการสร้าง Intent โดยระบุ App Component ปลายทางแบบเจาะจง ไม่ว่าจะเป็นรูปของ Class หรือ String ก็ตาม

val context: Context = /* ... */

// Specific target app component with class
val intent = Intent(context, TimelineActivity::class.java)
startActivity(intent)

// Specific target app component with string
val intent = Intent().apply {
    setClassName("com.akexorcist", "com.akexorcist.MainActivity")
}
startActivity(intent)

จากโค้ดตัวอย่าง นั่นหมายความว่าโค้ดที่นักพัฒนาเขียนเกี่ยวกับ Intent เพื่อเปลี่ยน Activity ไปมาภายในแอปจะเป็นแบบ Explicit Intent นั่นเอง

Implicit Intent

Implicit Intent คือการสร้าง Intent โดยไม่ได้ระบุ App Component ปลายทางแบบเจาะจง แต่จะกำหนดเงื่อนไขในการทำงานที่ต้องการด้วยรูปแบบของ Action, Type, และ Category แทน และจะใช้กับ Activity เท่านั้น

val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_TEXT, "Hello! Android")
    type = "text/plain"
}
startActivity(intent)

ระบบแอนดรอยด์จะทำการตรวจสอบแอปที่อยู่ภายในเครื่องทั้งหมดว่ามีแอปไหนที่มี App Component ตรงกับเงื่อนไขใน Implicit Intent ดังกล่าวบ้าง

จากนั้นระบบแอนดรอยด์ก็จะแสดงหน้าต่างที่เรียกว่า Chooser Dialog ขึ้นมาเพื่อให้ผู้ใช้เลือกว่าจะต้องการให้แอปไหนทำงานพร้อมกับส่ง Implicit Intent ให้แอปนั้นไปจัดการต่อ

กรณีแบบไหนควรใช้ Intent แบบไหน?

เนื่องจากทั้ง 2 แบบมีความแตกต่างกันที่รูปแบบการระบุ App Component ปลายทาง จึงทำให้ Explicit Intent นิยมใช้เรียก App Component ที่อยู่ภายในแอปเดียวกันมากกว่า และ Implicit Intent จะนิยมใช้ในการเรียกข้ามแอปกัน

แน่นอนว่าเราจะใช้ Explicit INtent เพื่อเรียก Activity ที่อยู่ในแอปอื่นได้ ยกตัวอย่างเช่น

val packageName = "com.google.android.apps.photos"
val activityClassName = "com.google.android.apps.photos.home.HomeActivity"
val intent = Intent().apply {
    component = ComponentName(packageName, activityClassName)
}
startActivity(intent)

ตัวอย่างคำสั่งนี้คือการเปิดหน้า Home ของแอป Google Photos

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

ดังนั้นจึงสรุปได้ดังนี้

  • Explicit Intent – เหมาะกับการเรียก App Component ที่อยู่ภายในแอปเดียวกัน
  • Implicit Intent – เหมาะกับการเรียก App Component ที่อยู่คนละแอปเพื่อให้ช่วยทำงานบางอย่าง เช่น แชร์ข้อความหรือไฟล์ภาพ, หรือแสดงรูปภาพที่มีในเครื่องเพื่อให้ผู้ใช้เลือกว่าจะเอาภาพไหนมาใช้งานภายในแอปของเรา เป็นต้น

แนบข้อมูลเพิ่มไว้ใน Intent ได้ด้วย Bundle

ในกรณีที่จำเป็นต้องแนบข้อมูลบางอย่างเพื่อส่งไปให้ปลายทางด้วย นักพัฒนาสามารถแนบข้อมูลไว้ใน Intent ด้วยการใช้ Bundle ได้

val intent = Intent(/* ... */).apply {
    putExtra("extra_user_id", 81029102)
    putExtra("extra_name", "Sleeping For Less")
}

ข้อมูลที่ Bundle รองรับก็จะมี Primitive Data, Parcelable, และ Serializable เท่านั้น ดังนั้นถ้าต้องการส่ง POJO หรือ Data Class ไปทั้งก้อนก็ให้ทำเป็น Parcelable ก่อน

ด้วยวิธีนี้จะทำให้ปลายทางสามารถเรียกข้อมูลที่แนบมากับ Intent เพื่อนำไปใช้งานต่อได้ เช่น รับ User ID เพื่อนำไปแสดงข้อมูลต่อ เป็นต้น

// Activity
val userId: Int = intent.extras?.getInt("extra_user_id") ?: 0
val name: String? = intent.extras?.getString("extra_name")

Parameter สำหรับ Implicit Intent

เนื่องจาก Explicit Intent กับ Implicit Intent ต่างกันตรงที่การเรียก App Component ปลายทาง ดังนั้นวิธีการกำหนดค่าใน Intent ก็จะต่างกันด้วยเช่นกัน เพราะว่า Explicit Intent จะเจาะจงปลายทางแน่นอนอยู่แล้ว แต่สำหรับ Implicit Intent นี่สิ จะทำยังไง?

Implicit Intent สามารถกำหนด Parameter ที่ใช้ระบุปลายทางได้ดังนี้

  • Action : รูปแบบ Action ของ App Component ปลายทาง
  • Type : ประเภทข้อมูล ถึงแม้ว่าระบบแอนดรอยด์จะรู้อยู่แล้วว่าข้อมูลที่ส่งผ่าน Implicit Intent เป็นรูปแบบไหน แต่การกำหนด Type ก็จะช่วยกรองรูปแบบข้อมูลของ App Component ปลายทางที่ไม่ต้องการออกไปได้
  • Category : หมวดหมู่ของ App Component ปลายทาง

สามารถกำหนด Parameter อย่างใดอย่างหนึ่งหรือทั้งหมดก็ได้ ขึ้นอยู่กับรูปแบบที่ App Component ปลายทางต้องการ

แต่ Action ถือว่าเป็นเป็น Parameter สำคัญที่ Implicit Intent ทุกตัวต้องมี เพราะ App Component ปลายทางจะมีการกำหนดไว้ว่าตัวไหนรองรับ Action แบบไหน เพื่อที่จะได้รองรับการทำงานได้ตรงตามกับ Action ดังนั้นอยากจะใช้ Implicit Intent เพื่อให้ App Component ปลายทางทำอะไรก็ควรกำหนด Action ให้ถูกต้อง

ตัวอย่าง Action สำหรับ Activity

  • ACTION_VIEW
  • ACTION_EDIT
  • ACTION_CHOOSER
  • ACTION_CALL
  • ACTION_SEND

ตัวอย่าง Action สำหรับ Broadcast Receiver

  • ACTION_BOOT_COMPLETED
  • ACTION_POWER_CONNECTED
  • ACTION_SCREEN_ON
  • MEDIA_MOUNTED

ซึ่งจริง ๆ แล้ว Action เหล่านี้มันก็คือ String ธรรมดานั่นเอง

const val ACTION_GET_CONTENT = "android.intent.action.GET_CONTENT"

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

Action with Data

Action สำหรับ Activity บางตัวอาจจะต้องมีการกำหนด Data ที่อยู่ในรูป Uri ด้วย เพื่อให้สามารถทำงานได้ ยกตัวอย่างเช่น เจ้าของบล็อกอยากให้ในแอปมีปุ่มกดเพื่อโทรออกไปยังเบอร์ที่ต้องการ ก็จะใช้คำสั่ง Implicit Intent แบบนี้

val intent = Intent(Intent.ACTION_CALL).apply {
    data = Uri.parse("tel:1150")
}
startActivity(intent)
คำสั่งข้างบนเป็นแค่คำสั่งบางส่วนเท่านั้น จริง ๆ แล้วต้องประกาศ Permission ใน Android Manifest และครอบด้วยคำสั่งสำหรับ Runtime Permission เพื่อให้ทำงานบน Android 6.0 Marshmallow ขึ้นไปอย่างถูกต้อง

Action with Type

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

const val GALLERY_REQUEST_CODE = 99

fun openGalleryImagePicker() {
    val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
        type = "image/*"
    }
    startActivityForResult(intent, GALLERY_REQUEST_CODE)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == GALLERY_REQUEST_CODE && resultCode == RESULT_OK) {
        // Do something
    }
}

สำหรับการใช้ Implicit Intent เพื่อให้ผู้ใช้เลือกข้อมูลจากแอปตัวอื่น ๆ จะต้องใช้คำสั่ง startActivityForResult แทน (เพราะมีการส่งข้อมูลกลับมา) ส่วนผลลัพธ์ที่ได้ก็จะถูกส่งเข้ามาใน onActivityResult

ส่วน Request Code จะเป็นเลขใด ๆ ก็ได้ ถ้ามีการเรียก Implicit Intent ที่ทำงานคล้ายกันหลายตัว ควรจะเป็นเลขที่ไม่ซ้ำกัน เช่น ในหน้านั้นสามารถเลือกรูปจาก Gallery และเลือกเพลงจากใน File Explorer ได้ ควรจะมี Request Code แยกกัน เพื่อให้สามารถแยก Action ได้ เพราะทั้งคู่มี Action ที่เหมือนกัน ต่างกันที่ Type ของข้อมูล

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

val EXTRA_ID = "extra_id"
val EXTRA_NAME = "extra_name"

val id: String = /* ... */
val name: String = /* ... */
val intent = Intent().apply { 
    putExtra(EXTRA_ID, id)
    putExtra(EXTRA_NAME, name)
}
setResult(Activity.RESULT_OK, intent)
finish()

จะเห็นว่าสามารถกำหนด Intent ที่จะส่งกลับไปยัง Activity ก่อนหน้าได้ด้วยคำสั่ง setResult ที่สามารถกำหนด Result Code ได้ตามใจชอบ และถ้ามีข้อมูลที่จะส่งกลับไปก็แนบ Intent เข้าไปได้ด้วย

โดยข้อมูลดังกล่าวก็จะแนบกลับไปใน onActivityResult นั่นเอง

const val EXTRA_ID = "extra_id"
const val EXTRA_NAME = "extra_name"

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == GALLERY_REQUEST_CODE && resultCode == RESULT_OK) {
        val id = data?.getStringExtra(EXTRA_ID)
        val name = data?.getStringExtra(EXTRA_NAME)
        /* .. */
    }
}

กรณีที่ต้องการเรียก App Component ปลายทางที่มีการส่งข้อมูลกลับมาให้ นักพัฒนาสามารถใช้ Activity Result API แทนได้นะ อ่านได้ที่บทความการเรียกใช้งาน Activity ที่มีการส่งข้อมูลกลับด้วย Activity Result API

การเรียกใช้งาน Activity ที่มีการส่งข้อมูลกลับด้วย Activity Result API
Activity Result API เป็นหนึ่งในความสามารถของ AndroidX ที่จะช่วยให้นักพัฒนาเปลี่ยนวิธีการใช้คำสั่ง startActivityForResult ให้ง่ายกว่าที่เคยเป็นมา

Action with Category

และนอกจากนี้บาง Action ก็เรียกใช้งานคู่กับ Category เช่นกัน ยกตัวอย่างเช่น Intent ที่จะสั่งเปิดแอปใด ๆ ที่มีอยู่บน Home Screen หรือ Apps Screen

เจ้าของบล็อกจะต้องใช้คำสั่งแบบนี้

val intent = Intent(Intent.ACTION_MAIN).apply {
    addCategory(Intent.CATEGORY_LAUNCHER)
}
startActivity(intent)

สำหรับคำสั่ง Category จะเป็น addCategory แทน เพื่อให้สามารถเพิ่ม Category ได้มากกว่าหนึ่งรูปแบบ

ตัวอย่าง Category ที่มีในระบบแอนดรอยด์

  • CATEGORY_DEFAULT
  • CATEGORY_LAUNCHER
  • CATEGORY_PREFERENCE
  • CATEGORY_CAR_DOCK
  • CATEGORY_APP_MUSIC

ซึ่งเบื้องหลังของ Category ก็คือ String แบบเดียวกับ Action นั่นเอง และสามารถ Custom เพื่อใช้งานเองได้ด้วย

const val CATEGORY_APP_MUSIC = "android.intent.category.APP_MUSIC"

Action with Extra Data

อย่างที่รู้กันอยู่แล้วว่า Intent สามารถแนบข้อมูลผ่าน Bundle ไปได้ด้วย ดังนั้น Implicit Intent ก็สามารถแนบข้อมูลผ่าน putExtra ได้เลย ขึ้นอยู่กับว่าปลายทางต้องการข้อมูลอะไรบ้าง

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

val intent = Intent(Intent.ACTION_SENDTO).apply {
    data = Uri.parse("mailto:")
    putExtra(Intent.EXTRA_EMAIL, "[email protected]")
    putExtra(Intent.EXTRA_SUBJECT, "Lorem ipsum dolor sit amet")
    putExtra(Intent.EXTRA_TEXT, "Cras in gravida purus, id tristique velit. Vivamus semper magna massa...")
}
startActivity(Intent.createChooser(intent, "Send an email via"))

การครอบ Intent อีกชั้นด้วยคำสั่ง Intent.createChooser(Intent intent, String message) จะทำให้ผู้ที่หลงเข้ามาอ่านสามารถกำหนดคำที่จะแสดงในหน้าต่าง Chooser Dialog ได้ ซึ่งจะมีผลกับแอนดรอยด์ในบางเวอร์ชันเท่านั้น

ตัวอย่าง Extra Data ที่มีในระบบแอนดรอยด์

  • EXTRA_EMAIL
  • EXTRA_PHONE_NUMBER
  • EXTRA_STREAM
  • EXTRA_SUBJECT
  • EXTRA_TITLE

จะต้องใช้ Action แบบไหน? Type อะไร? แนบ Data ด้วยหรือป่าว? ต้องกำหนด Category มั้ย? และต้องส่ง Extra Data อะไรบ้าง? จะขึ้นอยู่กับแอปปลายทางล้วน ๆ ว่ากำหนดรูปแบบการรับข้อมูลไว้ยังไง

ดังนั้นนักพัฒนาจะต้องไปหาข้อมูลเอง หรือจะไปดูรูปแบบการส่งข้อมูลผ่าน Intent เบื้องต้นที่ทางทีมพัฒนาแอนดรอยด์ได้กำหนดไว้ใน Common Intents [Android Developers] ก็ได้

Common intents | Android Developers
An intent lets you start an activity in another app by describing an action you'd like to perform (such as "view a map" or "take a picture") in an Intent object. This type of intent is called an implicit intent because…

Flag

สำหรับกำหนดรูปแบบการทำงาน Task และ Back Stack ของ App Component ปลายทาง

val context: Context = ...
val intent = Intent(context, TimelineActivity::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
}
startActivity(intent)

คำสั่งข้างบนนี้จะเป็นการสร้าง TimelineActivity ขึ้นมาพร้อมกับเคลียร์ Activity Task ที่อยู่ใน Back Stack ทิ้งทั้งหมด

ตัวอย่าง Flag ที่มีให้ใช้

  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_SINGLE_TOP
  • FLAG_RECEIVER_FOREGROUND
  • FLAG_RECEIVER_NO_ABORT

สามารถอ่านรายละเอียดของ Flag เหล่านี้เพิ่มเติมได้ในบทความ Task และ Back Stack ตอนที่ 6 - Activity Launch Mode [2/2]

Task และ Back Stack ตอนที่ 6 - Activity Launch Mode [2/2]
สำหรับบทความนี้ไม่ขอเกริ่นอะไรมาก เพราะเป็นบทความที่ต่อเนื่องมาจากบทความก่อนหน้า ที่อธิบายเรื่องราวของ Activity Launch Mode โดยเฉพาะ

Intent Filter — ส่งมาแบบไหนไม่รู้ แต่จะเอาแค่แบบนี้

แล้วฝั่ง Activity ปลายทางกำหนดรูปแบบของ Implicit Intent ไว้ยังไงล่ะ?

เพื่อให้ง่ายต่อการทำงานร่วมกับ Implicit Intent ทางแอนดรอยด์จึงได้มีสิ่งที่เรียกว่า Intent Filter ขึ้นมาให้ใช้ นั่นก็คือ <intent-filter /> ที่จะต้องประกาศไว้ใน Android Manifest

<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application /* ... */ >
        <activity android:name=".MainActivity">
             <intent-filter> 
                 <action android:name="android.intent.action.MAIN" /> 

                 <category android:name="android.intent.category.LAUNCHER" /> 
             </intent-filter> 
        </activity>
    </application>
</manifest>

โดย Intent Filter จะช่วยให้ผู้ที่หลงเข้ามาอ่านสามารถกำหนดได้ว่า App Component ตัวไหนรับ Parameter ของ Implicit Intent แบบไหนได้บ้าง

ยกตัวอย่างจากอันที่มีอยู่เลยละกัน

<activity android:name=".Main2Activity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
  • Action : android.intent.action.Main หรือ Intent.ACTION_MAIN เป็นการกำหนดว่าให้ Activity ตัวนี้เป็น Activity หลักของแอปตัวนี้ (เมื่อแอปเริ่มทำงานจะเข้า Activity ตัวนี้เป็นตัวแรก)
  • Category : android.intent.category.LAUNCHER หรือ Intent.CATEGORY_LAUNCHER เป็นการกำหนดว่าให้สร้างไอคอนของแอปไว้ที่หน้า Launcher ของแอนดรอยด์ และเมื่อกดที่ไอคอนของแอปดังกล่าวก็จะเป็นการสั่งเปิด Activity ตัวนี้ทันที
นั่นหมายความว่าการที่แอปติดตั้งลงในเครื่องแล้วมีไอคอนโผล่ขึ้นมาใน Home Screen หรือ Apps Screen ก็เป็นเพราะการกำหนดค่าแบบนี้ลงใน Android Manifest นั่นเอง

ดังนั้น Intent Filter ก็คือ XML Tag ที่จะต้องใส่ไว้ใน Android Manifest เพื่อให้ระบบแอนดรอยด์สามารถตรวจสอบได้ง่ายว่า App Component ตัวไหนรองรับ Explicit Intent แบบไหนบ้าง

โดยจะกำหนดไว้ใน Activity, Broadcast Receiver หรือ Service ตัวไหนก็ได้ตามที่ต้องการ (ให้เหมาะสมกับการทำงานของ App Component นั้นด้วย)

เตรียม Intent Filter ใน Android Manifest ให้พร้อม

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

val message = "Welcome to Sleeping For Less"
val intent = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
    putExtra(Intent.EXTRA_TEXT, message)
}
startActivity(intent)

จาก Intent ดังกล่าวก็จะต้องประกาศ​ <intent-filter ไว้ใน Android Manifest แบบนี้

<activity android:name=".PostStatusActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>

และถ้ามี Mime Type หลายแบบใน App Component เดียวกัน ก็ประกาศแบบนี้ได้เลย

<activity android:name=".PostStatusActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
        <data android:mimeType="image/*"/>
    </intent-filter>
</activity>

หรือถ้าต้องการแบ่ง Intent Filter เป็นสองชุดใน App Component เดียวกันก็สามารถทำแบบนี้ได้เลย

<activity android:name=".PostStatusActivity">
    <intent-filter>
        <action android:name="android.intent.action.EDIT"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>

อ่านค่าใน Implicit Intent ของ App Component ปลายทาง

ถ้า Intent Filter ตรงกับ App Component ที่เตรียมไว้ ก็จะทำให้ App Component นั้นทำงานทันที โดยค่าที่ถูกส่งมาจากต้นทางสามารถเช็คได้จากค่าที่อยู่ใน Intent

val intent: Intent = /* ... */

val action: String? = intent.action
val category: Set<String> = intent.categories
val uri: Uri? = intent.data
val mimeType: String? = intent.type
val message: String? = intent.getStringExtra(Intent.EXTRA_TITLE)

ส่วนจะเอาค่าเหล่านั้นไปทำอะไรบ้าง ก็แล้วแต่นักพัฒนาเลย

Pending Intent : ไม่ค่อยเป็นที่จดจำ แต่ก็สำคัญเหมือนกันนะ

Pending Intent เป็น Wrapper ที่มี Intent อยู่ข้างใน เอาไว้ใช้งานบางอย่างที่ยังไม่อยากให้ Intent ณ ตอนนั้น แต่อยากจะให้รอจนกว่าจะถึงเงื่อนไขที่กำหนด แล้ว Intent ที่อยู่ในนั้นถึงจะทำงาน

โดย Pending Intent จะถูกใช้กับการทำงานบางส่วนของระบบแอนดรอยด์ ยกตัวอย่างเช่น Notification Manager หรือ Alarm Manager เป็นต้น

เพราะเวลาที่ Notification แสดงอยู่บน Notification Drawer จะต้องรอผู้ใช้กด Notification นั้นเสียก่อน แล้ว Pending Intent ที่อยู่ข้างในถึงจะเริ่มทำงานและทำให้ Intent ถูกส่งต่อให้ระบบแอนดรอยด์เพื่อเรียก App Component ปลายทางนั่นเอง

นั่นจึงเป็นที่มาว่านักพัฒนาจะต้องสร้าง Intent เก็บไว้ใน Pending Intent เพื่อส่งต่อให้ Notification Manager ไปจัดการต่อ

ตัวอย่างคำสั่ง Notification ที่มี PendingIntent ด้วย โดยให้กดที่แถบ Notification แล้วเปิดหน้าเว็ป https://akexorcist.dev ผ่าน ACTION_VIEW

val context: Context = /* ... */
val channelId: String = /* ... */
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://akexorcist.dev"))
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val builder = NotificationCompat.Builder(context, channelId).apply {
    setContentText("Content Text")
    setContentTitle("Content Title")
    setSmallIcon(R.drawable.ic_notification)
    setContentIntent(pendingIntent)
}
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(0, builder.build())

จากตัวอย่างนี้ นักพัฒนาสามารถสร้าง Intent แบบไหนก็ได้ตามต้องการเท่าที่ Intent สามารถทำได้

ลองมาดูที่คำสั่งตอนสร้าง Pending Intent กันต่อ

val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)

เผื่อไม่ให้งงว่าในโค้ดตัวอย่างกำหนดค่าอะไรลงไปบ้าง

PendingIntent.getActivity(
    context: Context, 
    requestCode: Int, 
    intent: Intent, 
    flags: Int
)

จะเห็นว่าเป็น Parameter ของ Intent ทั้งหมดเลย ซึ่งเดี๋ยว Pending Intent เอาไปกำหนดลงใน Intent ให้เอง ส่วน Request Code จะใช้กรณีที่รูปแบบการทำงานมีหลายแบบ (เช่น Notification มีหลายประเภท) การกำหนด Request Code จะช่วยให้ App Component ปลายทางสามารถแยกการทำงานได้

สรุป

Intent ถือว่าเป็นพื้นฐานสำคัญที่อยากให้ลองทำความเข้าใจความสามารถและวิธีการใช้งาน ทั้งนี้ต้องดูเรื่องของ Intent Filter ควบคู่กันด้วย เพราะจะช่วยให้เข้าใจรูปแบบการทำงานที่ทีมพัฒนาแอนดรอยด์วางไว้ให้

ส่วน Pending Intent อาจจะไม่ได้ใช้งานบ่อยมากนัก เพราะจะเน้นไปที่เวลาเรียกใช้งาน Notification Manager กับ Alarm Manager ซะมากกว่า แต่ก็อาจจะมี 3rd Party Application บางตัวที่ใช้ Pending Intent ในการทำงานด้วยนะ

ก็หวังว่าบทความนี้จะช่วยให้ผู้ที่หลงเข้ามาอ่านได้เข้าใจเกี่ยวกับ Intent, Intent Filter และ Pending Intent มากขึ้นนะ

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