การแชร์ไฟล์

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

บทเรียนนี้แสดงวิธีสร้างการเลือกไฟล์ Activity ในแอปของคุณ ที่ตอบกลับคำขอไฟล์

รับคำขอไฟล์

ในการรับคำขอไฟล์จากแอปไคลเอ็นต์และตอบกลับด้วย URI เนื้อหา แอปของคุณควร ระบุการเลือกไฟล์ Activity แอปไคลเอ็นต์เริ่มต้นสิ่งนี้ Activity โดยโทรหา startActivityForResult() พร้อม Intent ที่ดำเนินการอยู่ ACTION_PICK เมื่อแอปไคลเอ็นต์เรียกใช้ startActivityForResult() แอปของคุณสามารถ ส่งผลลัพธ์ไปยังแอปไคลเอ็นต์ในรูปแบบ URI เนื้อหาสำหรับไฟล์ที่ผู้ใช้เลือก

หากต้องการดูวิธีใช้คำขอไฟล์ในแอปไคลเอ็นต์ ให้ดูบทเรียน การขอไฟล์ที่แชร์

สร้างกิจกรรมการเลือกไฟล์

หากต้องการตั้งค่าการเลือกไฟล์ Activity ให้เริ่มด้วยการระบุ Activity ในไฟล์ Manifest พร้อมด้วยตัวกรอง Intent ที่ตรงกับการดำเนินการ ACTION_PICK และ หมวดหมู่ CATEGORY_DEFAULT และ CATEGORY_OPENABLE เพิ่มตัวกรองประเภท MIME ด้วย สำหรับไฟล์ที่แอปแสดงไปยังแอปอื่นๆ ข้อมูลโค้ดต่อไปนี้จะแสดงวิธีระบุ ตัวกรอง Activity และ Intent ใหม่:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
        <application>
        ...
            <activity
                android:name=".FileSelectActivity"
                android:label="@File Selector" >
                <intent-filter>
                    <action
                        android:name="android.intent.action.PICK"/>
                    <category
                        android:name="android.intent.category.DEFAULT"/>
                    <category
                        android:name="android.intent.category.OPENABLE"/>
                    <data android:mimeType="text/plain"/>
                    <data android:mimeType="image/*"/>
                </intent-filter>
            </activity>

กำหนดกิจกรรมการเลือกไฟล์ในโค้ด

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

Kotlin

class MainActivity : Activity() {

    // The path to the root of this app's internal storage
    private lateinit var privateRootDir: File
    // The path to the "images" subdirectory
    private lateinit var imagesDir: File
    // Array of files in the images subdirectory
    private lateinit var imageFiles: Array<File>
    // Array of filenames corresponding to imageFiles
    private lateinit var imageFilenames: Array<String>

    // Initialize the Activity
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Set up an Intent to send back to apps that request a file
        resultIntent = Intent("com.example.myapp.ACTION_RETURN_FILE")
        // Get the files/ subdirectory of internal storage
        privateRootDir = filesDir
        // Get the files/images subdirectory;
        imagesDir = File(privateRootDir, "images")
        // Get the files in the images subdirectory
        imageFiles = imagesDir.listFiles()
        // Set the Activity's result to null to begin with
        setResult(Activity.RESULT_CANCELED, null)
        /*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */
        ...
    }
    ...
}

Java

public class MainActivity extends Activity {
    // The path to the root of this app's internal storage
    private File privateRootDir;
    // The path to the "images" subdirectory
    private File imagesDir;
    // Array of files in the images subdirectory
    File[] imageFiles;
    // Array of filenames corresponding to imageFiles
    String[] imageFilenames;
    // Initialize the Activity
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Set up an Intent to send back to apps that request a file
        resultIntent =
                new Intent("com.example.myapp.ACTION_RETURN_FILE");
        // Get the files/ subdirectory of internal storage
        privateRootDir = getFilesDir();
        // Get the files/images subdirectory;
        imagesDir = new File(privateRootDir, "images");
        // Get the files in the images subdirectory
        imageFiles = imagesDir.listFiles();
        // Set the Activity's result to null to begin with
        setResult(Activity.RESULT_CANCELED, null);
        /*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */
         ...
    }
    ...
}

ตอบสนองต่อการเลือกไฟล์

เมื่อผู้ใช้เลือกไฟล์ที่แชร์ แอปพลิเคชันของคุณจะต้องพิจารณาว่าไฟล์ใดถูกเลือก และ จากนั้นให้สร้าง URI เนื้อหาสำหรับไฟล์ เนื่องจาก Activity แสดงเมธอด รายการไฟล์ที่ใช้ได้ใน ListView เมื่อผู้ใช้คลิกที่ชื่อไฟล์ ระบบจะเรียกเมธอด onItemClick() ซึ่งคุณจะได้รับไฟล์ที่เลือก

