Android ACTION_IMAGE_CAPTURE Intento


163

Stiamo provando a utilizzare l'app della fotocamera nativa per consentire all'utente di scattare una nuova foto. Funziona bene se tralasciamo EXTRA_OUTPUT extrae restituisce la piccola immagine Bitmap. Tuttavia, se abbiamo putExtra(EXTRA_OUTPUT,...)l'intenzione prima di avviarlo, tutto funziona finché non si tenta di premere il pulsante "Ok" nell'app della fotocamera. Il pulsante "Ok" non fa nulla. L'app della fotocamera rimane aperta e nulla si blocca. Possiamo cancellarlo, ma il file non viene mai scritto. Cosa dobbiamo fare esattamente ACTION_IMAGE_CAPTUREper scrivere la foto scattata in un file?

Modifica: questo viene fatto tramite l' MediaStore.ACTION_IMAGE_CAPTUREintento, solo per essere chiari

Risposte:


144

questo è un bug ben documentato in alcune versioni di Android. vale a dire, sulle esperienze di google build di Android, l'acquisizione di immagini non funziona come documentato. quello che ho generalmente usato è qualcosa di simile in una classe di utilità.

public boolean hasImageCaptureBug() {

    // list of known devices that have the bug
    ArrayList<String> devices = new ArrayList<String>();
    devices.add("android-devphone1/dream_devphone/dream");
    devices.add("generic/sdk/generic");
    devices.add("vodafone/vfpioneer/sapphire");
    devices.add("tmobile/kila/dream");
    devices.add("verizon/voles/sholes");
    devices.add("google_ion/google_ion/sapphire");

    return devices.contains(android.os.Build.BRAND + "/" + android.os.Build.PRODUCT + "/"
            + android.os.Build.DEVICE);

}

quindi quando avvio l'acquisizione di immagini, creo un intento che controlla il bug.

Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
if (hasImageCaptureBug()) {
    i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File("/sdcard/tmp")));
} else {
    i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
}
startActivityForResult(i, mRequestCode);

quindi nell'attività a cui torno, faccio diverse cose in base al dispositivo.

protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
     switch (requestCode) {
         case GlobalConstants.IMAGE_CAPTURE:
             Uri u;
             if (hasImageCaptureBug()) {
                 File fi = new File("/sdcard/tmp");
                 try {
                     u = Uri.parse(android.provider.MediaStore.Images.Media.insertImage(getContentResolver(), fi.getAbsolutePath(), null, null));
                     if (!fi.delete()) {
                         Log.i("logMarker", "Failed to delete " + fi);
                     }
                 } catch (FileNotFoundException e) {
                     e.printStackTrace();
                 }
             } else {
                u = intent.getData();
            }
    }

questo ti evita di dover scrivere una nuova app per la fotocamera, ma neanche questo codice è eccezionale. i grandi problemi sono

  1. non ottieni mai immagini a grandezza naturale dai dispositivi con il bug. ottieni immagini larghe 512 pixel che vengono inserite nel provider di contenuti di immagini. sui dispositivi senza bug, tutto funziona come un documento, ottieni un'immagine normale di grandi dimensioni.

  2. devi mantenere la lista. come scritto, è possibile eseguire il flashing dei dispositivi con una versione di Android (ad esempio build di cyanogenmod ) che ha corretto il bug. in tal caso, il codice si arresterà in modo anomalo. la soluzione consiste nell'utilizzare l'intera impronta digitale del dispositivo.


21
su Galaxy S: intent.getData () restituisce null e, inoltre, l'app della fotocamera su Galaxy s inserisce da sola una nuova foto nella galleria, ma non viene restituito nessun ur. Quindi, se inserisci una foto dal file nella galleria, ci sarà una foto duplicata.
Arseniy,

1
hey il mio dispositivo non è elencato qui e l'ho implementato per via tua, ma la mia applicazione si blocca ancora non so il motivo. Per favore aiutami
Geetanjali

usando questo codice su dispositivi droid-x e sony xpheria. Entrambi i dispositivi restituiscono il valore di intento come null. anche droidx restituisce il codice risultato come 0 mentre xpheria restituisce il codice risultato come -1. Qualcuno sa perché dovrebbe essere così.
rOrlig,

