Intention Android ACTION_IMAGE_CAPTURE

163

Nous essayons d'utiliser l'application de caméra native pour permettre à l'utilisateur de prendre une nouvelle photo. Cela fonctionne très bien si nous omettons le EXTRA_OUTPUT extraet retournons la petite image Bitmap. Cependant, si nous avons putExtra(EXTRA_OUTPUT,...)l'intention avant de le démarrer, tout fonctionne jusqu'à ce que vous essayiez d'appuyer sur le bouton "Ok" dans l'application appareil photo. Le bouton "Ok" ne fait rien. L'application appareil photo reste ouverte et rien ne se verrouille. Nous pouvons l'annuler, mais le fichier n'est jamais écrit. Que devons-nous faire exactement pour pouvoir ACTION_IMAGE_CAPTUREécrire la photo prise dans un fichier?

Edit: Cela se fait via l' MediaStore.ACTION_IMAGE_CAPTUREintention, juste pour être clair

A dessiné
la source

Réponses:

144

il s'agit d'un bogue bien documenté dans certaines versions d'Android. Autrement dit, sur les versions Google Experience d'Android, la capture d'image ne fonctionne pas comme documenté. ce que j'ai généralement utilisé est quelque chose comme ça dans une classe d'utilitaires.

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);

}

puis lorsque je lance la capture d'image, je crée une intention qui vérifie le bogue.

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);

puis dans l'activité à laquelle je retourne, je fais différentes choses en fonction de l'appareil.

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();
            }
    }

cela vous évite d'avoir à écrire une nouvelle application pour appareil photo, mais ce code n'est pas non plus génial. les gros problèmes sont

  1. vous n'obtenez jamais d'images en taille réelle des appareils avec le bogue. vous obtenez des images de 512 pixels de large qui sont insérées dans le fournisseur de contenu d'image. sur les appareils sans bogue, tout fonctionne comme un document, vous obtenez une grande image normale.

  2. vous devez maintenir la liste. comme écrit, il est possible que les périphériques soient flashés avec une version d'Android (disons les versions de cyanogenmod ) qui a corrigé le bogue. si cela se produit, votre code plantera. le correctif consiste à utiliser toute l'empreinte digitale de l'appareil.

yanokwa
la source
21
sur Galaxy S: intent.getData () renvoie null, et de plus, l'application appareil photo sur Galaxy s insère une nouvelle photo dans la galerie par elle-même, mais aucun uri n'est retourné. Donc, si vous insérez une photo du fichier dans la galerie, il y aura une photo dupliquée.
Arseniy
1
hé mon appareil n'est pas répertorié ici et je l'ai implémenté par votre manière.Mais mon application plante toujours je ne connais pas la raison.Veuillez m'aider
Geetanjali
en utilisant ce code sur les appareils droid-x et sony xpheria. Les deux appareils renvoient la valeur d'intention comme null. droidx renvoie également le code de résultat à 0 tandis que xpheria renvoie le code de résultat à -1. Tout le monde sait pourquoi cela devrait être le cas.
rOrlig
5
Le lien vers le "bogue bien documenté" qui est au début de la réponse de yanokwa est incorrect. Ce bogue concerne l'appel de l'application caméra sans putExtra (EXTRA_OUTPUT, ...)
Frank Harper
code.google.com/p/android/issues/detail?id=1480 Eh bien, comment expliquez-vous cela alors?
Pencilcheck
50

Je sais que cela a déjà été répondu, mais je sais que beaucoup de gens ont trébuché à ce sujet, alors je vais ajouter un commentaire.

J'ai eu exactement le même problème sur mon Nexus One. Cela provenait du fichier qui n'existait pas sur le disque avant le démarrage de l'application appareil photo. Par conséquent, je me suis assuré que le fichier existait avant de démarrer l'application appareil photo. Voici un exemple de code que j'ai utilisé:

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();
        }

Je crée d'abord un nom de fichier unique (un peu) en utilisant un hachage MD5 et je le mets dans le dossier approprié. Je vérifie ensuite s'il existe (ne devrait pas, mais c'est une bonne pratique de vérifier quand même). S'il n'existe pas, j'obtiens le répertoire parent (un dossier) et crée la hiérarchie des dossiers jusqu'à celui-ci (donc si les dossiers menant à l'emplacement du fichier n'existent pas, ils le seront après cette ligne. Ensuite, après cela Je crée le fichier Une fois le fichier créé, j'obtiens l'Uri et le passe à l'intention, puis le bouton OK fonctionne comme prévu et tout est doré.

Maintenant, lorsque le bouton OK est enfoncé sur l'application appareil photo, le fichier sera présent à l'emplacement donné. Dans cet exemple, ce serait /sdcard/Android/data/com.example.myapp/files/234asdioue23498ad.jpg

Il n'est pas nécessaire de copier le fichier dans le "onActivityResult" comme indiqué ci-dessus.

Donn Felker
la source
13
Assurez-vous d'avoir <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />dans votre manifeste si vous utilisez une version plus récente qu'Android 1.5 - passé quelques heures à déboguer le matériel de la caméra et cela n'était pas inclus, donc mes sauvegardes échoueraient toujours.
swanson
Cela fonctionne bien pour moi, mais toutes les 10 images environ, le fichier vierge n'est pas écrasé et je me retrouve avec un fichier de 0 octet où l'image devrait être. C'est très ennuyeux et difficile à retrouver.
joey_g216
2
sur certains appareils, tels que le nexus 7 avec jelly bean, vous devez changer Environment.getExternalStorageDirectory (). getName () pour utiliser .getAbsolutePath () au lieu de .getName ()
Keith
35

J'ai suivi un certain nombre de stratégies de capture de photos et il semble toujours y avoir un cas, une plate-forme ou certains appareils, où certaines ou toutes les stratégies ci-dessus échoueront de manière inattendue. J'ai pu trouver une stratégie qui utilise le code de génération d'URI ci-dessous qui semble fonctionner dans la plupart sinon tous les cas.

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);

