Mengambil foto

Catatan: Halaman ini merujuk ke class Camera, yang sudah tidak digunakan lagi. Sebaiknya gunakan CameraX atau, untuk kasus penggunaan tertentu, gunakan Camera2. CameraX dan Camera2 mendukung Android 5.0 (API level 21) dan versi yang lebih baru.

Tutorial 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 tutorial 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 Anda:

<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, setel 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.

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 yang ditampilkan yang dikirim ke onActivityResult() sebagai Bitmap kecil di bagian ekstra, pada "data" utama. 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: Gambar thumbnail dari "data" ini mungkin hanya cocok untuk sebuah ikon. Menangani gambar berukuran penuh memerlukan usaha sedikit lebih banyak.

Menyimpan foto ukuran penuh

Aplikasi Kamera Android menyimpan foto ukuran penuh jika Anda memberinya file untuk disimpan ke dalamnya. Anda harus memberi 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. Direktori yang disediakan oleh metode ini digunakan bersama oleh semua aplikasi. Di Android 9 (level API 28) dan versi yang lebih rendah, izin baca dan tulis ke direktori ini masing-masing memerlukan izin READ_EXTERNAL_STORAGE dan WRITE_EXTERNAL_STORAGE:

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

Di Android 10 (API level 29) dan yang lebih tinggi, direktori yang tepat untuk berbagi foto adalah tabel MediaStore.Images. Anda tidak perlu mendeklarasikan izin penyimpanan apa pun, selama aplikasi hanya perlu mengakses foto yang diambil pengguna menggunakan aplikasi Anda.

Namun, jika ingin foto tetap bersifat pribadi hanya untuk aplikasi Anda, Anda dapat menggunakan direktori yang disediakan oleh Context.getExternalFilesDir(). Pada Android 4.3 dan versi yang lebih rendah, akses tulis ke direktori ini juga memerlukan izin WRITE_EXTERNAL_STORAGE. Mulai 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 rendah dengan menambahkan atribut maxSdkVersion:

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

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

Setelah menentukan direktori untuk file tersebut, Anda harus membuat nama file yang tahan bentrok. Anda juga dapat menyimpan jalur dalam variabel anggota untuk digunakan nanti. Berikut adalah contoh solusi dalam metode yang menampilkan nama file unik untuk sebuah foto baru yang menggunakan stempel tanggal-waktu. (Contoh ini mengasumsikan bahwa Anda memanggil metode dari dalam Context).

Kotlin

lateinit 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

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

Java

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

Catatan: Kami menggunakan getUriForFile(Context, String, File) yang menampilkan URI content://. Untuk aplikasi lebih baru yang menargetkan Android 7.0 (API level 24) dan versi yang lebih tinggi, meneruskan URI file:// yang 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="androidx.core.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-files-path name="my_images" path="Pictures" />
</paths>

Komponen jalur ini sesuai dengan jalur yang ditampilkan oleh getExternalFilesDir() saat dipanggil dengan Environment.DIRECTORY_PICTURES. Pastikan Anda 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

        BitmapFactory.decodeFile(currentPhotoPath, bmOptions)

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

        // Determine how much to scale down the image
        val scaleFactor: Int = Math.max(1, 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;

    BitmapFactory.decodeFile(currentPhotoPath, bmOptions);

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

    // Determine how much to scale down the image
    int scaleFactor = Math.max(1, 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);
}