5
Il link al "bug ben documentato" che è all'inizio della risposta di yanokwa non è corretto. Quel bug riguarda la chiamata all'app della fotocamera senza putExtra (EXTRA_OUTPUT, ...)
Frank Harper


50

So che a questo è già stata data una risposta, ma so che molte persone sono inciampate su questo, quindi aggiungerò un commento.

Ho avuto lo stesso identico problema nel mio Nexus One. Questo proveniva dal file non esistente sul disco prima dell'avvio dell'app della fotocamera. Pertanto, mi sono assicurato che il file esistente prima avviasse l'app della fotocamera. Ecco alcuni esempi di codice che ho usato:

String storageState = Environment.getExternalStorageState();
        if(storageState.equals(Environment.MEDIA_MOUNTED)) {

            String path = Environment.getExternalStorageDirectory().getName() + File.separatorChar + "Android/data/" + MainActivity.this.getPackageName() + "/files/" + md5(upc) + ".jpg";
            _photoFile = new File(path);
            try {
                if(_photoFile.exists() == false) {
                    _photoFile.getParentFile().mkdirs();
                    _photoFile.createNewFile();
                }

            } catch (IOException e) {
                Log.e(TAG, "Could not create file.", e);
            }
            Log.i(TAG, path);

            _fileUri = Uri.fromFile(_photoFile);
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE );
            intent.putExtra( MediaStore.EXTRA_OUTPUT, _fileUri);
            startActivityForResult(intent, TAKE_PICTURE);
        }   else {
            new AlertDialog.Builder(MainActivity.this)
            .setMessage("External Storeage (SD Card) is required.\n\nCurrent state: " + storageState)
            .setCancelable(true).create().show();
        }

Per prima cosa creo un nome di file univoco (in qualche modo) usando un hash MD5 e lo metto nella cartella appropriata. Quindi controllo per vedere se esiste (non dovrebbe, ma è buona norma controllare comunque). Se non esiste, ottengo la directory principale (una cartella) e creo la gerarchia delle cartelle (quindi se le cartelle che portano alla posizione del file non esistono, seguiranno questa riga. Quindi Creo il file: una volta creato il file ottengo l'Uri e lo passo all'intento, quindi il pulsante OK funziona come previsto e tutto diventa dorato.

Ora, quando si preme il pulsante Ok sull'app della fotocamera, il file sarà presente nella posizione specificata. In questo esempio sarebbe /sdcard/Android/data/com.example.myapp/files/234asdioue23498ad.jpg

Non è necessario copiare il file in "onActivityResult" come pubblicato sopra.


13
Assicurati di avere <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />nel tuo manifest se stai utilizzando una versione più recente di Android 1.5: ho trascorso alcune ore a eseguire il debug delle cose della fotocamera e questo non è stato incluso, quindi i miei salvataggi fallirebbero sempre.
Swanson,

Questo funziona bene per me, ma ogni 10 immagini circa, il file vuoto non viene sovrascritto e finisco con un file a 0 byte dove dovrebbe essere l'immagine. È molto fastidioso ed è stato difficile rintracciarlo.
joey_g216,

2
su alcuni dispositivi, come il Nexus 7 con jelly bean, è necessario modificare Environment.getExternalStorageDirectory (). getName () per utilizzare .getAbsolutePath () invece di .getName ()
Keith

35

Ho attraversato una serie di strategie di acquisizione di foto e sembra che ci sia sempre un caso, una piattaforma o determinati dispositivi, in cui alcune o tutte le strategie di cui sopra falliranno in modi inaspettati. Sono stato in grado di trovare una strategia che utilizza il codice di generazione URI di seguito, che sembra funzionare nella maggior parte se non in tutti i casi.

mPhotoUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
            new ContentValues());
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mPhotoUri);
startActivityForResult(intent,CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE_CONTENT_RESOLVER);

