19 April 2013

การใช้ Intent สำหรับแชร์ไฟล์ให้แอปอื่นๆ

Updated on


         จากบทความเก่าที่เป็นการแชร์ข้อมูลที่เป็น Text และ Email แต่ทั้งสองบทความนั้นก็ยังเป็นข้อมูลที่เป็นข้อความอยู่ดี คราวนี้ก็มาถึงการแชร์ข้อมูลที่เป็นไฟล์ต่างๆ กันดูบ้าง

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

        แต่ถ้าเกิดเป็นไฟล์แปลกๆล่ะ? ก็จะพบปัญหาไม่มีแอพที่รองรับ ดังนั้นจึงทำโค๊ดอีกส่วนไว้ให้ สำหรับแชร์ไฟล์ใดๆก็ตาม


         แต่สำหรับโค๊ดในการนำไฟล์ต่างๆ มาแชร์นั้นจะต่างจากข้อความ Text เพราะว่าไฟล์เหล่านี้ต้องมีตัวตนอยู่ในเครื่องก่อนอยู่แล้วนั่นเอง ในขณะที่ข้อความ Text สามารถสร้างขึ้นมาเองได้จากการพิมใน Edit Text และในการดึงข้อมูลไฟล์มาแชร์ จะใช้วิธีอิงที่อยู่ของไฟล์โดยใช้คลาส Uri

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

        ทำไมต้องมานั่งก๊อปไฟล์ลง External Storage หรือ SD ก่อนล่ะ ?? ดึงไฟล์โดยตรงจากโฟลเดอร์ assets หรือ res/raw เลยไม่ง่ายกว่าหรอ?

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

        ทีนี้มาดูคำสั่งการนำที่อยู่ของไฟล์มาเก็บไว้ในคลาส Uri กันก่อนเลย

