Fotoğraf çekme

Not: Bu sayfada, desteği sonlandırılan Camera sınıfı ele alınmaktadır. CameraX veya belirli kullanım alanları için Camera2 kullanmanızı öneririz. Hem CameraX hem de Camera2, Android 5.0 (API düzeyi 21) ve sonraki sürümleri destekler.

Bu derste, işi cihazdaki başka bir kamera uygulamasına devrederek fotoğraf çekmeyi öğreneceksiniz. (Kendi kamera işlevinizi oluşturmak istiyorsanız Kamerayı kontrol etme başlıklı makaleyi inceleyin.)

Müşteri uygulamanızı çalıştıran cihazlar tarafından çekilen gökyüzü fotoğraflarını birleştirerek dünya çapında bir hava durumu haritası oluşturan kitle kaynaklı bir hava durumu hizmeti uyguladığınızı varsayalım. Fotoğrafları entegre etmek, uygulamanızın yalnızca küçük bir parçasıdır. Kamerayı yeniden icat etmek yerine, minimum düzeyde çabayla fotoğraf çekmek istiyorsunuz. Neyse ki Android işletim sistemli çoğu cihazda en az bir kamera uygulaması yüklü Bu derste, telefonun sizin için nasıl fotoğraf çekeceğini öğreneceksiniz.

Kamera özelliğini isteme

Uygulamanızın temel işlevlerinden biri fotoğraf çekmekse Google Play'deki görünürlüğünü kamerası olan cihazlarla kısıtlayın. Uygulamanızın kameraya ihtiyaç duyduğunu belirtmek için manifest dosyanıza <uses-feature> etiketi ekleyin:

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

Uygulamanız kamera kullanıyorsa ancak çalışması için kameraya ihtiyaç duymuyorsa android:required yerine false değerini ayarlayın. Bu sayede Google Play, kamerası olmayan cihazların uygulamanızı indirmesine izin verir. Ardından, hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) işlevini çağırarak çalışma zamanında kameranın kullanılabilirliğini kontrol etmek sizin sorumluluğunuzdadır. Kamera yoksa kamera özelliklerinizi devre dışı bırakmanız gerekir.

Küçük resmi alma

Uygulamanızın amacı yalnızca fotoğraf çekmek değilse muhtemelen kamera uygulamasından resmi alıp onunla bir şeyler yapmak istersiniz.

Android Kamera uygulaması, onActivityResult()'a gönderilen Intent döndürme mesajında, "data" anahtarının altındaki eklerde küçük bir Bitmap olarak kodlar. Aşağıdaki kod bu resmi alır ve bir ImageView içinde gösterir.

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

Not: "data" adresindeki bu küçük resim, simge olarak kullanılabilir ancak başka bir amaç için uygun değildir. Tam boyutlu bir resimle çalışmak biraz daha fazla iş gerektirir.

Tam boyutlu fotoğrafı kaydetme

Android Kamera uygulaması, kaydedilecek bir dosya belirtirseniz tam boyutlu fotoğraf kaydeder. Kamera uygulamasının fotoğrafı kaydetmesi gereken tam nitelikli bir dosya adı sağlamanız gerekir.

Genellikle kullanıcının cihaz kamerasıyla çektiği fotoğraflar, tüm uygulamalar tarafından erişilebilmesi için cihazdaki herkese açık harici depolama alanına kaydedilmelidir. Paylaşılan fotoğraflar için doğru dizin, getExternalStoragePublicDirectory() tarafından DIRECTORY_PICTURES bağımsız değişkeniyle sağlanır. Bu yöntemle sağlanan dizin tüm uygulamalar arasında paylaşılır. Android 9 (API seviyesi 28) ve önceki sürümlerde bu dizinde okuma ve yazma işlemleri için sırasıyla READ_EXTERNAL_STORAGE ve WRITE_EXTERNAL_STORAGE izinleri gerekir:

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

Android 10 (API düzeyi 29) ve sonraki sürümlerde, fotoğraf paylaşmak için uygun dizin MediaStore.Images tablosudur. Uygulamanızın yalnızca kullanıcının uygulamanızı kullanarak çektiği fotoğraflara erişmesi gerektiği sürece herhangi bir depolama izni beyan etmeniz gerekmez.

