Fotos aufnehmen

Hinweis:Auf dieser Seite wird die Klasse Camera beschrieben, die eingestellt wird. Wir empfehlen die Verwendung von CameraX oder für bestimmte Anwendungsfälle Camera2. Sowohl CameraX als auch 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 Aufgabe an eine andere Kamera-App auf dem Gerät delegieren. Wenn Sie lieber Ihre eigene Kamerafunktion erstellen möchten, lesen Sie den Hilfeartikel Kamera steuern.

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

Kamerafunktion anfordern

Wenn das Aufnehmen von Fotos eine wichtige Funktion Ihrer App ist, beschränken Sie die Sichtbarkeit bei Google Play auf Geräte mit Kamera. Wenn Sie angeben möchten, dass Ihre Anwendung eine Kamera benötigt, fügen Sie das Tag <uses-feature> in Ihre Manifestdatei ein:

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

Wenn Ihre Anwendung eine Kamera verwendet, diese aber nicht zur Funktion benötigt, setzen Sie android:required stattdessen auf false. So können Nutzer auf Geräten ohne Kamera Ihre App herunterladen. Sie müssen dann die Verfügbarkeit der Kamera zur Laufzeit prüfen, indem Sie hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) aufrufen. Wenn keine Kamera verfügbar ist, sollten Sie die Kamerafunktionen deaktivieren.

Thumbnail abrufen

Wenn das einfache Aufnehmen eines Fotos nicht der Höhepunkt Ihrer App ist, möchten Sie das Bild wahrscheinlich aus der Kamera-App zurückholen und etwas damit tun.

Die Android-Kamera-App codiert das Foto in der AntwortIntent, die an onActivityResult() gesendet wird, als kleine Bitmap in den Extras unter dem Schlüssel "data". Der folgende Code ruft dieses Bild ab und zeigt es in einer ImageView an.

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 Thumbnail-Bild von "data" eignet sich zwar für ein Symbol, aber nicht für viel mehr. Die Arbeit mit einem Bild in voller Größe ist etwas aufwendiger.

Foto in Originalgröße speichern

Die Android-Kamera-App speichert ein Foto in Originalgröße, wenn Sie eine Datei angeben, in der es gespeichert werden soll. Sie müssen einen voll qualifizierten Dateinamen angeben, unter dem die Kamera-App das Foto speichern soll.

Grundsätzlich sollten alle Fotos, die der Nutzer mit der Kamera des Geräts aufnimmt, auf dem Gerät im öffentlichen externen Speicher gespeichert werden, damit alle Apps darauf zugreifen können. Das richtige Verzeichnis für freigegebene Fotos wird von getExternalStoragePublicDirectory() mit dem Argument DIRECTORY_PICTURES angegeben. Das Verzeichnis, das mit dieser Methode angegeben wird, wird von allen Apps gemeinsam genutzt. Unter Android 9 (API-Ebene 28) und niedriger sind die Berechtigungen READ_EXTERNAL_STORAGE und WRITE_EXTERNAL_STORAGE erforderlich, um in diesem Verzeichnis lesen und schreiben zu können:

<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 die Tabelle MediaStore.Images das richtige Verzeichnis für die Freigabe von Fotos. Sie müssen keine Speicherberechtigungen angeben, solange Ihre App nur auf die Fotos zugreifen muss, die der Nutzer mit Ihrer App aufgenommen hat.

Wenn die Fotos jedoch nur für Ihre App sichtbar sein sollen, können Sie stattdessen das von Context.getExternalFilesDir() bereitgestellte Verzeichnis verwenden. Unter Android 4.3 und niedriger ist für das 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 angeben, dass die Berechtigung nur bei niedrigeren Android-Versionen angefordert werden soll, 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 Ihre App deinstalliert.

Nachdem Sie das Verzeichnis für die Datei festgelegt haben, müssen Sie einen kollisionsresistenten Dateinamen erstellen. Sie können den Pfad auch in einer Mitgliedsvariablen für die spätere Verwendung speichern. Hier ist eine Beispiellösung in einer Methode, die mit einem Zeitstempel einen eindeutigen Dateinamen für ein neues Foto zurückgibt. In diesem Beispiel wird davon ausgegangen, dass Sie die Methode innerhalb einer 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;
}

Nachdem Sie eine Datei für das Foto erstellt haben, können Sie Intent 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) oder höher ausgerichtet sind, führt das Übergeben eines file://-URI über eine Paketgrenze zu einer FileUriExposedException. Daher stellen wir jetzt eine allgemeinere Möglichkeit zum Speichern von Bildern mithilfe einer FileProvider vor.

Jetzt müssen Sie FileProvider konfigurieren. Fügen Sie Ihrer Anwendung im Manifest 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>

Der String „authorities“ muss mit dem zweiten Argument für getUriForFile(Context, String, File) übereinstimmen. Im Abschnitt „Metadaten“ der Anbieterdefinition sehen Sie, dass der Anbieter erwartet, dass zulässige Pfade in einer speziellen Ressourcendatei res/xml/file_paths.xml konfiguriert werden. Hier ist der für dieses Beispiel erforderliche Inhalt:

<?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. Ersetzen Sie com.example.package.name durch den tatsächlichen Paketnamen Ihrer App. In der Dokumentation zu FileProvider finden Sie eine ausführliche Beschreibung der Pfadspezifier, die Sie neben external-path verwenden können.

Foto zu einer Galerie hinzufügen

Wenn Sie ein Foto über einen Intent erstellen, sollten Sie wissen, wo sich das Bild befindet, da Sie angegeben haben, wo es gespeichert werden soll. Für alle anderen ist es am einfachsten, Ihr Foto über den Medienanbieter des Systems zugänglich zu machen.

Hinweis:Wenn Sie Ihr Foto im von getExternalFilesDir() bereitgestellten Verzeichnis gespeichert haben, kann der Medienscanner nicht auf die Dateien zugreifen, da sie nur für Ihre App freigegeben sind.

In der folgenden Beispielmethode wird gezeigt, wie Sie den Medienscanner des Systems aufrufen, um Ihr Foto zur Datenbank des Medienanbieters hinzuzufügen und es in der Android-Galerie-App und anderen 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

Bei begrenztem Arbeitsspeicher kann die Verwaltung mehrerer Bilder in voller Größe schwierig sein. Wenn der Speicher Ihrer Anwendung nach dem Anzeigen weniger als 10 Bilder aufgebraucht ist, können Sie die Größe des dynamischen Heaps erheblich reduzieren, indem Sie das JPEG in ein Speicherarray erweitern, das bereits auf die Größe der Zielansicht skaliert ist. 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);
}