17 January 2017

[Android Code] Intent และ Pending Intent - ส่งใจ ไปหาเธอ


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

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

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

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

        ซึ่ง Component ที่ว่าก็คือ
        • Activity
        • BroadcastReceiver
        • Service

        ถ้าสังเกตดีๆจะเห็นว่า Component ทั้ง 3 ตัว จะต้องมีการประกาศไว้ใน Android Manifest ทุกครั้งเมื่อสร้างขึ้นมาใช้งาน

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

Explicit Intent

        เป็นรูปแบบการใช้งาน Intent ที่มีการระบุ Component หรือ Class ปลายทางอย่างเจาะจง


        ยกตัวอย่างคำสั่ง

        กรณีของ Activity - สั่งให้ Activity ที่กำหนด เริ่มทำงานทันที

Intent intent = new Intent(MainActivity.this, TimelineActivity.class);
startActivity(intent);

Intent intent = new Intent();
intent.setClass(MainActivity.this, TimelineActivity.class);
startActivity(intent);

Intent intent = new Intent();
ComponentName componentName = new ComponentName(MainActivity.this, TimelineActivity.class);
intent.setComponent(componentName);
startActivity(intent);


        กรณีของ BroadcastReceiver - สั่งให้ BroadcastReceiver ที่กำหนด เริ่มทำงานทันที

Intent intent = new Intent(MainActivity.this, UpdateReceiver.class);
sendBroadcast(intent);

Intent intent = new Intent();
intent.setClass(MainActivity.this, UpdateReceiver.class);
sendBroadcast(intent);

Intent intent = new Intent();
ComponentName componentName = new ComponentName(MainActivity.this, UpdateReceiver.class);
intent.setComponent(componentName);
sendBroadcast(intent);


        กรณีของ Service - สั่งให้ Service ที่กำหนด เริ่มทำงานทันที

Intent intent = new Intent(MainActivity.this, UploadPhotoService.class);
startService(intent);

Intent intent = new Intent();
intent.setClass(MainActivity.this, UploadPhotoService.class);
startService(intent);

Intent intent = new Intent();
ComponentName componentName = new ComponentName(MainActivity.this, UploadPhotoService.class);
intent.setComponent(componentName);
startService(intent);

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

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

        และถ้าอยากส่งข้อมูลใดๆให้ปลายทางก็จะมีคลาส Bundle ให้แนบข้อมูลจำพวก Primitive Data Type เพื่อส่งไปให้ปลายทางได้

int value = 30;
Intent intent = new Intent(MainActivity.this, TimelineActivity.class);
intent.putExtra("key_value", value);
startActivity(intent);

int value = 30;
Bundle bundle = new Bundle();
bundle.putInt("key_value", value);
Intent intent = new Intent(MainActivity.this, TimelineActivity.class);
intent.putExtras(bundle);
startActivity(intent);

        ซึ่ง Intent ทุกตัวสามารถมีข้อมูลแนบมาได้ ดังนั้นปลายทางที่ Intent ส่งไปก็สามารถดึงข้อมูลแบบนี้ได้ทันทีไม่ว่าจะเป็น Activity, BroadcastReceiver หรือ Service

Implicit Intent

        เป็นรูปแบบการใช้งาน Intent ที่ไม่ได้ระบุ Component หรือ Class อย่างเจาะจง จะเป็นตัวไหนก็ได้ ขอแค่ตรงกับเงื่อนไขที่กำหนดไว้ก็พอ ซึ่งเงื่อนไขที่ว่าก็คือ Action, Type และ Category


        การทำงานของ Implicit Intent จะเป็นการส่ง Intent ไปที่ Android System โดยมีการระบุเงื่อนไขไว้ว่าต้องการปลายทางที่มี Action, Type หรือ Category แบบไหน แล้วเจ้า Android System ก็จะไปดูในเครื่องว่ามีตัวไหนบ้างที่รองรับตามที่กำหนดไว้ (ถ้ามีมากกว่าหนึ่งตัวก็จะให้ผู้ใช้เป็นคนเลือก) แล้วจึงส่ง Intent เพื่อไปสั่งให้เป้าหมายเริ่มทำงาน

        Intent แบบไม่เจาะจงปลายทางนั้นเป็นยังไง? ลองนึกภาพเวลาผู้ที่หลงเข้ามาอ่านกดแชร์ภาพซักภาพจากใน Gallery ดูครับ


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

        นั่นล่ะครับที่เรียกว่า Implicit Intent

        ยกตัวอย่างคำสั่ง

        กรณีของ Activity

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, "Hello Android Fragmentation World");
intent.setType("text/plain");
startActivity(intent);

Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, "Hello Android Fragmentation World");
intent.setType("text/plain");
startActivity(intent);


        กรณีของ BroadcastReceiver