เมื่อใช้ Intent ในการส่ง URI ของไฟล์จากแอปหนึ่งไปยังอีกแอปหนึ่ง คุณจะต้องระวังเพื่อให้ได้ URI ที่ ที่สามารถอ่านได้ ดำเนินการกับอุปกรณ์ที่ใช้ Android 6.0 (API ระดับ 23) ขึ้นไป ต้องมีแบบพิเศษ เนื่องจากมีการเปลี่ยนแปลงโมเดลสิทธิ์ใน Android เวอร์ชันนั้น โดยเฉพาะอย่างยิ่ง ของ READ_EXTERNAL_STORAGE การเป็น สิทธิ์ที่เป็นอันตราย ซึ่งแอปที่ใช้รับอาจไม่มี

โดยคำนึงถึงข้อควรพิจารณาเหล่านี้ เราขอแนะนำให้คุณหลีกเลี่ยงการใช้ Uri.fromFile() ซึ่ง ก่อให้เกิดข้อเสียหลายประการ วิธีการนี้

  • ไม่อนุญาตให้แชร์ไฟล์ระหว่างโปรไฟล์
  • แอปของคุณต้องมี WRITE_EXTERNAL_STORAGE สิทธิ์ในอุปกรณ์ที่ใช้ Android 4.4 (API ระดับ 19) หรือต่ำกว่า
  • แอปที่รับต้องมี READ_EXTERNAL_STORAGE ซึ่ง จะล้มเหลวในเป้าหมายการแชร์ที่สำคัญ เช่น Gmail ซึ่งไม่มีสิทธิ์ดังกล่าว

แทนที่จะใช้ Uri.fromFile() คุณสามารถใช้สิทธิ์ URI เพื่อให้สิทธิ์แอปอื่นๆ เข้าถึง URI ที่เฉพาะเจาะจง แม้ว่าสิทธิ์ URI จะไม่ทำงานใน URI file:// สร้างโดย Uri.fromFile() แอปเหล่านี้ ทำงานกับ URI ที่เชื่อมโยงกับผู้ให้บริการเนื้อหา FileProvider API สามารถ ช่วยสร้าง URI ดังกล่าว วิธีนี้ยังใช้กับไฟล์ที่ไม่ได้ ในพื้นที่เก็บข้อมูลภายนอก แต่ในพื้นที่เก็บข้อมูลในเครื่องของแอปที่ส่ง Intent

ใน onItemClick() รับ File สำหรับชื่อไฟล์ของไฟล์ที่เลือก แล้วส่งเป็นอาร์กิวเมนต์ไปยัง getUriForFile() รวมถึง ที่คุณระบุไว้ใน องค์ประกอบ <provider> สำหรับ FileProvider URI เนื้อหาที่ได้จะมีสิทธิ์ ซึ่งเป็นส่วนของเส้นทางที่สอดคล้องกับไฟล์ (ตามที่ระบุในข้อมูลเมตา XML) และชื่อไฟล์รวมถึง ส่วนขยาย วิธีจับคู่ไดเรกทอรีกับเส้นทาง FileProvider ส่วนที่ใช้ข้อมูลเมตา XML อธิบายอยู่ในส่วน ระบุไดเรกทอรีที่แชร์ได้

ข้อมูลโค้ดต่อไปนี้แสดงวิธีตรวจหาไฟล์ที่เลือกและรับ URI เนื้อหาสำหรับไฟล์ดังกล่าว

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            /*
             * Get a File for the selected file name.
             * Assume that the file names are in the
             * imageFilename array.
             */
            val requestFile = File(imageFilenames[position])
            /*
             * Most file-related method calls need to be in
             * try-catch blocks.
             */
            // Use the FileProvider to get a content URI
            val fileUri: Uri? = try {
                FileProvider.getUriForFile(
                        this@MainActivity,
                        "com.example.myapp.fileprovider",
                        requestFile)
            } catch (e: IllegalArgumentException) {
                Log.e("File Selector",
                        "The selected file can't be shared: $requestFile")
                null
            }
            ...
        }
        ...
    }

Java

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
            @Override
            /*
             * When a filename in the ListView is clicked, get its
             * content URI and send it to the requesting app
             */
            public void onItemClick(AdapterView<?> adapterView,
                    View view,
                    int position,
                    long rowId) {
                /*
                 * Get a File for the selected file name.
                 * Assume that the file names are in the
                 * imageFilename array.
                 */
                File requestFile = new File(imageFilename[position]);
                /*
                 * Most file-related method calls need to be in
                 * try-catch blocks.
                 */
                // Use the FileProvider to get a content URI
                try {
                    fileUri = FileProvider.getUriForFile(
                            MainActivity.this,
                            "com.example.myapp.fileprovider",
                            requestFile);
                } catch (IllegalArgumentException e) {
                    Log.e("File Selector",
                          "The selected file can't be shared: " + requestFile.toString());
                }
                ...
            }
        });
        ...
    }

โปรดทราบว่าคุณจะสร้าง URI เนื้อหาได้เฉพาะสำหรับไฟล์ที่อยู่ในไดเรกทอรีเท่านั้น ที่คุณระบุไว้ในไฟล์ข้อมูลเมตาที่มีองค์ประกอบ <paths> เป็น ตามที่อธิบายไว้ในส่วนระบุไดเรกทอรีที่แชร์ได้ หากคุณโทรหา getUriForFile() เป็นเวลา Fileในเส้นทางที่คุณยังไม่ได้ระบุ คุณจะได้รับ IllegalArgumentException