Uri uri = Uri.fromFile(new File("file:///sdcard/ที่อยู่ไฟล์");

        ตัวอย่าง

Uri uri = Uri.fromFile(new File("file:///sdcard/tmp/image_logo.png");

        จากคำสั่งข้างต้นจะดึงจากไฟล์จาก /tmp/image_logo.png

        "เครื่องที่ใช้ทดสอบไม่มี SD Card จะกำหนดที่อยู่ยังไง??" ผู้ที่หลงเข้ามาอ่านหลายๆคนอาจจะสงสัย เพราะจากโค๊ดข้างบนเจ้าของบล็อกกำหนดให้ดึงไฟล์ที่อยู่ใน SD Card นี่นา แต่ถ้าเป็นเครื่องที่ไม่มี SD Card ล่ะ จะต้องทำยังไงล่ะ?

        ถึงแม้ว่าเครื่องที่ไม่มีช่องใส่ SD Card ก็สามารถใช้คำสั่งนั้นได้ เพราะว่าเครื่องเหล่านี้จะ Path มาที่ Intenal Storage ให้เอง

        สำหรับตัวแปรของคลาส Intent ก็ยังคงกำหนดเป็น Send เหมือนเดิม

Intent intent = new Intent(Intent.ACTION_SEND);

        ทีนี้ก็กำหนดข้อมูลที่จะส่งไปด้วย Intent โดยใช้คำสั่งดังนี้ได้เลย

intent.putExtra(Intent.EXTRA_STREAM, uri);

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

String mimeType = MimeTypeMap.getSingleton()

        .getMimeTypeFromExtension(MimeTypeMap

                .getFileExtensionFromUrl(file.getPath()));

intent.setType(mimeType);

        ตัวแปร mimeType ก็จะเก็บ String สำหรับกำหนด File Type ไว้ โดยอิงจากนามสกุลไฟล์ สมมติว่าไฟล์ดังกล่าวมีนามสกุลเป็น png ก็จะออกมาเป็น "image/png" ถ้าเป็ฯ mp4 ก็จะเป็น "video/mp4" เป็นต้น นี่จึงเป็นสาเหตุที่เจ้าของบล็อกทำโค๊ดรวมสำหรับแชร์ไฟล์ทุกแบบ

        แต่คำสั่งนี้ก็มีปัญหาอย่างที่บอกในตอนแรก สำหรับไฟล์นามสกุลแปลกๆก็จะเกิดปัญหาไม่มีแอพที่สามารถรองรับไฟล์นั้นๆ ได้เลย ดังนั้นเจ้าของบล็อกจึงเพิ่มโค๊ดอีกแบบไว้ให้ สำหรับแก้ปัญหาดังกล่าว จะให้มองว่าเป็นไฟล์หนึ่งไฟล์ แอพที่สามารถรองรับไฟล์นั้นๆ ได้ ก็จะเป็นแอพที่เกี่ยวกับการส่งข้อมูลหรือไฟล์ใดๆ ทั้งหมด เช่น Gmail, Google Drive หรือ Skype เป็นต้น โดยใช้คำสั่งดังนี้

intent.setType("file/*");

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

        ภาพซ้ายมือจะเห็นว่ามองเป็นไฟล์วีดีโอที่มีนามสกุลเป็น mp4 แอพที่มีให้เลือกก็จะเป็นแอพที่รองรับกับไฟล์วีดีโอได้ทั้งหมด เช่น YouTube เมื่อเลือก YouTube ก็จะเป็นการอัพโหลดไฟล์วีดีโอLine เมื่อเลือก Line ก็จะเป็นการส่งไฟล์วีดีโอให้บุคคลที่ต้องการ

        ภาพขวามือจะมองว่าเป็นไฟล์ใดๆ ก็จะมีแต่แอพที่รองรับไฟล์ใดๆ เช่น ไม่มี Line ให้เลือก เพราะ Line รองรับการแชร์ไฟล์วีดีโอ แต่ว่าตอนนี้มองเป็นไฟล์ใดๆ ซึ่ง Line ไม่รองรับการส่งไฟล์ใดๆ ซึ่งความต่างกันของ File Type อยู่ที่ "video/*" กับ "file/*" นั่นเอง

        ดังนั้นคำสั่งที่ยกตัวอย่างมาก็จะมีทั้งหมด ดังนี้

Uri uri = Uri.fromFile(new File("file:///sdcard/tmp/vid.mp4");
Intent intent = new Intent(Intent.ACTION_SEND);
String mimeType = MimeTypeMap.getSingleton()
        .getMimeTypeFromExtension(MimeTypeMap
                .getFileExtensionFromUrl(file.getPath()));
intent.setType(mimeType);
//intent.setType("file/*");
intent.putExtra(Intent.EXTRA_STREAM, uri );
startActivity(Intent.createChooser(intent, "Share file via"));

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

File file = new File("file:///sdcard/tmp/vid.mp4");

ContentValues content = new ContentValues(4);
content.put(Video.VideoColumns.TITLE, "My Video Title");
content.put(Video.VideoColumns.DATE_ADDED
        , System.currentTimeMillis() / 1000);
content.put(MediaStore.Video.Media.DATA, file.getAbsolutePath());
ContentResolver resolver = getContentResolver();
Uri uri = resolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI
        , content);

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("video/*");
intent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(intent, "Share video via"));

        เพราะว่าไฟล์วีดีโอจะต้องมีการกำหนดค่าบางอย่างให้กับไฟล์ เผื่อไว้สำหรับแอพ Youtube (มีแต่แอพนี้ที่เจอปัญหาที่ว่า) สำหรับแอพอื่นๆ คำสั่งในส่วนนี้ก็ไม่มีผลอะไรอยู่ดี ใช้ได้ปกติ

        ทีนี้ขอเพิ่มฟังก์ชันเข้าไปหน่อย ซึ่งไม่ได้เกี่ยวกับตัวอย่างโดยตรง ใช้สำหรับก๊อปไฟล์จาก assets ไปไว้ใน External Storage หรือ SD

public File assetToFile(String filePath) {
    new File(Environment.getExternalStorageDirectory().getAbsolutePath()
            , "tmp").mkdir();
    String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);
    File file = new File(Environment.getExternalStorageDirectory()
            , "tmp/" + fileName);
        
    try {
        InputStream is = getResources().getAssets().open(filePath);
            OutputStream out = new FileOutputStream(file);
            byte[] buffer = new byte[is.available()];
            is.read(buffer);
            out.write(buffer, 0, buffer.length);
            is.close();
            out.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return file;
}

เนื่องจากมีการก๊อปไฟล์ลง External Storage หรือ SD Card ด้วย การแก้ไขข้อมูลในนั้น จะต้องมีการประกาศ Permission ทุกครั้ง โดยประกาศใน AndroidManifest.xml (ทุก Permission ประกาศที่นี่)

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />


        ทีนี้ก็มาดู โค๊ดตัวอย่างในบทความนี้กันเลยดีกว่า
main.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"
    android:gravity="center" >

    <Button
        android:id="@+id/buttonIntent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="Share" />

</RelativeLayout>

        สำหรับ Layout ในตัวอย่างนี้ก็มีแต่ Button เท่านั้น เอาไว้กดปุ่มเพื่อแชร์ไฟล์

Main.java
package app.akexorcist.intentsharefile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.provider.MediaStore.Video;
import android.provider.MediaStore.Video.Media;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.MimeTypeMap;
import android.widget.Button;

public class Main extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    /**** Document from assets ****/
    final File file = assetToFile("worksheet/image_logo.png");
    //final File file = assetToFile("worksheet/doc.docx");
    //final File file = assetToFile("worksheet/IRF740.pdf");
    //final File file = assetToFile("sound_effect.mp3");
    //final File file = assetToFile("my_video.mp4");
    
    /**** Document from external storage (SD Card) ****/
    //final File file = new File("file:///sdcard/tmp/image_logo.png");
    //final File file = new File("file:///sdcard/tmp/doc.docx");
    //final File file = new File("file:///sdcard/tmp/IRF740.pdf");
    //final File file = new File("file:///sdcard/tmp/sound_effect.mp3");
    //final File file = new File("file:///sdcard/tmp/my_video.mp4");
    
    Button buttonIntent = (Button)findViewById(R.id.buttonIntent);
    buttonIntent.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            
            /*** For any file sharing, except video file ***/
            
            Intent intent = new Intent(Intent.ACTION_SEND);
            String mimeType = MimeTypeMap.getSingleton()
                    .getMimeTypeFromExtension(MimeTypeMap
                            .getFileExtensionFromUrl(file.getPath()));
            intent.setType(mimeType);
            //intent.setType("file/*");
            intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
            startActivity(Intent.createChooser(intent, "Share file via"));
            
            /* 
             * For video sharing 
             *
            ContentValues content = new ContentValues(4);
            content.put(Video.VideoColumns.TITLE, "My Video Title");
            content.put(Video.VideoColumns.DATE_ADDED
                    , System.currentTimeMillis() / 1000);
            content.put(MediaStore.Video.Media.DATA, file.getAbsolutePath());
            ContentResolver resolver = getContentResolver();
            Uri uri = resolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI
                    , content);

            Intent intent = new Intent(Intent.ACTION_SEND);
            intent.setType("video/*");
            intent.putExtra(Intent.EXTRA_STREAM, uri);
            startActivity(Intent.createChooser(intent, "Share video via"));
             * 
             */
            
            }
        });
    }
    
    public File assetToFile(String filePath) {
        new File(Environment.getExternalStorageDirectory().getAbsolutePath()
                , "tmp").mkdir();
        String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);
        File file = new File(Environment.getExternalStorageDirectory()
                , "tmp/" + fileName);
        
        try {
            InputStream is = getResources().getAssets().open(filePath);
            OutputStream out = new FileOutputStream(file);
            byte[] buffer = new byte[is.available()];
            is.read(buffer);
            out.write(buffer, 0, buffer.length);
            is.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }
}

        1. ตัวแปรของคลาส File ที่สร้างไว้เป็นตัวอย่างในการกำหนดไฟล์ที่จะแชร์ โดยไฟล์จะอยู่ในโฟลเดอร์ assets ซึ่งจะก๊อปไว้ใน External Storage หรือ SD ก่อนจะนำไปใช้งานเพื่อแชร์ โดยจะใช้ฟังก์ชัน assetToFile เพื่อก๊อปไฟล์

        2. สำหรับกรณีปกติที่เรียกจากไฟล์ที่อยู่ใน External Storage หรือ SD โดยตรง

        3. ประกาศและกำหนดค่าให้กับ Button ที่ได้สร้างไว้ และกำหนด Listener ด้วย

        4. คำสั่งสำหรับแชร์ไฟล์ที่ต้องการ โดยกำหนดไฟล์จากที่เก็บไว้ในคลาส Uri แล้วแสดงรายชื่อแอพที่ต้องการแชร์ โดยมีหัวข้อของรายชื่อว่า "Share file via" ส่วนรายละเอียดสำหรับการกำหนด File Type ให้ดูที่หมายเลข 5 กับ 6 แทน

        5. กำหนด File Type ให้กับ Intent โดยอิงจากนามสกุลของไฟล์ที่จะแชร์

        6. ในกรณีที่ต้องการกำหนดให้เป็นไฟล์ใดๆ ให้ใช้คำสั่งนี้แทนหมายเลข 5

        7. คำสั่งสำหรับการแชร์ไฟล์เป็นวีดีโอ (กรณีที่กำหนดเป็นไฟล์ใดๆ ไม่ต้องใช้)

        8. ฟังก์ชันสำหรับก๊อปไฟล์จากโฟลเดอร์ assets ไป External Storage หรือ SD

        9. สร้างโฟลเดอร์ tmp สำหรับเก็บไฟล์ แล้วกำหนดที่อยู่ที่ต้องการเก็บไฟล์

        10. คำสั่งในการก๊อปไฟล์ไปไว้ใน External Storage หรือ SD

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

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="8" />
    
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="app.akexorcist.intentsharefile.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>
    </application>

</manifest>

        จากตัวอย่างข้างต้น (ยังไม่ได้แก้ไขโค๊ดจากตัวอย่าง) จะเป็นการแชร์ไฟล์ภาพ

        โดยเจ้าของบล็อกจะกดปุ่มแชร์ แล้วเลือกแชร์ภาพไปยังแอพ Pixlr Express ภาพก็จะเก็บลง External Storage หรือ SD แล้วเปิดบน Pixlr Express ได้ทันที


        สำหรับผู้ที่หลงเข้ามาอ่านคนใดต้องการไฟล์ตัวอย่างสามารถดาวน์โหลดได้ที่ Intent Share File [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]