Intent intent = new Intent(Intent.ACTION_SCREEN_ON);
sendBroadcast(intent);

Intent intent = new Intent();
intent.setAction(Intent.ACTION_SCREEN_ON);
sendBroadcast(intent);

        สำหรับ Service จะไม่นิยมใช้ Explicit Intent เพราะว่าการทำงานของ Service มักจะเป็นการเรียกใช้งานแบบเจาะจงเท่านั้น หรือไม่ก็ส่งไปที่ BroadcastReceiver แล้วเรียกให้ Service ทำงานอีกทอดแทน

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

        แต่ถ้าถามว่า สามารถใช้ Explicit Intent ในเพื่อเรียก Component จากแอปฯตัวอื่นๆได้มั้ย? บอกเลยว่าได้ครับ แต่ผู้ที่หลงเข้ามาอ่านจะต้องระบุ Component ปลายทางแบบเจาะจงด้วยนะ (ก็มันคือ Explicit Intent นี่นา)

String packageName = "com.google.android.apps.photos";
String fullClassName = "com.google.android.apps.photos.home.HomeActivity";
Intent intent = new Intent();
intent.setComponent(new ComponentName(packageName, fullClassName));
startActivity(intent);

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

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

        • Explicit Intent เหมาะกับ Component ในแอปฯเดียวกัน เพราะรู้ปลายทางแน่นอน ในกรณีที่ต้องใช้กับ Component ของแอปฯตัวอื่น ควรมีการเช็คว่าแอปฯปลายทางมีอยู่ในเครื่องจริงๆหรือไม่
        • Implicit Intent เหมาะกับ Component ระหว่างแอปฯด้วยกัน เนื่องจากไม่สามารถเจาะจงปลายทางได้อย่างแน่นอน ถึงแม้ว่าจะสามารถใช้กับ Component ภายในแอปฯเดียวกันได้ แต่จะใช้ทำไมในเมื่อรู้ปลายทางแน่นอนอยู่แล้ว (ใช้ Explicit Intent แทนดีกว่ามั้ย?)

Parameter ต่างๆสำหรับ Implicit Intent

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

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

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

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

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

        ตัวอย่าง Action สำหรับ Activity
        • ACTION_VIEW
        • ACTION_EDIT
        • ACTION_CHOOSER
        • ACTION_CALL
        • ACTION_SEND

        ตัวอย่าง Action สำหรับ BroadcastReceiver
        • ACTION_BOOT_COMPLETED
        • ACTION_POWER_CONNECTED
        • ACTION_SCREEN_ON
        • MEDIA_MOUNTED

        สำหรับ Action ของ BroadcastReceiver ที่มีให้ใช้งานนั้น มักจะเรียกใช้งานจาก BroadcastReceiver ปลายทางที่สร้างขึ้นเองมากกว่า เพราะ Action มาตรฐานเหล่านี้จะถูกส่งจาก Android System เป็นหลักอยู่แล้ว ถ้าจะมีการใช้ Action สำหรับ Broadcast ก็จะเป็น Custom Action เพื่อใช้งานเองซะมากกว่า

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

public static final String ACTION_GET_CONTENT = "android.intent.action.GET_CONTENT";

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

Action with Data

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

Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(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 ก็จะส่งข้อมูลกลับมาให้

private static final int GALLERY_REQUEST_CODE = 99;

...

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, GALLERY_REQUEST_CODE);

...

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == GALLERY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        // Do something here 
    }
}


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

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

Action with Category

        และนอกจากนี้บาง Action ก็เรียกใช้งานคู่กับ Category เช่นกัน (ก็ขึ้นอยู่กับ Component ปลายทางนั่นแหละ)

        ยกตัวอย่างเช่น เจ้าของบล็อกต้องการเรียกแสดงรายชื่อแอปฯทั้งหมดที่แสดงอยู่ในหน้า Launcher ของเครื่อง


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

Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
startActivity(intent);


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

        ตัวอย่าง Category ที่มีใน Android System
        • CATEGORY_DEFAULT
        • CATEGORY_LAUNCHER
        • CATEGORY_PREFERENCE
        • CATEGORY_CAR_DOCK
        • CATEGORY_APP_MUSIC

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

public static final String CATEGORY_APP_MUSIC = "android.intent.category.APP_MUSIC";

