Fotos aufnehmen

Hinweis:Diese Seite bezieht sich auf die Klasse Camera, die eingestellt wurde. Wir empfehlen die Verwendung von KameraX oder für bestimmte Anwendungsfälle Kamera2. CameraX und Camera2 unterstützen Android 5.0 (API-Level 21) und höher.

In dieser Lektion erfahren Sie, wie Sie ein Foto aufnehmen, indem Sie die Arbeit an eine andere Kamera-App auf dem Gerät delegieren. (Falls Sie lieber eigene Kamerafunktionen erstellen möchten, finden Sie entsprechende Informationen unter Kamera steuern.)

Angenommen, Sie implementieren einen Crowd-Source-Wetterdienst, der eine globale Wetterkarte erstellt. Dazu werden Bilder des Himmels kombiniert, die mit Geräten mit Ihrer Client-App aufgenommen wurden. Die Einbindung von Fotos ist nur ein kleiner Teil Ihrer App. Sie möchten Fotos mit minimalem Aufwand aufnehmen und nicht immer wieder neu erfinden. Glücklicherweise ist auf den meisten Android-Geräten bereits mindestens eine Kamera-App installiert. In dieser Lektion erfahren Sie, wie Sie dafür ein Foto aufnehmen.

Kamerafunktion anfordern

Wenn eine wesentliche Funktion deiner App das Aufnehmen von Bildern ist, solltest du ihre Sichtbarkeit bei Google Play auf Geräte mit Kamera beschränken. Um anzugeben, dass deine App auf die Verwendung einer Kamera angewiesen ist, füge ein <uses-feature>-Tag in deine Manifestdatei ein:

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

Wenn Ihre Anwendung zwar eine Kamera verwendet, aber keine Kamera erfordert, setzen Sie stattdessen android:required auf false. Google Play erlaubt dann Geräten ohne Kamera, deine App herunterzuladen. Es liegt in deiner Verantwortung, durch Aufrufen von hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) die Verfügbarkeit der Kamera zur Laufzeit zu prüfen. Wenn keine Kamera verfügbar ist, sollten Sie die Kamerafunktionen deaktivieren.

Thumbnail abrufen

Wenn die einfache Aufgabe beim Aufnehmen eines Fotos nicht der Höhepunkt des Ehrgeizs deiner App ist, solltest du das Bild wahrscheinlich von der Kamera-App zurückerhalten und etwas damit machen.

Die Android-Kamera-App codiert das Foto in der Rückgabe Intent, die an onActivityResult() gesendet wird, als kleine Bitmap in den Extras unter dem Schlüssel "data". Mit dem folgenden Code wird dieses Bild abgerufen und in einem ImageView angezeigt.

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

Hinweis: Dieses Miniaturbild vom "data" könnte gut für ein Symbol sein, aber nicht viel mehr. Der Umgang mit einem Bild in Originalgröße ist etwas aufwendiger.

Foto in Originalgröße speichern

Die Android-Kamera-App speichert Fotos in Originalgröße, wenn Sie ihr eine Datei zum Speichern geben. Du musst einen voll qualifizierten Dateinamen angeben, unter dem die Kamera-App das Foto speichern soll.

Grundsätzlich sollten alle Fotos, die der Nutzer mit der Gerätekamera aufnimmt, in einem öffentlichen externen Speicher auf dem Gerät gespeichert werden, damit alle Apps darauf zugreifen können. Das richtige Verzeichnis für geteilte Fotos wird von getExternalStoragePublicDirectory() mit dem Argument DIRECTORY_PICTURES bereitgestellt. Das durch diese Methode bereitgestellte Verzeichnis wird von allen Apps gemeinsam genutzt. Unter Android 9 (API-Level 28) und niedriger sind für das Lesen und Schreiben in diesem Verzeichnis die Berechtigungen READ_EXTERNAL_STORAGE bzw. WRITE_EXTERNAL_STORAGE erforderlich:

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

Unter Android 10 (API-Level 29) und höher ist das richtige Verzeichnis zum Teilen von Fotos die Tabelle MediaStore.Images. Du musst keine Speicherberechtigungen angeben, solange deine App nur auf die Fotos zugreifen muss, die der Nutzer mit deiner App aufgenommen hat.

