Mengambil foto

Pelajaran ini menjelaskan cara mengambil foto dengan mendelegasikan tugas ke aplikasi kamera lain di perangkat. (Jika Anda ingin membuat fungsionalitas kamera sendiri, lihat Mengontrol Kamera.)

Misalkan Anda mengimplementasikan layanan cuaca crowd-sourced yang membuat peta cuaca global dengan memadukan foto-foto langit yang diambil oleh perangkat yang menjalankan aplikasi klien. Mengintegrasikan foto hanyalah sebagian kecil dari tugas aplikasi Anda. Anda ingin mengambil foto tanpa repot, bukan menemukan kembali teknologi kamera. Untungnya, sebagian besar perangkat Android sudah disertai dengan minimal satu aplikasi kamera. Dalam pelajaran ini, Anda akan mempelajari bagaimana aplikasi tersebut mengambil foto untuk Anda.

Meminta fitur kamera

Jika mengambil foto merupakan fungsi pokok aplikasi Anda, maka batasi visibilitasnya di Google Play ke perangkat yang memiliki kamera. Untuk menyatakan bahwa aplikasi Anda bergantung pada ketersediaan kamera, tempatkan tag <uses-feature> dalam file manifes:

    <manifest ... >
        <uses-feature android:name="android.hardware.camera"
                      android:required="true" />
        ...
    </manifest>
    

Jika aplikasi Anda menggunakan kamera, tetapi tidak mengharuskan ketersediaan kamera agar dapat berfungsi, tetapkan android:required ke false. Dengan demikian, Google Play akan memungkinkan perangkat tanpa kamera untuk mendownload aplikasi Anda. Selanjutnya, Anda bertanggung jawab untuk memeriksa ketersediaan kamera saat aplikasi diluncurkan dengan memanggil hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY). Jika kamera tidak tersedia, Anda harus menonaktifkan fitur kamera.

Mengambil foto dengan aplikasi kamera

Cara Android mendelegasikan tindakan ke aplikasi lain adalah dengan memanggil Intent yang menjelaskan tindakan yang ingin Anda lakukan. Proses ini terdiri dari tiga bagian: Intent itu sendiri, panggilan untuk memulai Activity eksternal, dan kode untuk menangani data gambar saat fokus beralih kembali ke aktivitas Anda.

Berikut adalah fungsi yang memanggil intent untuk mengambil foto.

Kotlin

    val REQUEST_IMAGE_CAPTURE = 1

    private fun dispatchTakePictureIntent() {
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
            takePictureIntent.resolveActivity(packageManager)?.also {
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
            }
        }
    }
    

Java

    static final int REQUEST_IMAGE_CAPTURE = 1;

    private void dispatchTakePictureIntent() {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
    }
    

Perhatikan bahwa metode startActivityForResult() dilindungi oleh kondisi yang memanggil resolveActivity(), yang menampilkan komponen aktivitas pertama yang dapat menangani intent. Pemeriksaan ini merupakan langkah penting karena jika Anda memanggil startActivityForResult() menggunakan intent yang tidak dapat ditangani aplikasi mana pun, aplikasi Anda akan mengalami error. Jadi, selama hasilnya bukan null, intent tersebut aman digunakan.

Mendapatkan thumbnail

Jika pekerjaan mudah seperti mengambil foto bukan merupakan tugas pokok aplikasi Anda, maka Anda mungkin ingin mengambil kembali gambar dari aplikasi kamera dan melakukan sesuatu dengannya.

Aplikasi Kamera Android mengenkode foto itu dalam Intent kembalian yang dikirim ke onActivityResult() sebagai Bitmap kecil di bagian ekstra, pada kunci "data". Kode berikut mengambil gambar ini dan menampilkannya di ImageView.

Kotlin

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
            val imageBitmap = data.extras.get("data") as Bitmap
            imageView.setImageBitmap(imageBitmap)
        }
    }
    

Java

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
            Bundle extras = data.getExtras();
            Bitmap imageBitmap = (Bitmap) extras.get("data");
            imageView.setImageBitmap(imageBitmap);
        }
    }
    

Catatan: Thumbnail dari "data" ini mungkin hanya cocok untuk ikon. Menangani gambar berukuran penuh memerlukan usaha sedikit lebih banyak.

Menyimpan foto ukuran penuh

Aplikasi Kamera Android menyimpan foto ukuran penuh jika Anda memberikannya file untuk disimpan ke dalamnya. Anda harus memberikan nama file yang sepenuhnya memenuhi syarat di mana aplikasi kamera akan menyimpan foto itu.