Action with Extra Data

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

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

Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setType("text/plain");
intent.setData(Uri.parse("mailto:[email protected]"));
intent.putExtra(Intent.EXTRA_SUBJECT, "Why you still miss him?");
intent.putExtra(Intent.EXTRA_TEXT, "Just open your eyes, better guy might be around you");
startActivity(Intent.createChooser(intent, "Send email via"));


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

        ตัวอย่าง Extra Data ที่มีใน Android System
        • EXTRA_EMAIL
        • EXTRA_PHONE_NUMBER
        • EXTRA_STREAM
        • EXTRA_SUBJECT
        • EXTRA_TITLE

        อย่าลืมนะครับว่าจะต้องใช้ Action แบบไหน, Type อะไร, แนบ Data ด้วยหรือป่าว, ต้องกำหนด Category มั้ย และต้องส่ง Extra Data อะไรบ้างนั้นขึ้นอยู่กับแอปฯปลายทางล้วนๆ ว่ากำหนดรูปแบบการรับข้อมูลไว้ยังไง ผู้ที่หลงเข้ามาอ่านต้องไปหาข้อมูลเอาเอง หรือจะไปดูรูปแบบการส่งข้อมูลผ่าน Intent เบื้องต้นที่ทางทีมพัฒนาแอนดรอยด์ได้กำหนดไว้ก็ได้ Common Intents [Android Developers]

Flag

        เป็นค่าที่สามารถกำหนดรูปแบบการทำงาน Task และ Backstack ของปลายทางได้

Intent intent = new Intent(MainActivity.this, TimelineActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);

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

        ตัวอย่าง Flag ที่มีใน Android System ซึ่งจะแยกตาม Component
        • FLAG_ACTIVITY_CLEAR_TOP
        • FLAG_ACTIVITY_SINGLE_TOP
        • FLAG_RECEIVER_FOREGROUND
        • FLAG_RECEIVER_NO_ABORT

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

        รู้ตัวอีกทีก็เผลออธิบายเรื่อง Intent ซะยาว มาถึงตอนนี้ผู้ที่หลงเข้ามาอ่านบางคนก็อาจจะสงสัยต่ออีกว่า

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

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

        เพื่อให้ง่ายต่อการทำงานร่วมกับ Implicit Intent ทางแอนดรอยด์จึงได้มีสิ่งที่เรียกว่า Intent Filter ขึ้นมาให้ใช้ แต่มันไม่ใช่คำสั่งภาษา Java แต่อย่างใดนะ

         เพราะมันคือ <intent-filter> ... </intent-filter> ที่ใช้ใน Android Manifest จ้า คุ้นๆมั้ยเอ่ย?

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.akexorcist.myscratch2">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <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>

        เพราะมันมีให้เห็นอยู่ใน Android Manifest มาตั้งแต่แรกที่สร้างโปรเจคเลยนะ

        โดยเจ้า Intent Filter จะช่วยให้ผู้ที่หลงเข้ามาอ่านสามารถกำหนดได้ว่า Component (Activity, BroadcastReceiver และ Service) ตัวไหนรับ 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 เป็นการกำหนดว่าให้สร้าง App Icon ไว้ที่หน้า Launcher ของแอนดรอยด์ และเมื่อกดที่ App Icon ดังกล่าวก็จะเป็นการสั่งเปิด Activity ตัวนี้ทันที

        การที่แอปฯติดตั้งลงในเครื่องแล้วมี App Icon โผล่ขึ้นมา เป็นเพราะการกำหนดค่าแบบนี้ลงใน Android Manifest นั่นเอง

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


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

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

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

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

String message = "Welcome to Sleeping For Less";
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, message);
startActivity(intent);

        เมื่อรู้คำสั่งต้นทางแล้ว ก็ประกาศ Intent Filter ให้ตรงกัน ดังนี้

<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 หลายแบบใน 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 เป็นสองชุดใน 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 ใน Activity/BroadcastReceiver/Service

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

        การรับค่า Implicit Intent ใน Activity

public class MyActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);

        Intent intent = getIntent();
        String action = intent.getAction();
        Set<String> category = intent.getCategories();
        Uri uri = intent.getData();
        String mimeType = intent.getType();
        String message = intent.getStringExtra(Intent.EXTRA_TITLE);
        
        ...

    }
    
    ...
    
}


         การรับค่า Implicit Intent ใน BroadcastReceiver

