Nota:questa pagina fa riferimento alla classe Fotocamera, che è stata ritirata. Ti consigliamo di utilizzare CameraX o, per casi d'uso specifici, Camera2. Sia CameraX che Camera2 supportano Android 5.0 (livello API 21) e versioni successive.
Questa lezione insegna come scattare una foto delegando il lavoro a un'altra app fotocamera disponibile dispositivo. Se preferisci creare la tua funzionalità della videocamera, consulta Controllare la videocamera.
Supponiamo che tu stia implementando un servizio meteo in crowdsourcing che crea una mappa del meteo globale mescolando le foto del cielo scattate dai dispositivi su cui è in esecuzione la tua app client. L'integrazione delle foto è solo una piccola parte della tua applicazione. Vuoi scattare foto senza problemi, non reinventare la fotocamera. Fortunatamente, la maggior parte dei dispositivi Android ha già installato almeno un'applicazione per la fotocamera. In questa lezione imparerai a scattare una foto al posto tuo.
Richiedere la funzionalità della fotocamera
Se una funzione essenziale dell'applicazione sta scattando foto, limitane la visibilità sul
Google Play su dispositivi dotati di videocamera. Per pubblicizzare il fatto che la tua applicazione dipende dalla presenza di una
videocamera, inserisci un
tag <uses-feature>
nel
file manifest:
<manifest ... > <uses-feature android:name="android.hardware.camera" android:required="true" /> ... </manifest>
Se la tua applicazione utilizza, ma non richiede una videocamera per funzionare, imposta android:required
su false
. In questo modo, Google Play consentirà ai dispositivi senza fotocamera di scaricare la tua applicazione. È quindi tua responsabilità verificare
della videocamera in fase di esecuzione chiamando
hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
.
Se una videocamera non è disponibile, devi disattivare le relative funzionalità.
Scarica la miniatura
Se la semplice azione di scattare una foto non è il traguardo della tua app, probabilmente vorrai recuperare l'immagine dall'applicazione della fotocamera e utilizzarla.
L'applicazione Fotocamera di Android codifica la foto nel messaggio di risposta
Intent
inviato a
onActivityResult()
come piccolo Bitmap
negli extra,
sotto la chiave "data"
. Il codice seguente recupera questa immagine e la visualizza in un
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); } }
Nota: questa immagine in miniatura di "data"
potrebbe essere adatta per un'icona,
ma non molto di più. La gestione di un'immagine a grandezza originale richiede un po' più di lavoro.
Salvare la foto a grandezza originale
L'applicazione Fotocamera di Android salva una foto a grandezza originale se le specifichi un file in cui salvarla. Devi fornire un nome file completo in cui l'app della fotocamera deve salvare la foto.
In genere, tutte le foto scattate dall'utente con la fotocamera del dispositivo devono essere salvate sul dispositivo
nello spazio di archiviazione esterno pubblico in modo che siano accessibili a tutte le app. La directory corretta per i file
le foto sono fornite da
getExternalStoragePublicDirectory()
,
con
DIRECTORY_PICTURES
. La directory fornita da questo metodo è condivisa tra tutte le app. Su Android 9 (livello API
28) e più in basso, per leggere e scrivere in questa directory è necessario
READ_EXTERNAL_STORAGE
e
WRITE_EXTERNAL_STORAGE
autorizzazioni, rispettivamente:
<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
Su Android 10 (livello API 29) e versioni successive, la directory corretta per la condivisione delle foto è la
Tabella MediaStore.Images
.
Non è necessario dichiarare alcuna autorizzazione per lo spazio di archiviazione, ma è sufficiente che l'app acceda
foto scattate dall'utente utilizzando la tua app.
Tuttavia, se vuoi che le foto rimangano private solo per la tua app, puoi utilizzare la directory fornita da Context.getExternalFilesDir()
.
Su Android 4.3 e versioni precedenti, la scrittura in questa directory richiede anche l'autorizzazione WRITE_EXTERNAL_STORAGE
. A partire da Android 4.4, l'autorizzazione non è più richiesta perché la directory
non è accessibile da altre app, quindi puoi dichiarare che l'autorizzazione deve essere richiesta solo sulle
versioni precedenti di Android aggiungendo l'attributo
maxSdkVersion
:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> ... </manifest>
Nota: i file salvati nelle directory fornite da
getExternalFilesDir()
o
getFilesDir()
vengono
eliminati quando l'utente disinstalla la tua app.
Una volta scelta la directory per il file, devi creare un nome per il file resistente alle collisioni.
Ti consigliamo inoltre di salvare il percorso in una variabile membro per utilizzarlo in un secondo momento. Ecco una soluzione di esempio
in un metodo che restituisce un nome di file univoco per una nuova foto utilizzando la data e l'ora.
Questo esempio presuppone che tu stia chiamando il metodo dall'interno di un 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; }
Con questo metodo a disposizione per creare un file per la foto, ora puoi creare e richiamare Intent
come segue:
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); } } }
Nota: stiamo utilizzando
getUriForFile(Context, String, File)
che restituisce un URI content://
. Per le app più recenti che hanno come target Android 7.0 (livello API 24) e versioni successive, il passaggio di un URI file://
oltre un confine del pacchetto causa un FileUriExposedException
.
Pertanto, ora presentiamo un modo più generico per archiviare le immagini utilizzando
FileProvider
.
A questo punto, devi configurare
FileProvider
. Nel tuo
manifest dell'app, aggiungi un provider alla tua applicazione:
<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>
Assicurati che la stringa autorità corrisponda al secondo argomento a
getUriForFile(Context, String, File)
.
Nella sezione dei metadati della definizione del provider, puoi vedere che il provider si aspetta
percorsi idonei da configurare in un file di risorse dedicato,
<?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>
Il componente percorso corrisponde al percorso restituito da
getExternalFilesDir()
quando viene chiamato con
Environment.DIRECTORY_PICTURES
.
Assicurati di sostituire com.example.package.name
con il nome effettivo del pacchetto
la tua app. Consulta anche la documentazione
FileProvider
per un
descrizione dettagliata degli indicatori di percorso che puoi utilizzare oltre a external-path
.
Aggiungere la foto a una galleria
Quando crei una foto tramite un'intenzione, dovresti sapere dove si trova l'immagine, perché hai indicato dove salvarla. Per tutti gli altri, forse il modo più semplice per accessibile la foto consiste nel renderla accessibile al fornitore di contenuti multimediali del sistema.
Nota:se hai salvato la foto nella directory fornita da
getExternalFilesDir()
,
lo scanner multimediale non può accedere ai file perché sono privati per la tua app.
Il seguente metodo di esempio mostra come richiamare lo scanner di contenuti multimediali del sistema per aggiungere il tuo foto al database del fornitore di contenuti multimediali, rendendola disponibile nell'applicazione Android Gallery e ad altre app.
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); }
Decodificare un'immagine scalata
Gestire più immagini a grandezza originale può essere complicato se la memoria è limitata. Se la tua applicazione esaurisce la memoria dopo aver visualizzato solo alcune immagini, puoi ridurre notevolmente la quantità di heap dinamico utilizzato espandendo il file JPEG in un array di memoria già scalato per essere corrispondente alle dimensioni della visualizzazione di destinazione. Il seguente metodo di esempio dimostra questa tecnica.
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); }