Hinweis:Auf dieser Seite wird die Klasse Camera verwendet, die verworfen wurde. 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 wird beschrieben, wie du ein Foto aufnimmst, indem du die Arbeit an eine andere Kamera-App auf dem Gerät delegierst. Wenn Sie lieber Ihre eigene Kamerafunktion erstellen möchten, finden Sie weitere Informationen unter Kamera steuern.
Angenommen, Sie implementieren einen Crowdsourcing-Wetterdienst, der eine globale Wetterkarte erstellt, indem er Bilder des Himmels zusammenfügt, die von Geräten mit Ihrer Client-App aufgenommen wurden. Das Einbinden von Fotos ist nur ein kleiner Teil Ihrer Anwendung. Du möchtest einfach nur Fotos machen 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 die Kamera dazu bringen, ein Foto für Sie aufzunehmen.
Kamerafunktion anfordern
Wenn eine wichtige Funktion deiner App das Aufnehmen von Bildern ist, solltest du die Sichtbarkeit deiner App bei Google Play auf Geräte mit einer Kamera beschränken. Wenn Sie angeben möchten, dass Ihre Anwendung eine Kamera benötigt, fügen Sie Ihrer Manifestdatei ein <uses-feature>-Tag hinzu:
<manifest ... > <uses-feature android:name="android.hardware.camera" android:required="true" /> ... </manifest>
Wenn deine App eine Kamera verwendet, aber nicht benötigt, um zu funktionieren, setze android:required auf false. Dadurch können Geräte ohne Kamera deine App bei Google Play herunterladen. Es liegt dann in deiner Verantwortung, die Verfügbarkeit der Kamera zur Laufzeit durch Aufrufen von hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) zu prüfen.
Wenn eine Kamera nicht verfügbar ist, solltest du die Kamerafunktionen deaktivieren.
Thumbnail abrufen
Wenn das einfache Aufnehmen eines Fotos nicht das einzige Ziel deiner App ist, möchtest du das Bild wahrscheinlich von der Kamera-App zurückerhalten und etwas damit tun.
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 Thumbnail-Bild von "data" eignet sich möglicherweise für ein Symbol, aber nicht für viel mehr. Die Verarbeitung eines Bildes in voller Größe erfordert etwas mehr Aufwand.
Foto in Originalgröße speichern
Die Android-Kamera-App speichert ein Foto in voller Größe, wenn Sie ihr eine Datei zum Speichern übergeben. Du musst einen voll qualifizierten Dateinamen angeben, unter dem die Kamera-App das Foto speichern soll.
Im Allgemeinen sollten alle Fotos, die der Nutzer mit der Gerätekamera aufnimmt, auf dem Gerät im öffentlichen externen Speicher gespeichert werden, damit sie für alle Apps zugänglich sind. Das richtige Verzeichnis für geteilte Fotos wird von getExternalStoragePublicDirectory() mit dem Argument DIRECTORY_PICTURES bereitgestellt. Das von dieser Methode bereitgestellte Verzeichnis wird von allen Apps gemeinsam genutzt. Unter Android 9 (API-Level 28) und niedriger sind zum 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 deklarieren, solange deine App nur auf die Fotos zugreifen muss, die der Nutzer mit deiner App aufgenommen hat.
Wenn die Fotos jedoch nur für deine App privat bleiben sollen, kannst du stattdessen das von Context.getExternalFilesDir() bereitgestellte Verzeichnis verwenden.
Unter Android 4.3 und niedriger ist für das Schreiben in dieses Verzeichnis auch 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 angeben, dass die Berechtigung nur für niedrigere 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 du in den von getExternalFilesDir() oder getFilesDir() bereitgestellten Verzeichnissen speicherst, werden gelöscht, wenn der Nutzer deine 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 speichern, um ihn später zu verwenden. Hier ist ein Beispiel für eine Lösung in einer Methode, die einen eindeutigen Dateinamen für ein neues Foto mit einem Datums- und Zeitstempel zurückgibt.
(In diesem Beispiel wird davon ausgegangen, dass Sie die Methode innerhalb eines 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; }
Da diese Methode zum Erstellen einer Datei für das Foto verfügbar ist, können Sie den 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), wodurch ein content://-URI zurückgegeben wird. Bei neueren Apps, die auf Android 7.0 (API-Level 24) und höher ausgerichtet sind, führt die Übergabe eines file://-URI über eine Paketgrenze hinweg zu einer FileUriExposedException.
Daher stellen wir jetzt eine allgemeinere Methode zum Speichern von Bildern mit einem FileProvider vor.
Jetzt müssen Sie die FileProvider konfigurieren. Fügen Sie im Manifest Ihrer App 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 String „authorities“ mit dem zweiten Argument für getUriForFile(Context, String, File) übereinstimmt.
Im Metadatenbereich der Anbieterdefinition sehen Sie, dass der Anbieter erwartet, dass zulässige Pfade in einer dedizierten Ressourcendatei konfiguriert werden:
<?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 sie mit Environment.DIRECTORY_PICTURES aufgerufen wird.
Ersetzen Sie com.example.package.name durch den tatsächlichen Paketnamen Ihrer App. Eine ausführliche Beschreibung der Pfadspezifizierer, die Sie neben external-path verwenden können, finden Sie in der Dokumentation zu FileProvider.
Fügen Sie das Foto einer Galerie hinzu.
Wenn du ein Foto über einen Intent erstellst, solltest du wissen, wo sich das Bild befindet, da du angegeben hast, wo es gespeichert werden soll. Für alle anderen ist es wahrscheinlich am einfachsten, das Foto über den MediaProvider des Systems zugänglich zu machen.
Hinweis:Wenn du dein Foto in dem von getExternalFilesDir() bereitgestellten Verzeichnis gespeichert hast, kann der Medienscanner nicht auf die Dateien zugreifen, da sie für deine App privat sind.
Die folgende Beispielmethode zeigt, wie Sie den Medienscanner des Systems aufrufen, um Ihr Foto der Datenbank des Medienanbieters hinzuzufügen, damit es in der Android-Galerie-App und in anderen Apps verfügbar ist.
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 voller Größe kann bei begrenztem Speicherplatz schwierig sein. Wenn deine Anwendung nach der Anzeige von nur wenigen Bildern Out of Memory ist, kannst du die Menge des verwendeten dynamischen Heaps drastisch reduzieren, indem du das JPEG in ein Speicher-Array erweiterst, 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); }