26 April 2013

การรับข้อมูล Intent จากแอปอื่นๆ

Updated on


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

        อย่างแรกเลยให้ผู้ที่หลงเข้ามาอ่าน มาทำความเข้าใจกันก่อนว่าไฟล์ Activity ที่ใช้เป็นตัวอย่าง ไม่ใช่ Main.java แบบเดิมแล้ว เพราะในบทความนี้ ในการรับค่าจาก Intent จากแอพอื่นนั้น ไม่ได้เป็นการเปิดแอพขึ้นมาโดยปกติ แต่ว่าจะให้เปิด Activity ที่เขียนคำสั่งให้รอรับ Intent จากแอพอื่นโดยเฉพาะแทน

        ดังนั้น Main.java จึงปล่อยทิ้งไว้ให้เป็นหน้าแรกเมื่อเปิดแอพ ซึ่งเปิดมาก็จะไม่มีอะไร เพราะคำสั่งจะอยู่ที่ GetContent.java โดยเจ้าของบล็อกจะให้ Activity ใน GetContent.java นั้นเป็น Activity สำหรับรอรับ Intent จากแอพอื่นๆเท่านั้น ดังนั้นจึงไม่สามารถเปิดแอพดังกล่าวขึ้นมาแล้วเปิดหน้านี้ได้


        หน้า GetContent จะเปิดจากการ Intent ด้วยแอพอื่นๆเท่านั้น

        เมื่อเปิดแอพตัวอย่างนี้ตามปกติก็พบกับหน้า Main แบบนี้


        สำหรับ Main.java กับ main.xml คงไม่ต้องอธิบายอะไร ไม่มีโค๊ด เพราะกำหนดใน Layout ให้แสดงข้อความบอกอย่างเดียวเท่านั้นเอง

        ทีนี้มาดูวิธีการทำให้ Activity รับค่าผ่าน Intent จากแอพกันเลย เดิมทีเวลาสร้าง Activity ในโปรเจคใดๆก็ตาม ก็จะต้องประกาศ Activity นั้นๆไว้ใน AndroidManifest.xml ด้วย ยังจำกันได้ใช่มั้ย?

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:name="@style/AppTheme"
    <activity
        android:name=".Main"
        android:label="@string/app_name"
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

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

        เป็น xml ที่คุ้นเคย โดยประกาศ Activity สองตัวคือ Main กับ GetContent และกำหนดให้หน้า Main เป็นหน้าหลักสุด เมื่อเปิดแอพตัวนี้ขึ้นมา ส่วน GetContent ก็แค่ประกาศ Activity ไว้ในนี้เฉยๆ ตามปกติที่คุ้นเคย

        แต่เนื่องจากเจ้าของบล็อกต้องการให้ GetContent รับค่าจาก Intent ดังนั้นจึงต้องมีการประกาศเพิ่มเข้าไปในแท็กของ GetContent ดังนี้

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:name="@style/AppTheme"
    <activity
        android:name=".Main"
        android:label="@string/app_name"
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity
        android:name=".GetContent" >
        <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>
