Catatan: Halaman ini merujuk ke class Camera, yang sudah tidak digunakan lagi. Sebaiknya gunakan CameraX atau, untuk kasus penggunaan tertentu, Camera2. CameraX dan Camera2 mendukung Android 5.0 (level API 21) dan 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 (level API 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 (level API 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 pada 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,
<?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); }