Wenn du jedoch möchtest, dass die Fotos nur für deine App privat bleiben, kannst du stattdessen das von Context.getExternalFilesDir() bereitgestellte Verzeichnis verwenden. Unter Android 4.3 und niedriger ist zum Schreiben in dieses Verzeichnis außerdem die Berechtigung WRITE_EXTERNAL_STORAGE erforderlich. Ab Android 4.4 ist die Berechtigung nicht mehr erforderlich, da andere Apps nicht auf das Verzeichnis zugreifen können. Sie können also erklären, dass die Berechtigung nur unter niedrigeren Versionen von Android angefordert werden sollte, indem Sie das Attribut maxSdkVersion hinzufügen:

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

Hinweis: Dateien, die Sie in den von getExternalFilesDir() oder getFilesDir() bereitgestellten Verzeichnissen speichern, werden gelöscht, wenn der Nutzer die App deinstalliert.

Nachdem Sie das Verzeichnis für die Datei ausgewählt haben, müssen Sie einen kollisionssicheren Dateinamen erstellen. Sie können den Pfad auch zur späteren Verwendung in einer Member-Variablen speichern. Hier ist eine Beispiellösung für eine Methode, die einen eindeutigen Dateinamen für ein neues Foto mit Datums- und Zeitstempel zurückgibt. In diesem Beispiel wird davon ausgegangen, dass Sie die Methode aus einem Context aufrufen.

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

Wenn diese Methode zum Erstellen einer Datei für das Foto verfügbar ist, kannst du das Intent jetzt so erstellen und aufrufen:

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

Hinweis: Wir verwenden getUriForFile(Context, String, File), das einen content://-URI zurückgibt. Bei neueren Apps, die auf Android 7.0 (API-Level 24) und höher ausgerichtet sind, wird durch die Übergabe eines file://-URI über eine Paketgrenze ein FileUriExposedException ausgelöst. Aus diesem Grund stellen wir jetzt eine allgemeinere Art zum Speichern von Bildern mit FileProvider vor.

Jetzt müssen Sie FileProvider konfigurieren. Fügen Sie Ihrer Anwendung im Manifest Ihrer Anwendung einen Anbieter hinzu:

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

Achten Sie darauf, dass der Autorisierungsstring mit dem zweiten Argument getUriForFile(Context, String, File) übereinstimmt. Im Metadatenabschnitt der Anbieterdefinition sehen Sie, dass der Anbieter erwartet, dass zulässige Pfade in einer dedizierten Ressourcendatei konfiguriert werden: res/xml/file_paths.xml. Hier sind die für dieses Beispiel erforderlichen Inhalte:

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

Die Pfadkomponente entspricht dem Pfad, der von getExternalFilesDir() zurückgegeben wird, wenn er mit Environment.DIRECTORY_PICTURES aufgerufen wird. Achte darauf, com.example.package.name durch den tatsächlichen Paketnamen deiner Anwendung zu ersetzen. In der Dokumentation zu FileProvider findest du eine ausführliche Beschreibung der Pfadspezifizierer, die du zusätzlich zu external-path verwenden kannst.

Foto zu einer Galerie hinzufügen

Wenn Sie ein Foto über einen Intent erstellen, sollten Sie wissen, wo sich das Bild befindet, da Sie es zuvor angegeben haben. Für alle anderen ist der einfachste Weg, ein Foto zugänglich zu machen, es über den Medienanbieter des Systems zugänglich zu machen.

Hinweis: Wenn Sie Ihr Foto in dem von getExternalFilesDir() angegebenen Verzeichnis gespeichert haben, kann der Medienscanner nicht auf die Dateien zugreifen, da sie nur für Ihre Anwendung zugänglich sind.

Die folgende Beispielmethode zeigt, wie der Medienscanner des Systems aufgerufen wird, um dein Foto der Datenbank des Mediaanbieters hinzuzufügen und es in der Android Gallery App und für andere Apps verfügbar zu machen.

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

Skaliertes Bild decodieren

Die Verwaltung mehrerer Bilder in Originalgröße kann bei begrenztem Speicherplatz schwierig sein. Wenn Sie feststellen, dass Ihrer Anwendung nach dem Anzeigen nur einiger Bilder nicht mehr genügend Arbeitsspeicher zur Verfügung steht, können Sie die Menge des verwendeten dynamischen Heaps erheblich reduzieren. Dazu erweitern Sie die JPEG-Datei in ein Speicherarray, das bereits auf die Größe der Zielansicht skaliert wurde. Die folgende Beispielmethode veranschaulicht diese Technik.

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