Nota: questa pagina fa riferimento alla classe Fotocamera, che è stata ritirata. Ti consigliamo di utilizzare FotocameraX o, per casi d'uso specifici, Fotocamera2. Sia CameraX che Camera2 supportano Android 5.0 (livello API 21) e versioni successive.
Questa lezione insegna a scattare una foto delegando il lavoro a un'altra app fotocamera sul dispositivo. Se preferisci sviluppare le funzionalità della tua videocamera, consulta Controllo della videocamera.
Supponiamo che tu stia implementando un servizio meteorologico in crowdsourcing che crea una mappa meteorologica globale unendo immagini del cielo scattate da dispositivi che eseguono la tua app client. L'integrazione delle foto è solo una piccola parte della tua applicazione. L'obiettivo è scattare foto con il minimo impegno, senza dover reinventare la fotocamera. Fortunatamente, sulla maggior parte dei dispositivi Android è già installata almeno un'applicazione fotocamera. In questa lezione imparerai a far scattare una foto per te.
Richiedere la funzionalità fotocamera
Se una funzione essenziale della tua applicazione è lo scatto di foto, limitane la visibilità su Google Play ai dispositivi dotati di fotocamera. Per indicare che la tua applicazione dipende da una fotocamera, 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 fotocamera 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 la disponibilità della videocamera in fase di runtime chiamando hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
.
Se la videocamera non è disponibile, disattiva le funzionalità della videocamera.
Scarica la miniatura
Se la semplice impresa di scattare una foto non è il culmine dell'ambizione della tua app, allora probabilmente ti conviene recuperare l'immagine dall'applicazione Fotocamera e utilizzarla.
L'applicazione Fotocamera Android codifica la foto nel reso
Intent
consegnato a
onActivityResult()
come piccolo Bitmap
negli extra,
nella 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 utile per un'icona,
ma non molto di più. Gestire un'immagine a grandezza originale richiede un po' più di lavoro.
Salva la foto a grandezza originale
L'applicazione Fotocamera Android salva una foto a grandezza originale se le assegni un file in cui salvare. Devi fornire un nome file completo in cui l'app Fotocamera deve salvare la foto.
In genere, tutte le foto scattate dall'utente con la fotocamera del dispositivo devono essere salvate sul dispositivo
in una memoria esterna pubblica in modo che siano accessibili a tutte le app. La directory corretta per le foto condivise è fornita da getExternalStoragePublicDirectory()
, con l'argomento DIRECTORY_PICTURES
. La directory fornita con questo metodo è condivisa tra tutte le app. Su Android 9 (livello API 28) e versioni precedenti, la lettura e la scrittura in questa directory richiedono rispettivamente le autorizzazioni READ_EXTERNAL_STORAGE
e WRITE_EXTERNAL_STORAGE
:
<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 di archiviazione, purché l'app debba accedere soltanto alle foto scattate dall'utente utilizzando l'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 dalla versione 4.4 di Android, l'autorizzazione non è più necessaria perché la directory non è accessibile ad altre app, pertanto puoi dichiarare che l'autorizzazione deve essere richiesta solo nelle 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 l'app.
Una volta scelta la directory per il file, devi creare un nome file resistente alle collisioni.
Puoi anche salvare il percorso in una variabile membro per utilizzarlo in seguito. Ecco una soluzione di esempio in un metodo che restituisce un nome file univoco per una nuova foto utilizzando un timestamp con data e 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 disponibile per creare un file per la foto, ora puoi creare e richiamare
Intent
in questo modo:
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://
attraverso il confine di un pacchetto causa un FileUriExposedException
.
Pertanto, ora presentiamo un modo più generico di archiviare le immagini utilizzando un FileProvider
.
Ora devi configurare FileProvider
. Nel file manifest dell'app, aggiungi un provider all'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 di autorizzazione corrisponda al secondo argomento a
getUriForFile(Context, String, File)
.
Nella sezione dei metadati della definizione del provider, puoi vedere che il provider prevede che vengano configurati percorsi idonei 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 del percorso corrisponde al percorso che viene restituito da getExternalFilesDir()
quando richiamato con Environment.DIRECTORY_PICTURES
.
Assicurati di sostituire com.example.package.name
con il nome effettivo del pacchetto della tua app. Inoltre, controlla la documentazione di FileProvider
per una descrizione completa degli identificatori di percorso che puoi utilizzare oltre a external-path
.
Aggiungere la foto a una galleria
Quando crei una foto utilizzando un intent, dovresti sapere dove si trova l'immagine perché hai già detto dove salvarla. Per tutti gli altri, forse il modo più semplice per rendere accessibile una foto è renderla accessibile dal 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 multimediale del sistema per aggiungere la tua foto al database del fornitore di contenuti multimediali, rendendola disponibile nell'applicazione Galleria di Android e in 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); }
Decodifica un'immagine in scala
Se la memoria è limitata, la gestione di più immagini a grandezza originale può essere complessa. Se l'applicazione esaurisce la memoria dopo aver visualizzato solo alcune immagini, puoi ridurre drasticamente la quantità di heap dinamico utilizzato espandendo il formato JPEG in un array di memoria già scalato in modo da corrispondere 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); }