Per contribuire ulteriormente alla discussione e aiutare i nuovi arrivati, ho creato un'app di esempio / test che mostra diverse strategie per l'implementazione di foto. I contributi di altre implementazioni sono decisamente incoraggiati ad aggiungere alla discussione.

https://github.com/deepwinter/AndroidCameraTester


Questo è stato grandioso. Devo ancora testarlo su tutti i miei dispositivi, ma suppongo che tu l'abbia già fatto un bel po '. C'è così tanto rumore su questo argomento, e questa risposta particolare è così semplice e pulita. Finora funziona su Nexus 5, che è dove ho ricevuto per la prima volta molti rapporti secondo cui ACTION_IMAGE_CAPTURE non funzionava senza EXTRA_OUTPUT. Spero che questo funzioni su tutta la linea, ma finora tutto bene. Grazie
Rich

1
@deepwinter - Grazie signore. Mi stavo togliendo i capelli ma la soluzione per la risoluzione dei contenuti funziona bene su tutti gli scenari problematici che ho avuto.
Billy Coover,

Un po 'tardi a questa festa, ma questa è stata l'unica soluzione per lavorare per me. Grazie mille!
StackJP,

posso in qualche modo recuperare MediaStore.EXTRA_OUTPUT onActivityResulto devo davvero ricordarmelo da solo? Dovrei salvarlo in modo persistente in modo che la mia app lo ricordi anche se viene distrutta mentre viene scattata una foto ...
prom85

Non funziona su Samsung Galaxy Note 3 con lolipop: ava.lang.NullPointerException: tentativo di invocare il metodo virtuale 'java.lang.String android.net.Uri.getScheme ()' su un riferimento a oggetto null
Igor Janković

30

Ho avuto lo stesso problema in cui il pulsante OK nell'app della fotocamera non ha fatto nulla, sia sull'emulatore che su Nexus One.

Il problema è scomparso dopo aver specificato un nome file sicuro senza spazi bianchi, senza caratteri speciali, in MediaStore.EXTRA_OUTPUT Inoltre, se si specifica un file che risiede in una directory non ancora creata, è necessario crearlo prima. L'app Camera non fa mkdir per te.


1
E assicurati di usare mkdirs()invece che mkdir()se il tuo percorso ha più di un livello di directory e vuoi che vengano creati tutti ( mkdircrea solo l'ultimo, la directory 'leaf'). Ho avuto dei problemi con questo, e questa risposta mi ha aiutato.
Diana,

possiamo usare semplici timestamp ?? o possono anche fare degli errori ?? per
favore, taggami

@ user2247689 Non sono sicuro del nome del file creato da "semplici timestamp" ma purché non contengano caratteri speciali non consentiti dal formato FAT sulla scheda SD, dovrebbe essere ok, penso.
Yenchi,

28

Il flusso di lavoro che descrivi dovrebbe funzionare come l'hai descritto. Potrebbe essere utile se tu potessi mostrarci il codice attorno alla creazione dell'Intento. In generale, il seguente schema dovrebbe consentirti di fare ciò che stai provando.

private void saveFullImage() {
  Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  File file = new File(Environment.getExternalStorageDirectory(), "test.jpg");
  outputFileUri = Uri.fromFile(file);
  intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
  startActivityForResult(intent, TAKE_PICTURE);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if ((requestCode == TAKE_PICTURE) && (resultCode == Activity.RESULT_OK)) {
    // Check if the result includes a thumbnail Bitmap
    if (data == null) {    
      // TODO Do something with the full image stored
      // in outputFileUri. Perhaps copying it to the app folder
    }
  }
}

Nota che è l' attività della fotocamera che creerà e salverà il file e non fa effettivamente parte dell'applicazione, quindi non avrà l'autorizzazione di scrittura nella cartella dell'applicazione. Per salvare un file nella cartella dell'app, creare un file temporaneo sulla scheda SD e spostarlo nella cartella dell'app nel onActivityResultgestore.


Questo / dovrebbe / funzionare, ma il pulsante ok dell'app della fotocamera non fa nulla. Siamo riusciti a farlo scrivere sulla scheda SD, quindi sospetto che si tratti di un problema con le autorizzazioni dei file (anche se pensavo che un'applicazione avesse automaticamente le autorizzazioni di scrittura nella sua directory personale). grazie per l'aiuto!
Estratto il