Pour contribuer davantage à la discussion et aider les nouveaux arrivants, j'ai créé un exemple / test d'application qui montre plusieurs stratégies différentes pour la mise en œuvre de la capture de photos. Les contributions d'autres implémentations sont définitivement encouragées à ajouter à la discussion.

https://github.com/deepwinter/AndroidCameraTester

hiver profond
la source
C'était génial. Je n'ai pas encore testé cela sur tous mes appareils, mais je suppose que vous l'avez déjà fait un peu. Il y a tellement de bruit sur ce sujet, et cette réponse particulière est si simple et claire. Fonctionne sur Nexus 5 jusqu'à présent, c'est là que j'ai reçu pour la première fois de nombreux rapports selon lesquels ACTION_IMAGE_CAPTURE ne fonctionnait pas sans EXTRA_OUTPUT. J'espère que cela fonctionne dans tous les domaines, mais jusqu'ici tout va bien. Thx
Rich
1
@deepwinter - Merci monsieur. Je m'arrachais les cheveux mais votre solution de résolution de contenu fonctionne bien sur tous les scénarios problématiques que j'avais.
Billy Coover le
Un peu tard à cette fête, mais c'est la seule solution qui fonctionne pour moi. Merci beaucoup!
StackJP
puis-je en quelque sorte récupérer le MediaStore.EXTRA_OUTPUT onActivityResultou dois-je vraiment m'en souvenir moi-même? Je devrais l'enregistrer de manière persistante pour que mon application s'en souvienne même si elle est détruite pendant qu'une photo est prise ...
prom85
Cela ne fonctionne pas sur le Samsung Galaxy Note 3 avec lolipop: ava.lang.NullPointerException: Tentative d'invocation de la méthode virtuelle 'java.lang.String android.net.Uri.getScheme ()' sur une référence d'objet nulle
Igor Janković
30

J'ai eu le même problème où le bouton OK de l'application appareil photo ne faisait rien, à la fois sur l'émulateur et sur le nexus one.

Le problème a disparu après avoir spécifié un nom de fichier sûr qui est sans espaces, sans caractères spéciaux, dans MediaStore.EXTRA_OUTPUT De plus, si vous spécifiez un fichier qui réside dans un répertoire qui n'a pas encore été créé, vous devez d'abord le créer. L'application Appareil photo ne fait pas mkdir pour vous.

Yenchi
la source
1
Et assurez-vous que vous utilisez mkdirs()plutôt que mkdir()si votre chemin a plus d'un niveau de répertoire et que vous voulez qu'ils soient tous créés ( mkdirne crée que le dernier, le répertoire «feuille»). J'ai eu quelques problèmes avec ça, et cette réponse m'a aidé.
Diana
pouvons-nous utiliser des horodatages simples? ou peut-il également faire des erreurs? les pleas me
taguent
@ user2247689 Je ne suis pas sûr du nom de fichier que vous avez créé par "horodatages simples", mais tant qu'ils ne contiennent pas de caractères spéciaux non autorisés par le format FAT sur la carte SD, ça devrait aller, je pense.
Yenchi
28

Le flux de travail que vous décrivez doit fonctionner comme vous l'avez décrit. Cela pourrait aider si vous pouviez nous montrer le code autour de la création de l'intention. En général, le modèle suivant devrait vous permettre de faire ce que vous essayez.

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
    }
  }
}

Notez que c'est l' activité de l' appareil photo qui créera et enregistrera le fichier, et qu'elle ne fait pas réellement partie de votre application, elle n'aura donc pas l'autorisation d'écriture sur votre dossier d'application. Pour enregistrer un fichier dans votre dossier d'application, créez un fichier temporaire sur la carte SD et déplacez-le vers votre dossier d'application dans le onActivityResultgestionnaire.