Secara umum, semua foto yang diambil pengguna dengan kamera perangkat akan disimpan di penyimpanan eksternal publik pada perangkat sehingga dapat diakses oleh semua aplikasi. Direktori yang sesuai untuk foto bersama disediakan oleh getExternalStoragePublicDirectory(), dengan argumen DIRECTORY_PICTURES. Karena direktori yang disediakan oleh metode ini digunakan bersama dengan semua aplikasi, akses baca dan tulis ke direktori tersebut memerlukan, secara berturut-turut, izin READ_EXTERNAL_STORAGE dan WRITE_EXTERNAL_STORAGE. Izin tulis secara implisit memperbolehkan akses baca, jadi jika Anda perlu menulis ke penyimpanan eksternal, Anda hanya perlu meminta satu izin:

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

Namun, jika ingin foto tetap bersifat pribadi hanya untuk aplikasi Anda, Anda dapat menggunakan direktori yang disediakan oleh getExternalFilesDir(). Pada Android 4.3 dan yang lebih lama, akses tulis ke direktori ini juga memerlukan izin WRITE_EXTERNAL_STORAGE. Mulai dari Android 4.4, izin ini tidak lagi diperlukan karena direktori tidak dapat diakses oleh aplikasi lain, sehingga Anda dapat mendeklarasikan bahwa izin hanya perlu diminta pada versi Android yang lebih lama dengan menambahkan atribut maxSdkVersion:

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

Catatan: File yang Anda simpan di direktori yang disediakan oleh getExternalFilesDir() atau getFilesDir() akan dihapus saat pengguna meng-uninstal aplikasi Anda.

Setelah memutuskan direktori untuk file, Anda harus membuat nama file yang tahan bentrok. Anda juga dapat menyimpan jalur dalam variabel anggota untuk digunakan nanti. Berikut ini contoh solusi dalam metode yang menampilkan nama file unik untuk sebuah foto baru yang menggunakan stempel tanggal-waktu:

Kotlin

    var currentPhotoPath: String

    @Throws(IOException::class)
    private fun createImageFile(): File {
        // Create an image file name
        val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
        val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        return File.createTempFile(
                "JPEG_${timeStamp}_", /* prefix */
                ".jpg", /* suffix */
                storageDir /* directory */
        ).apply {
            // Save a file: path for use with ACTION_VIEW intents
            currentPhotoPath = absolutePath
        }
    }
    

Java

    String currentPhotoPath;

    private File createImageFile() throws IOException {
        // Create an image file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        File image = File.createTempFile(
            imageFileName,  /* prefix */
            ".jpg",         /* suffix */
            storageDir      /* directory */
        );

        // Save a file: path for use with ACTION_VIEW intents
        currentPhotoPath = image.getAbsolutePath();
        return image;
    }
    

Dengan ketersediaan metode ini untuk membuat file bagi foto, sekarang Anda dapat membuat dan memanggil Intent seperti ini:

Kotlin

    val REQUEST_TAKE_PHOTO = 1

    private fun dispatchTakePictureIntent() {
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
            // Ensure that there's a camera activity to handle the intent
            takePictureIntent.resolveActivity(packageManager)?.also {
                // Create the File where the photo should go
                val photoFile: File? = try {
                    createImageFile()
                } catch (ex: IOException) {
                    // Error occurred while creating the File
                    ...
                    null
                }
                // Continue only if the File was successfully created
                photoFile?.also {
                    val photoURI: Uri = FileProvider.getUriForFile(
                            this,
                            "com.example.android.fileprovider",
                            it
                    )
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                    startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO)
                }
            }
        }
    }
    

Java

    static final int REQUEST_TAKE_PHOTO = 1;

    private void dispatchTakePictureIntent() {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // Ensure that there's a camera activity to handle the intent
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            // Create the File where the photo should go
            File photoFile = null;
            try {
                photoFile = createImageFile();
            } catch (IOException ex) {
                // Error occurred while creating the File
                ...
            }
            // Continue only if the File was successfully created
            if (photoFile != null) {
                Uri photoURI = FileProvider.getUriForFile(this,
                                                      "com.example.android.fileprovider",
                                                      photoFile);
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
            }
        }
    }
    

Catatan: Kami menggunakan getUriForFile(Context, String, File) yang menampilkan URI content://. Untuk aplikasi lebih baru yang menargetkan Android 7.0 (API level 24) dan yang lebih tinggi, meneruskan URI file:// melampaui batas paket akan menyebabkan FileUriExposedException. Oleh karena itu, kami menyajikan cara yang lebih umum untuk menyimpan foto menggunakan FileProvider.