1
La tua app ha il permesso di scrivere nella sua directory personale - l'app della fotocamera (che scatta la foto) no. Tuttavia, è possibile spostare il file in onActivityResult, poiché è presente nell'app. In alternativa, potresti implementare un'attività della fotocamera da solo, ma sembra eccessivo.
Reto Meier,

La ripetizione dell'app della fotocamera sembra essere un approccio comune ma noioso ... Quando usiamo getDir ("images", MODE_WORLD_WRITEABLE), tale autorizzazione non si applica a nessun file che diciamo di creare in quella directory?
Estratto il

Non necessariamente. L'autorizzazione è per la cartella, non necessariamente per i file al suo interno.
Reto Meier,

Ho testato il codice con dispositivi Android con Android 2.2 e versioni successive e tutti hanno salvato l'immagine nel percorso fornito. Quindi immagino che questo dovrebbe essere un comportamento standard per ottenere immagini.
Janusz,

7

Per dare seguito al commento di Yenchi sopra, anche il pulsante OK non farà nulla se l'app della fotocamera non è in grado di scrivere nella directory in questione.

Ciò significa che non è possibile creare il file in un luogo scrivibile solo dalla propria applicazione (ad esempio, qualcosa sotto getCacheDir())Qualcosa sotto getExternalFilesDir()dovrebbe funzionare, tuttavia.

Sarebbe bello se l'app della fotocamera stampasse un messaggio di errore nei registri se non fosse in grado di scrivere nel EXTRA_OUTPUTpercorso specificato , ma non l'ho trovato.


4

per fare in modo che la fotocamera scriva su sdcard ma la conservi in ​​un nuovo album nell'app della galleria, lo uso come segue:

 File imageDirectory = new File("/sdcard/signifio");
          String path = imageDirectory.toString().toLowerCase();
           String name = imageDirectory.getName().toLowerCase();


            ContentValues values = new ContentValues(); 
            values.put(Media.TITLE, "Image"); 
            values.put(Images.Media.BUCKET_ID, path.hashCode());
            values.put(Images.Media.BUCKET_DISPLAY_NAME,name);

            values.put(Images.Media.MIME_TYPE, "image/jpeg");
            values.put(Media.DESCRIPTION, "Image capture by camera");
           values.put("_data", "/sdcard/signifio/1111.jpg");
         uri = getContentResolver().insert( Media.EXTERNAL_CONTENT_URI , values);
            Intent i = new Intent("android.media.action.IMAGE_CAPTURE"); 

            i.putExtra(MediaStore.EXTRA_OUTPUT, uri);

            startActivityForResult(i, 0); 

Tieni presente che dovrai generare un nome file univoco ogni volta e sostituire 1111.jpg che ho scritto. Questo è stato testato con Nexus One. l'uri viene dichiarato nella classe privata, quindi sul risultato dell'attività sono in grado di caricare l'immagine dall'uri su imageView per l'anteprima, se necessario.


da dove viene la colonna "_data"? non c'è una costante per questo?
Matthias,

3
ah, è developer.android.com/reference/android/provider/… Non dovresti davvero usare le stringhe magiche nel codice.
Matthias,

1

Ho avuto lo stesso problema e l'ho risolto con il seguente:

Il problema è che quando specifichi un file a cui solo la tua app ha accesso (ad es. Chiamando getFileStreamPath("file");)

Questo è il motivo per cui mi sono appena assicurato che il file dato esista davvero e che TUTTI abbiano accesso in scrittura ad esso.

Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
File outFile = getFileStreamPath(Config.IMAGE_FILENAME);
outFile.createNewFile();
outFile.setWritable(true, false);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT,Uri.fromFile(outFile));
startActivityForResult(intent, 2);

In questo modo, l'app della fotocamera ha accesso in scrittura all'URI specificato e il pulsante OK funziona bene :)


AndroidRuntime (emulatore 2.1) genera NoSuchMethodError: java.io.File.setWritable
jwadsack

1