ให้สิทธิ์สำหรับไฟล์

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

ข้อมูลโค้ดต่อไปนี้แสดงวิธีตั้งค่าสิทธิ์อ่านไฟล์

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            ...
            if (fileUri != null) {
                // Grant temporary read permission to the content URI
                resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                ...
            }
            ...
        }
        ...
    }

Java

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks in the ListView
        fileListView.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView,
                    View view,
                    int position,
                    long rowId) {
                ...
                if (fileUri != null) {
                    // Grant temporary read permission to the content URI
                    resultIntent.addFlags(
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
                ...
             }
             ...
        });
    ...
    }

ข้อควรระวัง: การโทรไปยัง setFlags() เป็นเพียงวิธีเดียว วิธีให้สิทธิ์เข้าถึงไฟล์อย่างปลอดภัยโดยใช้สิทธิ์การเข้าถึงชั่วคราว หลีกเลี่ยงการโทร เมธอด Context.grantUriPermission() สำหรับ URI เนื้อหาของไฟล์ เนื่องจากวิธีการนี้จะให้สิทธิ์เข้าถึงซึ่งคุณสามารถเพิกถอนได้โดย กำลังโทรหา Context.revokeUriPermission()

อย่าใช้ Uri.fromFile() บังคับให้รับแอป ที่จะได้รับสิทธิ์ READ_EXTERNAL_STORAGE จะใช้งานไม่ได้เลยหากคุณพยายามแชร์กับผู้ใช้ทั้งหมดและเป็นเวอร์ชัน ของ Android ที่ต่ำกว่า 4.4 (API ระดับ 19) จะต้องมี แอปที่มีWRITE_EXTERNAL_STORAGE และเป้าหมายการแชร์ที่สำคัญจริงๆ เช่น แอป Gmail ไม่มี READ_EXTERNAL_STORAGE ซึ่งทําให้ ไม่สำเร็จ แต่คุณสามารถใช้สิทธิ์ URI เพื่อให้สิทธิ์เข้าถึง URI ที่เจาะจงแก่แอปอื่นๆ แทนได้ แม้ว่าสิทธิ์ URI จะไม่ทำงานกับ URI แบบ file:// ดังที่ Uri.fromFile() ทำได้ ทำงานกับ Uris ที่เกี่ยวข้องกับผู้ให้บริการเนื้อหา แทนที่จะใช้หัวข้อของคุณเองเพียงเพื่อเรื่องนี้ คุณมีสิทธิ์และควรใช้ FileProvider ตามที่อธิบายไว้ในการแชร์ไฟล์

แชร์ไฟล์กับแอปที่ส่งคำขอ

หากต้องการแชร์ไฟล์กับแอปที่ขอไฟล์ ให้ส่ง Intent ที่มี URI ของเนื้อหาและสิทธิ์ในการ setResult() เมื่อ Activity ที่คุณเพิ่งกำหนดเสร็จแล้ว ระบบจะส่ง Intent ที่มี URI เนื้อหาไปยังแอปไคลเอ็นต์ ข้อมูลโค้ดต่อไปนี้จะแสดงวิธีดำเนินการ

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            ...
            if (fileUri != null) {
                ...
                // Put the Uri and MIME type in the result Intent
                resultIntent.setDataAndType(fileUri, contentResolver.getType(fileUri))
                // Set the result
                setResult(Activity.RESULT_OK, resultIntent)
            } else {
                resultIntent.setDataAndType(null, "")
                setResult(RESULT_CANCELED, resultIntent)
            }
        }
    }

Java

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView,
                    View view,
                    int position,
                    long rowId) {
                ...
                if (fileUri != null) {
                    ...
                    // Put the Uri and MIME type in the result Intent
                    resultIntent.setDataAndType(
                            fileUri,
                            getContentResolver().getType(fileUri));
                    // Set the result
                    MainActivity.this.setResult(Activity.RESULT_OK,
                            resultIntent);
                    } else {
                        resultIntent.setDataAndType(null, "");
                        MainActivity.this.setResult(RESULT_CANCELED,
                                resultIntent);
                    }
                }
        });

มอบวิธีให้ผู้ใช้กลับไปยังแอปไคลเอ็นต์ได้ทันทีเมื่อเลือกไฟล์แล้ว วิธีหนึ่งที่สามารถทำได้คือระบุเครื่องหมายถูกหรือปุ่มเสร็จสิ้น เชื่อมโยงเมธอดกับ ปุ่มโดยใช้ android:onClick ในเมธอด ให้เรียก finish() เช่น

Kotlin

    fun onDoneClick(v: View) {
        // Associate a method with the Done button
        finish()
    }

Java

    public void onDoneClick(View v) {
        // Associate a method with the Done button
        finish();
    }

ดูข้อมูลที่เกี่ยวข้องเพิ่มเติมได้ที่