Reto Meier
la source
Cela / devrait / fonctionner, mais le bouton ok de l'application appareil photo ne fait rien. Nous l'avons fait fonctionner en écrivant sur la carte SD, donc je soupçonne qu'il s'agit d'un problème d'autorisations de fichiers (même si je pensais qu'une application avait automatiquement des autorisations d'écriture sur son répertoire personnel). Merci pour l'aide!
Tiré
1
Votre application dispose d'une autorisation d'écriture sur son répertoire personnel, contrairement à l'application appareil photo (qui prend la photo). Vous pouvez toutefois déplacer le fichier dans onActivityResult, car c'est dans votre application. Vous pouvez également implémenter une activité de caméra vous-même, mais cela semble exagéré.
Reto Meier
Refaire l'application de la caméra semble être une approche courante mais fastidieuse ... Lorsque nous utilisons getDir ("images", MODE_WORLD_WRITEABLE), cette autorisation ne s'applique-t-elle pas aux fichiers que nous lui demandons de créer dans ce répertoire?
Tiré
Pas nécessairement. L'autorisation concerne le dossier, pas nécessairement les fichiers qu'il contient.
Reto Meier
J'ai testé le code avec des appareils Android avec Android 2.2 et plus récent et tous ont enregistré l'image dans le chemin fourni. Donc je suppose que cela devrait être un comportement standard pour obtenir des images.
Janusz
7

Pour faire suite au commentaire de Yenchi ci-dessus, le bouton OK ne fera rien non plus si l'application appareil photo ne peut pas écrire dans le répertoire en question.

Cela signifie que vous ne pouvez pas créer le fichier dans un endroit uniquement accessible en écriture par votre application (par exemple, quelque chose sous getCacheDir())Quelque chose sousgetExternalFilesDir() devrait fonctionner, cependant.

Ce serait bien si l'application de la caméra imprimait un message d'erreur dans les journaux si elle ne pouvait pas écrire dans le EXTRA_OUTPUTchemin spécifié , mais je n'en ai pas trouvé.

Joe
la source
4

pour que la caméra écrive sur sdcard mais garde dans un nouvel album sur l'application de la galerie, j'utilise ceci:

 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); 

Veuillez noter que vous devrez générer un nom de fichier unique à chaque fois et remplacer le 1111.jpg que j'ai écrit. Cela a été testé avec nexus one. l'URI est déclaré dans la classe privée, donc sur le résultat de l'activité, je suis capable de charger l'image de l'URI vers imageView pour un aperçu si nécessaire.

nabulaer
la source
d'où vient la colonne "_data"? n'y a-t-il pas de constante pour cela?
Matthias
3
ah, c'est developer.android.com/reference/android/provider/ ... Vous ne devriez vraiment pas utiliser de chaînes magiques dans le code.
Matthias
1

J'ai eu le même problème et je l'ai résolu avec ce qui suit:

Le problème est que lorsque vous spécifiez un fichier auquel seule votre application a accès (par exemple en appelant getFileStreamPath("file"); )

C'est pourquoi je me suis juste assuré que le fichier donné existe vraiment et que TOUT LE MONDE y a accès en écriture.

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);

De cette façon, l'application appareil photo a un accès en écriture à l'Uri donné et le bouton OK fonctionne bien :)

Goddchen
la source
AndroidRuntime (émulateur 2.1) lance NoSuchMethodError: java.io.File.setWritable
jwadsack
1

Je vous recommande de suivre le post de formation Android pour capturer une photo. Ils montrent dans un exemple comment prendre de petites et de grandes photos. Vous pouvez également télécharger le code source ici

JuanVC
la source
0

J'ai créé une bibliothèque simple qui gérera le choix des images à partir de différentes sources (Galerie, Appareil photo), peut-être l'enregistrer dans un emplacement (carte SD ou mémoire interne) et renvoyer l'image, alors n'hésitez pas à l'utiliser et à l'améliorer - Android-ImageChooser .

svenkapudija
la source
votre lien ne fonctionne plus !! pourquoi, je l'ai vu il y a 2 semaines, et je voulais l'utiliser :(
Mohammad Ersan
0

Le fichier doit être accessible en écriture par la caméra, comme l'a souligné Praveen .

Dans mon utilisation, je voulais stocker le fichier dans la mémoire interne. J'ai fait ça avec:

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);
    }
}

Voici cacheFileun fichier global utilisé pour désigner le fichier qui est écrit. Ensuite, dans la méthode de résultat, l'intention renvoyée est nulle. Ensuite, la méthode de traitement de l'intention ressemble à:

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){}
    }
}

Voici getImageFile()une méthode utilitaire pour nommer et créer le fichier dans lequel l'image doit être stockée, et copyFile()une méthode pour copier un fichier.

Duncan
la source
0

Vous pouvez utiliser ce code

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;
}
Ngông Cuồng
la source
0

Il est très simple de résoudre ce problème avec le code de résultat d'activité Simple essayez cette méthode

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();
   }
}
Savita
la source
L'extrait incomplet ci-dessus ne mérite pas la simplicité qu'il revendiquait. Il y a sûrement des complexités cachées pour le voyageur inconnu. Tels que la recherche de la présence ou de l'absence du fichier. Comme le flétrissement, il est accessible au public ou privé de l'application. Tels que la nécessité d'un fournisseur ou l'obtention d'une taille de vignette uniquement. Ce sont des lacunes d'information dont le voyageur imprudent doit être conscient.
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
    }
}
Ajesh Gupta
la source