Ti consiglio di seguire il post di formazione Android per scattare una foto. Mostrano in un esempio come scattare foto piccole e grandi. Puoi anche scaricare il codice sorgente da qui


0

Ho creato una semplice libreria che gestirà la scelta delle immagini da diverse fonti (Galleria, Fotocamera), forse la salverò in una posizione (SD-Card o memoria interna) e restituirà l'immagine, quindi per favore libera di usarla e migliorarla - Android-ImageChooser .


il tuo link non funziona più !! perché l'ho visto 2 settimane fa e volevo usarlo :(
Mohammad Ersan,

0

Il file deve essere scrivibile dalla fotocamera, come ha sottolineato Praveen .

Nel mio utilizzo ho voluto archiviare il file nella memoria interna. Ho fatto questo con:

Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (i.resolveActivity(getPackageManager()!=null)){
    try{
        cacheFile = createTempFile("img",".jpg",getCacheDir());
        cacheFile.setWritavle(true,false);
    }catch(IOException e){}
    if(cacheFile != null){
        i.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(cacheFile));
        startActivityForResult(i,REQUEST_IMAGE_CAPTURE);
    }
}

Ecco cacheFileun file globale utilizzato per fare riferimento al file che è stato scritto. Quindi nel metodo risultato l'intento restituito è nullo. Quindi il metodo per elaborare l'intento è simile al seguente:

protected void onActivityResult(int requestCode,int resultCode,Intent data){
    if(requestCode != RESULT_OK){
        return;
    }
    if(requestCode == REQUEST_IMAGE_CAPTURE){
        try{
            File output = getImageFile();
            if(output != null && cacheFile != null){
                copyFile(cacheFile,output);

                //Process image file stored at output

                cacheFile.delete();
                cacheFile=null;
            }
        }catch(IOException e){}
    }
}

Ecco getImageFile()un metodo di utilità per denominare e creare il file in cui l'immagine deve essere memorizzata ed copyFile()è un metodo per copiare un file.


0

Puoi usare questo codice

private Intent getCameraIntent() {

    PackageManager packageManager = mContext.getPackageManager();
    List<ApplicationInfo> list = packageManager.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
    Intent main = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
    List<ResolveInfo> launchables = packageManager.queryIntentActivities(main, 0);
    if (launchables.size() == 1)
        return packageManager.getLaunchIntentForPackage(launchables.get(0).activityInfo.packageName);
    else
        for (int n = 0; n < list.size(); n++) {
            if ((list.get(n).flags & ApplicationInfo.FLAG_SYSTEM) == 1) {
                Log.d("TAG", "Installed Applications  : " + list.get(n).loadLabel(packageManager).toString());
                Log.d("TAG", "package name  : " + list.get(n).packageName);
                String defaultCameraPackage = list.get(n).packageName;


                if (launchables.size() > 1)
                    for (int i = 0; i < launchables.size(); i++) {
                        if (defaultCameraPackage.equals(launchables.get(i).activityInfo.packageName)) {
                            return packageManager.getLaunchIntentForPackage(defaultCameraPackage);
                        }
                    }
            }
        }
    return null;
}

0

Risolvere questo problema con il codice risultato attività è molto semplice. Prova questo metodo

if (reqCode == RECORD_VIDEO) {
   if(resCode == RESULT_OK) {
       if (uri != null) {
           compress();
       }
    } else if(resCode == RESULT_CANCELED && data!=null){
       Toast.makeText(MainActivity.this,"No Video Recorded",Toast.LENGTH_SHORT).show();
   }
}

Il frammento di cui sopra incompleto non merita la semplicità che ha affermato. Sicuramente, ci sono complessità nascoste per il viaggiatore sconosciuto. Come la scansione per presenza o assenza del file. Come appassire è pubblicamente disponibile o privato per l'app. Come la necessità di un provider o ottenere una dimensione solo in miniatura. Queste sono lacune informative di cui il viaggiatore incauto deve essere consapevole.
truthadjustr,

0
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == RESULT_CANCELED)
    {
        //do not process data, I use return; to resume activity calling camera intent
        enter code here
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.