public class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Set<String> category = intent.getCategories();
        Uri uri = intent.getData();
        String mimeType = intent.getType();
        String message = intent.getStringExtra(Intent.EXTRA_TITLE);

        ...

    }

    ...
    
}


        การรับค่า Implicit Intent ใน Service

public class MyService extends Service {

    ...

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent.getAction();
        Set<String> category = intent.getCategories();
        Uri uri = intent.getData();
        String mimeType = intent.getType();
        String message = intent.getStringExtra(Intent.EXTRA_TITLE);

        ...
        
        return super.onStartCommand(intent, flags, startId);
    }

    ...

}

        ที่เหลือก็จัดการเองตามใจชอบเลยจ้า

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

        ในที่สุดก็มาถึง PendingIntent แล้วววววว 

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

        เมื่อเทียบกันระหว่าง Intent กับ PendingIntent ก็จะประมาณนี้ล่ะนะ


         • Intent ทำงานทันทีเมื่อสั่งงาน
         • PendingIntent เก็บ Intent ไว้ เมื่อสั่งงานก็จะยังไม่ทำงานในทันที ต้องรอจนกว่าจะเป็นไปตามเงื่อนไขที่กำหนดไว้

         มีการทำงานแบบไหนที่ต้องส่ง Intent ให้ไปเก็บไว้ก่อนด้วยหรอ? ลองนึกถึงการทำงานของ NotificationManager กับ AlarmManager ดูสิ

         เวลาที่ Notification ทำงาน (แสดงอยู่บน Notification Bar) ก็ใช่ว่าผู้ใช้จะกดทันทีซะหน่อย ดังนั้นมันอาจจะถูกปล่อยทิ้งไว้พักใหญ่ๆแล้วค่อยถูกกดก็ได้ นั่นล่ะ!! PendingIntent จะถูกใช้ใน NotificationManager เพื่อเก็บ Intent ไว้จนกว่าผู้ใช้จะกดที่แถบ Notification นั่นเอง

        อาจจะสงสัยกันต่อว่าทำไมต้องลำบากสร้าง PendingIntent ด้วย ทำไมไม่โยน Intent ไปให้มันเก็บตั้งแต่แรกล่ะ? นั่นก็เพราะว่า Intent ไม่สามารถ Execute ได้ด้วยตัวเอง แต่ PendingIntent สามารถทำได้

        ดังนั้นโดยหลักการทำงานของ NotificationManager แล้ว จะต้องสร้าง Intent กับ PendingIntent แล้วเอาไปเก็บไว้ใน NotificationManager


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

NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.akexorcist.com/"));
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
mBuilder.setContentText("Content Text");
mBuilder.setContentTitle("Content Title");
mBuilder.setSmallIcon(R.mipmap.ic_launcher);
mBuilder.setContentIntent(pendingIntent);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1, mBuilder.build());

        ซึ่งผู้ที่หลงเข้ามาอ่านสามารถสร้าง Intent แบบไหนก็ได้ ตามต้องการ จะสั่งเปิด Activity หรือ Service ก็ได้หมด (เท่าที่ Intent มันทำได้น่ะแหละ)

        ทีนี้มาดูที่คำสั่งตอนสร้าง PendingIntent นิดหน่อยดีกว่า

PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);

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

PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags);

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

สรุป

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

        ส่วน PendingIntent อาจจะไม่ได้ใช้งานบ่อยมากนัก เพราะจะเน้นไปที่เวลาเรียกใช้งาน NotificationManager กับ AlarmManager ซะมากกว่า แต่ก็อาจจะมี 3rd Party Application บางตัวที่ใช้ PendingIntent ในการทำงานด้วยนะ (แต่ไม่รู้ตัวไหนมีบ้าง)

        ก็หวังว่าบทความนี้จะช่วยให้ผู้ที่หลงเข้าใจเกี่ยวกับ Intent, Intent Filter และ PendingIntent มากขึ้นนะฮะ (อุตส่าห์เขียนซะยาวเหยียดขนาดนี้)

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

        • Intent vs PendingIntent [AndroidPub]
        • Intent - API Reference [Android Developers]
        • PendingIntent - API Reference [Android Developers]
        • Intents and Intent Filters [Android Developers]
        • What is an Android PendingIntent?
        • Common Intents [Android Developers]




เหล่าพันธมิตรแอนดรอยด์

Devahoy Layer Net NuuNeoI The Cheese Factory Somkiat CC Mart Routine Artit-K Arnondora Kamonway Try to be android developer Oatrice Benz Nest Studios Kotchaphan@Medium Jirawatee@Medium Travispea@Medium