Sekarang, Anda perlu mengonfigurasi FileProvider. Dalam manifes aplikasi, tambahkan penyedia ke aplikasi Anda:
    <application>
       ...
       <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.android.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"></meta-data>
        </provider>
        ...
    </application>
    
Pastikan string otoritas cocok dengan argumen kedua ke getUriForFile(Context, String, File). Di bagian metadata definisi penyedia, Anda dapat melihat bahwa penyedia mengharapkan jalur yang memenuhi syarat untuk dikonfigurasi dalam file resource khusus, res/xml/file_paths.xml. Berikut adalah konten yang diperlukan untuk contoh khusus ini:
    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <external-path name="my_images" path="Android/data/com.example.package.name/files/Pictures" />
    </paths>
    
Komponen jalur ini bersesuaian dengan jalur yang ditampilkan oleh getExternalFilesDir() saat dipanggil dengan Environment.DIRECTORY_PICTURES. Pastikan untuk mengganti com.example.package.name dengan nama paket sebenarnya untuk aplikasi Anda. Selain itu, baca dokumentasi FileProvider untuk mendapatkan penjelasan lengkap tentang penentu jalur selain external-path yang dapat Anda gunakan.

Menambahkan foto ke galeri

Saat membuat foto melalui intent, Anda harus mengetahui lokasi gambar karena Anda sudah menyatakan di mana akan menyimpannya. Bagi orang lain, mungkin cara termudah untuk menemukan foto Anda adalah dengan mengaksesnya dari Penyedia Media pada sistem.

Catatan: Jika Anda menyimpan foto ke direktori yang disediakan oleh getExternalFilesDir(), pemindai media tidak dapat mengakses file karena file tersebut bersifat pribadi untuk aplikasi Anda.

Contoh metode berikut menunjukkan cara memanggil pemindai media sistem agar menambahkan foto Anda ke database Penyedia Media, sehingga foto akan tersedia di aplikasi Galeri Android dan aplikasi lainnya.

Kotlin

    private fun galleryAddPic() {
        Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
            val f = File(currentPhotoPath)
            mediaScanIntent.data = Uri.fromFile(f)
            sendBroadcast(mediaScanIntent)
        }
    }
    

Java

    private void galleryAddPic() {
        Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        File f = new File(currentPhotoPath);
        Uri contentUri = Uri.fromFile(f);
        mediaScanIntent.setData(contentUri);
        this.sendBroadcast(mediaScanIntent);
    }
    

Mendekode foto yang diskalakan

Mengelola banyak foto ukuran penuh bukanlah hal yang mudah dengan memori yang terbatas. Jika aplikasi Anda kehabisan memori setelah menampilkan beberapa gambar saja, Anda dapat mengurangi penggunaan heap dinamis secara dramatis dengan memperluas JPEG ke array memori yang telah diskalakan agar sesuai dengan ukuran tampilan tujuan. Contoh metode berikut menunjukkan teknik ini.

Kotlin

    private fun setPic() {
        // Get the dimensions of the View
        val targetW: Int = imageView.width
        val targetH: Int = imageView.height

        val bmOptions = BitmapFactory.Options().apply {
            // Get the dimensions of the bitmap
            inJustDecodeBounds = true

            val photoW: Int = outWidth
            val photoH: Int = outHeight

            // Determine how much to scale down the image
            val scaleFactor: Int = Math.min(photoW / targetW, photoH / targetH)

            // Decode the image file into a Bitmap sized to fill the View
            inJustDecodeBounds = false
            inSampleSize = scaleFactor
            inPurgeable = true
        }
        BitmapFactory.decodeFile(currentPhotoPath, bmOptions)?.also { bitmap ->
            imageView.setImageBitmap(bitmap)
        }
    }
    

Java

    private void setPic() {
        // Get the dimensions of the View
        int targetW = imageView.getWidth();
        int targetH = imageView.getHeight();

        // Get the dimensions of the bitmap
        BitmapFactory.Options bmOptions = new BitmapFactory.Options();
        bmOptions.inJustDecodeBounds = true;

        int photoW = bmOptions.outWidth;
        int photoH = bmOptions.outHeight;

        // Determine how much to scale down the image
        int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

        // Decode the image file into a Bitmap sized to fill the View
        bmOptions.inJustDecodeBounds = false;
        bmOptions.inSampleSize = scaleFactor;
        bmOptions.inPurgeable = true;

        Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath, bmOptions);
        imageView.setImageBitmap(bitmap);
    }