Membagikan file

Setelah menyiapkan aplikasi untuk berbagi file menggunakan URI konten, Anda dapat merespons permintaan aplikasi lain untuk file tersebut. Salah satu cara untuk merespons permintaan ini adalah dengan menyediakan antarmuka pemilihan file dari aplikasi server yang dapat dipanggil oleh aplikasi lain. Pendekatan ini memungkinkan aplikasi klien untuk membiarkan pengguna memilih file dari aplikasi server kemudian menerima URI konten file yang dipilih.

Tutorial ini menunjukkan cara membuat Activity pemilihan file dalam aplikasi Anda yang merespons permintaan file.

Menerima permintaan file

Untuk menerima permintaan file dari aplikasi klien dan merespons dengan URI konten, aplikasi Anda harus menyediakan Activity pemilihan file. Aplikasi klien memulai Activity ini dengan memanggil startActivityForResult() beserta Intent yang berisi tindakan ACTION_PICK. Saat aplikasi klien memanggil startActivityForResult(), aplikasi Anda dapat menampilkan hasil ke aplikasi klien, dalam bentuk URI konten untuk file yang dipilih pengguna.

Untuk mempelajari cara mengimplementasikan permintaan file di aplikasi klien, lihat tutorial Meminta file bersama.

Membuat Aktivitas pemilihan file

Untuk menyiapkan Activity pemilihan file, mulai dengan menentukan Activity dalam manifes Anda, bersama dengan filter intent yang cocok dengan tindakan ACTION_PICK dan kategori CATEGORY_DEFAULT serta CATEGORY_OPENABLE. Tambahkan juga filter jenis MIME untuk file yang ditayangkan aplikasi Anda ke aplikasi lain. Cuplikan berikut menunjukkan cara menentukan Activity dan filter intent baru:

    <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>

Menentukan Activity pemilihan file dalam kode

Selanjutnya, tentukan subclass Activity yang menampilkan file yang tersedia dari direktori files/images/ aplikasi Anda dalam penyimpanan internal dan memungkinkan pengguna memilih file yang diinginkan. Cuplikan berikut menunjukkan cara menentukan Activity ini dan merespons pilihan pengguna:

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
             */
             ...
        }
        ...
    }
    

Merespons pemilihan file

Setelah pengguna memilih file bersama, aplikasi Anda harus menentukan file yang dipilih kemudian membuat URI konten untuk file tersebut. Karena Activity menampilkan daftar file yang tersedia dalam ListView, saat pengguna mengklik nama file, sistem akan memanggil metode onItemClick(), tempat Anda bisa mendapatkan file yang dipilih.

Saat menggunakan intent untuk mengirim URI file dari satu aplikasi ke aplikasi lainnya, Anda harus berhati-hati untuk mendapatkan URI yang dapat dibaca oleh aplikasi lain. Diperlukan perhatian khusus untuk melakukan hal tersebut pada perangkat yang menjalankan Android 6.0 (API level 23) dan yang lebih baru karena perubahan pada model izin dalam versi Android tersebut, terutama READ_EXTERNAL_STORAGE dapat menjadi izin berbahaya, yang mungkin tidak dimiliki oleh aplikasi penerima.

Dengan mempertimbangkan hal ini, sebaiknya hindari penggunaan Uri.fromFile(), yang memiliki beberapa kekurangan. Metode ini:

  • Tidak mengizinkan berbagi file di seluruh profil.
  • Mewajibkan aplikasi memiliki izin WRITE_EXTERNAL_STORAGE di perangkat yang menjalankan Android 4.4 (API level 19) atau yang lebih rendah.
  • Mewajibkan aplikasi penerima memiliki izin READ_EXTERNAL_STORAGE, yang akan gagal pada target berbagi yang penting, seperti Gmail, yang tidak memiliki izin tersebut.

Sebagai ganti menggunakan Uri.fromFile(), Anda dapat menggunakan izin URI untuk memberi aplikasi lain akses ke URI tertentu. Meskipun izin URI tidak berfungsi pada URI file:// yang dihasilkan oleh Uri.fromFile(), izin tersebut dapat berfungsi pada URI yang berkaitan dengan Penyedia Konten. API FileProvider dapat membantu Anda membuat URI tersebut. Pendekatan ini juga berfungsi dengan file yang tidak berada di penyimpanan eksternal, tetapi berada di penyimpanan lokal aplikasi yang mengirimkan intent.