Ancak fotoğrafların yalnızca uygulamanız için gizli kalmasını istiyorsanız bunun yerine Context.getExternalFilesDir() tarafından sağlanan dizini kullanabilirsiniz. Android 4.3 ve önceki sürümlerde bu dizine yazmak için WRITE_EXTERNAL_STORAGE izni de gerekir. Android 4.4'ten itibaren dizin diğer uygulamalar tarafından erişilemediğinden bu izin artık gerekli değildir. Bu nedenle, maxSdkVersion özelliğini ekleyerek iznin yalnızca Android'in eski sürümlerinde istenmesi gerektiğini belirtebilirsiniz:

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

Not: getExternalFilesDir() veya getFilesDir() tarafından sağlanan dizinlere kaydettiğiniz dosyalar, kullanıcı uygulamanızı kaldırdığında silinir.

Dosyanın dizine karar verdikten sonra çakışmalara karşı dayanıklı bir dosya adı oluşturmanız gerekir. Ayrıca, yolu daha sonra kullanmak üzere bir üye değişkenine kaydedebilirsiniz. Burada, tarih ve saat damgası kullanarak yeni bir fotoğraf için benzersiz bir dosya adı döndüren bir yöntemde örnek bir çözüm verilmiştir. (Bu örnekte, yöntemi bir Context içinden çağırdığınız varsayılmaktadır.)

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

Fotoğraf için dosya oluşturmak üzere bu yöntemi kullanabilirsiniz. Artık Intent öğesini aşağıdaki gibi oluşturup çağırabilirsiniz:

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

Not: content:// URI döndüren getUriForFile(Context, String, File) sınıfını kullanıyoruz. Android 7.0 (API düzeyi 24) ve sonraki sürümleri hedefleyen daha yeni uygulamalarda, bir paket sınırında file:// URI'sinin iletilmesi FileUriExposedException'ye neden olur. Bu nedenle, artık FileProvider kullanarak resimleri daha genel bir şekilde depolama yöntemi sunuyoruz.

Artık FileProvider öğesini yapılandırmanız gerekiyor. Uygulamanızın manifest dosyasına bir sağlayıcı ekleyin:

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

Yetkililer dizesinin, getUriForFile(Context, String, File) için ikinci bağımsız değişkenle eşleştiğinden emin olun. Sağlayıcı tanımının meta veri bölümünde, uygun yolların res/xml/file_paths.xml adlı özel bir kaynak dosyasında yapılandırılmasını beklediğini görebilirsiniz. Aşağıda bu örnek için gereken içerik verilmiştir:

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

Yol bileşeni, Environment.DIRECTORY_PICTURES ile çağrıldığında getExternalFilesDir() tarafından döndürülen yola karşılık gelir. com.example.package.name yerine uygulamanızın gerçek paket adını girdiğinizden emin olun. Ayrıca, external-path dışında kullanabileceğiniz yol belirteçlerinin kapsamlı bir açıklaması için FileProvider dokümanlarına göz atın.

Fotoğrafı bir galeriye ekleme

Bir intent aracılığıyla fotoğraf oluşturduğunuzda, resminizin nereye kaydedileceğini belirttiğiniz için resminizin nerede olduğunu bilmeniz gerekir. Diğer kullanıcılar için fotoğrafınızı erişilebilir hale getirmenin en kolay yolu, sistemi kullanan medya sağlayıcıdan erişilebilir hale getirmektir.

Not: Fotoğrafınızı getExternalFilesDir() tarafından sağlanan dizin içine kaydettiyseniz bu dosyalar uygulamanıza özel olduğu için medya tarayıcısı dosyalara erişemez.

Aşağıdaki örnek yöntemde, fotoğrafınızı Medya Sağlayıcı'nın veritabanına eklemek için sistemin medya tarayıcısının nasıl çağrılacağı gösterilmektedir. Böylece fotoğrafınız Android Galeri uygulamasında ve diğer uygulamalarda kullanılabilir hale gelir.

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

Ölçeklendirilmiş bir resmin kodunu çözme

Sınırlı bellek ile birden fazla tam boyutlu resim yönetmek zor olabilir. Uygulamanızın, sadece birkaç resim gösterdikten sonra belleği tükendiğini fark ederseniz JPEG'i hedef görünümün boyutuna uyacak şekilde ölçeklendirilmiş bir bellek dizisine genişleterek kullanılan dinamik yığın miktarını önemli ölçüde azaltabilirsiniz. Aşağıdaki örnek yöntemde bu teknik gösterilmektedir.

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