</application>

        จะเห็นว่าเจ้าของบล็อกได้ประกาศ Intent Filter ไว้ข้างในแท็ก โดยมีการกำหนดให้ GetContent รับ Intent แบบ action.SEND และรูปแบบของข้อมูลที่รองรับคือ text/plain กับ image/*

        ดังนั้น GetContent ก็จะสามารถรับข้อมูลได้ทั้ง Text และรูปภาพ โดยเจ้าของบล็อกจะกำหนดว่าถ้ารับข้อมูลมาเป็นแบบ Text ก็จะนำข้อความที่รับมาแสดงลงบน Text View ที่สร้างไว้ และถ้าเป็นรูปภาพใดๆ ก็จะแสดงบน Image View ที่สร้างไว้

        มาดู Layout ของ GetContent กันก่อน จะใช้ชื่อว่า get_content.xml

get_content.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/textView"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="25sp" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

        ให้สังเกตุดีๆ ว่าเจ้าของบล็อกได้ประกาศ Text View และ Image View แต่ Text View ไม่ได้กำหนดข้อความที่จะแสดง Image View ก็ด้วย ทั้งนี้ก็เพราะว่าจะรอให้รับข้อมูลมาก่อนแล้วมากำหนดค่าแทนและ Text View กับ Image View จะซ้อนกันอยู่ตรงกลางหน้าจอถ้าข้อมูลที่ส่งมาเป็น Text ก็จะกำหนดให้ Text View แสดง 

        ส่วน Image View ที่ไม่ได้กำหนดค่าอะไรอยู่แล้วก็จะมองไม่เห็น ดังนั้นก็เสมือนว่า Layout นี้แสดงแค่ Text View อย่างเดียว

        แต่เมื่อข้อมูลที่ส่งมาเป็นรูปภาพใดๆ ก็จะแสดงใน Image View ส่วน Text View ที่ไม่ได้กำหนดค่าอะไร ก็จะมองไม่เห็นแทน ดังนั้น Layout นี้ก็ใช้แสดงค่าทั้งสองแบบโดยใช้ Layout เดียวกัน

        ทีนี้มาดูที่โค๊ดบางส่วนของ GetContent กันก่อน ไว้ค่อยดูโค๊ดเต็มทีหลัง

        สำหรับ GetContent ก็กำหนดโค๊ดเริ่มต้นเหมือนกัน Activity ทั่วไป

import android.app.Activity;
import android.os.Bundle;

public class GetContent extends Activity {
    
    Protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.get_content);

    }

}

        ทีนี้เจ้าของบล็อกก็เพิ่มโค๊ดเข้าไปเป็นคลาส Intent กับ String สองตัว

Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();

        สำหรับคลาส Intent ก็กำหนดให้รับค่าจากการ Intent ของแอพอื่น และ String ทั้งสองตัวคือ action กับ type จะให้ดึงข้อมูลจากคลาส Intent โดยที่ action คือรูปแบบของ Intent จากแอพที่ส่งมาว่าเป็นแบบใด

        ถ้าผู้ที่หลงเข้ามาอ่านคนใดที่อ่านบทความ Intent เก่าๆมาทั้งหมด จะสังเกตุเห็นว่าเวลาที่จะ Intent ไปแอพภายนอก จะมีการกำหนดค่านี้ด้วย ก็คือ ACTION_VIEW, ACTION_SEND หรือ ACTION_GET_CONTENT เพราะว่าใน Activity หนึ่ง จะสามารถกำหนดให้รับ Action ได้หลายแบบ ดังนั้นก็ต้องมีการเช็คด้วยว่าเป็น Action แบบไหน จะได้เรียกคำสั่งที่ตรงกัน แต่ตัวอย่างนี้กำหนดให้รับข้อมูลที่เป็น Action แบบ ACTION_SEND เท่านั้น

        ส่วน type คือรูปแบบของข้อมูลที่ส่งมาหรือ Data Type นั่นเอง ถ้าจำกันได้ก็จะเห็นว่าก่อน Intent ไปแอพภายนอกจะต้องกำหนดด้วย ดังนั้นก็จะมีการเช็ครูปแบบข้อมูลได้ เวลาที่รับข้อมูลหลายแบบด้วยกัน จากตัวอย่างนี้ได้กำหนดใน AndroidManifest.xml ว่ารับข้อมูลสองแบบ คือ text/plain ที่เป็นข้อความ Text และ image/* ที่เป็นข้อมูลรูปภาพใดๆ เวลารับข้อมูลมาก็จะต้องเช็คด้วยว่าเป็นแบบใด จะได้ใช้คำสั่งให้ตรง

        ถ้าข้อมูลเป็น text/plain ก็ดึงข้อมูลมาเป็น String แล้วแสดงใน Text View แต่ถ้าข้อมูลเป็น image/* ก็ดึงข้อมูลมาเป็น Uri แล้วแสดงใน Image View

        ทีนี้มาดูกันต่อว่า เมื่อประกาศและรับค่าจาก Intent มาแล้ว ทำอะไรต่อ

Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();

if (action.equals(Intent.ACTION_SEND) && type != null)) {
    // คำสั่งที่ต้องการ
}

        ก่อนอื่นเจ้าของบล็อกก็จะเช็คก่อนว่า ตัวแปร type ไม่ได้เป็น null ทั้งนี้ก็เพื่อความชัว แล้วเช็คว่า Action เป็น ACTION_SEND หรือไม่

        ถ้าสังเกตดีๆแล้ว Intent.ACTION_SEND จริงๆ ก็คือตัวแปร String ที่เก็บ String ของคำว่า "android.intent.action.SEND" เอาไว้นั่นเอง


        ก็จะเช็คว่าตัวแปร action มีค่าเท่ากับ "android.intent.action.SEND" หรือไม่ เมื่อเงื่อนไขทั้งสองถูกต้อง ก็จะเข้ามาใน If แล้วทำคำสั่งต่อไปนั่นเอง


        เมื่อเช็ค Action แล้ว ก็จะมาเช็ค Type กันต่อว่าเป็นข้อมูลแบบใด

Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();

if (action.equals(Intent.ACTION_SEND) && type != null) {
    if (type.equals("text/plain")) {
                
        // คำสั่งที่ต้องการ

    } else if (type.startsWith("image/")) {
                
        // คำสั่งที่ต้องการ
                
    }
}

        เจ้าของบล็อกก็จะใช้ if - else if เช็คว่าเป็นข้อมูลแบบใด โดยจะเริ่มจากเช็คก่อนว่าเป็นข้อมูลแบบ Text หรือป่าว ก็จะเอาตัวแปร type มาเทียบว่าเป็น "text/plain" หรือไม่ และก็จะเช็คต่อด้วย else if ถ้าเป็นไฟล์รูปภาพหรือไม่ ทีนี้ก็จะเห็นว่าไม่ได้ใช้คำสั่ง equals เหมือนกับ Text แต่ดันไปใช้คำสั่ง startsWith แทนซะงั้น ทำไมกันล่ะ?

        สำหรับคำสั่ง startsWith จะเป็นการเช็คว่า String ตัวนั้นๆ ขึ้นต้นด้วย String ตามที่กำหนดหรือไม่ เอาไว้เช็คคำขึ้นต้น โดยเจ้าของบล็อกจะให้เช็คว่าขึ้นต้นด้วย image/ หรือไม่ 

        ทั้งนี้ก็เพราะว่ารูปภาพกำหนด Type ได้หลายแบบนั่นเอง เช่น image/jpg, image/png, image/bmp หรือ image/* เป็นต้น ซึ่งจริงๆแล้ว ตอนที่ไปกำหนดใน AndroidManifest.xml ได้กำหนดเป็น image/* แล้ว ดังนั้นเวลาที่แอพใดๆก็ตาม กดแชร์ไฟล์ภาพไปที่แอพอื่นๆ ก็จะเห็นแอพตัวนี้ตลอด ไม่ว่าจะกำหนดเป็น image/png หรือ image/jpg ก็ตาม

        แต่ว่าที่ต้องใช้คำสั่ง startsWith เพราะจะได้ไม่ต้องเช็คทุกแบบ ถ้าใช้วิธีเช็ค equals แบบ Text ก็จะต้องมานั่งเช็คแบบนี้แทน

if (type.equals("image/*) && type.equals("image/png) 
        && type.equals("image/pg") && type.equals("image/bmp")) {
                
}

        จะเห็นว่าคำสั่งยาวโดยใช้เหตุ แถมเช็คไม่ครบทุกแบบด้วย เพราะอาจจะมีไฟล์ภาพที่เป็นนามสกุลไฟล์อื่นๆ อีกด้วย ดังนั้นจึงใช้ startsWith เพื่อเช็คว่าขึ้นต้นด้วย image/ ก็พอแล้ว เมื่อเช็ค Action และ Type แล้ว ก็จัดคำสั่งตามเงื่อนไขนั้นๆเลย

Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();

if (Intent.ACTION_SEND.equals(action) && type != null) {
    if ("text/plain".equals(type)) {
        String text = intent.getStringExtra(Intent.EXTRA_TEXT);
        textView.setText(text);
    } else if (type.startsWith("image/")) {
        Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
        imageView.setImageURI(uri);
    }
}

        โค๊ดก็ไม่ได้ยากอะไร สำหรับ Text ก็จะดึงจาก Extra ทันที คล้ายกับคำสั่งดึงข้อมูลที่ส่งผ่าน Intent ระหว่างสอง Activity เมื่อดึงข้อมูลมาก็เก็บไว้ในตัวแปร text แล้วแสดงใน Text View

        ส่วนข้อมูลที่เป็นรูปภาพก็จะแตกต่างออกไป ไม่ได้ดึงข้อมูลตรงๆ เพราะไม่นิยมที่จะส่งข้อมูลภาพผ่าน Intent กันตรงๆอยู่แล้ว เนื่องจากข้อมูลภาพมีขนาดใหญ่ จะสิ้นเปลืองทรัพยากรได้ง่าย ดังนั้นจึงใช้วิธีเก็บไฟล์ภาพลง External Storage หรือ SD แทน แล้วส่ง Uri ของไฟล์นั้นๆ ไปกับ Intent แทน เพื่อประหยัดทรัพยากร
        เมื่อได้ Uri มาแล้วก็จะเอาไปกำหนดในคำสั่งของ Image View เพียงเท่านี้ก็เสร็จเรียบร้อยแล้ว ดูโค๊ดเต็มๆเลยดีกว่า


GetContent.java
package app.akexorcist.intentreceivercontent;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;

public class GetContent extends Activity{    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.get_content);
        
        TextView textView = (TextView)findViewById(R.id.textView);
        ImageView imageView = (ImageView)findViewById(R.id.imageView);
        
        Intent intent = getIntent();
        String action = intent.getAction();
        String type = intent.getType();

        if (Intent.ACTION_SEND.equals(action) && type != null) {
            if ("text/plain".equals(type)) {
                String text = intent.getStringExtra(Intent.EXTRA_TEXT);
                 textView.setText(text);
            } else if (type.startsWith("image/")) {
                Uri uri = (Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM);
                imageView.setImageURI(uri);
            }
        }
    }
}


get_content.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/textView"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="25sp" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="app.akexorcist.intentreceivercontent"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="8" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="Main"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="GetContent" >
            <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>
    </application>

</manifest>

        เวลาที่ทดสอบก็ให้เปิดแอพใดๆ ก็ได้ที่สามารถแชร์ Text หรือรูปไปยังแอพอื่นๆได้



        สำหรับผู้ที่หลงเข้ามาอ่านคนใดต้องการไฟล์ตัวอย่างสามารถดาวน์โหลดได้จาก Intent Receiver Content [Google Drive]



บทความที่เกี่ยวข้อง

        การใช้ Intent สำหรับแชร์ข้อความ String [Send]
        การใช้ Intent สำหรับแชร์ข้อความสำหรับ Email [Send]
        การใช้ Intent เพื่อเปิด URL [View]
        การใช้ Intent เพื่อเปิดแผนที่ [View]
        การใช้ Intent เพื่อเปิดไฟล์ใดๆ [View]
        การเรียกเปิดแอพฯอื่นๆ ด้วย Intent
        การใช้ Intent สำหรับแชร์ไฟล์ใดๆ [Send]
        การเลือกไฟล์ภาพจาก Gallery ด้วย Intent [Result]
        การใช้งานกล้องเพื่อถ่ายภาพแบบง่ายๆด้วย Intent [Result]
        การใช้งานกล้องเพื่อบันทึกวีดีโอแบบง่ายๆด้วย Intent [Result]
        การอ่าน QR Code และ Barcode ด้วย Intent [Result]
        การรับข้อมูล Intent จากแอพฯอื่นๆ [Get Content]
        การรับข้อมูล Intent จากแอพฯอื่นแล้วส่งข้อมูลกลับไป [Result Content]