Di onItemClick(), dapatkan objek File untuk nama file dari file yang dipilih dan teruskan sebagai argumen ke getUriForFile(), beserta otoritas yang Anda tentukan dalam elemen <provider> untuk FileProvider. URI konten yang dihasilkan berisi otoritas, segmen jalur yang sesuai dengan direktori file (seperti yang ditentukan dalam meta-data XML), dan nama file termasuk ekstensinya. Cara FileProvider memetakan direktori ke segmen jalur berdasarkan meta-data XML dijelaskan di bagian Menentukan direktori yang dapat dibagikan.

Cuplikan berikut menunjukkan cara mendeteksi file terpilih dan mendapatkan URI konten untuk file tersebut:

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());
                    }
                    ...
                }
            });
            ...
        }
    

Ingat bahwa Anda hanya dapat membuat URI konten untuk file yang berada di direktori yang telah Anda tentukan dalam file meta-data yang berisi elemen <paths>, seperti yang dijelaskan di bagian Menentukan direktori yang dapat dibagikan. Jika memanggil getUriForFile() untuk File di jalur yang belum ditentukan, Anda akan menerima IllegalArgumentException.

Memberikan izin untuk file

Setelah mendapatkan URI konten untuk file yang ingin dibagikan dengan aplikasi lain, Anda harus mengizinkan aplikasi klien untuk mengakses file tersebut. Untuk mengizinkan akses, beri izin kepada aplikasi klien dengan menambahkan URI konten ke Intent, lalu menetapkan flag izin pada Intent. Izin yang Anda berikan bersifat sementara dan habis masa berlakunya secara otomatis saat stack tugas aplikasi penerima selesai.

Cuplikan kode berikut menunjukkan cara menyetel izin baca untuk file:

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);
                    }
                    ...
                 }
                 ...
            });
        ...
        }
    

Perhatian: Memanggil setFlags() adalah satu-satunya cara untuk memberikan akses ke file Anda dengan aman menggunakan izin akses sementara. Jangan memanggil metode Context.grantUriPermission() untuk URI konten file, karena metode ini memberikan akses yang hanya dapat dicabut dengan memanggil Context.revokeUriPermission().

Jangan gunakan Uri.fromFile(). Hal ini memaksa aplikasi penerima untuk memiliki izin READ_EXTERNAL_STORAGE, dan tidak akan berfungsi sama sekali jika Anda mencoba berbagi dengan seluruh pengguna, dan di versi Android yang lebih rendah dari 4.4 (API level 19), hal tersebut akan mewajibkan aplikasi Anda untuk memiliki WRITE_EXTERNAL_STORAGE. Selain itu, target berbagi yang sangat penting, seperti aplikasi Gmail, tidak memiliki READ_EXTERNAL_STORAGE, yang menyebabkan kegagalan panggilan ini. Sebagai gantinya, Anda dapat menggunakan izin URI untuk memberi aplikasi lain akses ke URI tertentu. Meskipun izin URI tidak berfungsi di URI file:// yang dihasilkan oleh Uri.fromFile(), izin tersebut berfungsi di URI yang berkaitan dengan Penyedia Konten. Daripada mengimplementasikannya sendiri, Anda dapat dan sebaiknya menggunakan FileProvider seperti yang dijelaskan dalam Berbagi file.

Berbagi file dengan aplikasi yang meminta

Untuk berbagi file dengan aplikasi yang memintanya, teruskan Intent yang berisi URI konten dan izin ke setResult(). Saat Activity yang baru saja Anda tetapkan telah selesai, sistem akan mengirim Intent berisi URI konten ke aplikasi klien. Cuplikan kode berikut menunjukkan Anda cara melakukan hal ini:

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);
                        }
                    }
            });
    

Sediakan cara agar pengguna dapat langsung kembali ke aplikasi klien setelah memilih file. Salah satu cara untuk melakukan hal ini adalah dengan memberi tanda centang atau tombol Selesai. Kaitkan metode dengan tombol menggunakan atribut android:onClick tombol. Dalam metode tersebut, panggil finish(). Contoh:

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();
        }
    

Untuk informasi terkait lainnya, lihat: