Intenção do Android ACTION_IMAGE_CAPTURE

163

Estamos tentando usar o aplicativo de câmera nativa para permitir que o usuário tire uma nova foto. Funciona muito bem se deixarmos de fora a EXTRA_OUTPUT extrae retornar a pequena imagem Bitmap. No entanto, se tivermos putExtra(EXTRA_OUTPUT,...)a intenção antes de iniciá-lo, tudo funcionará até você tentar pressionar o botão "Ok" no aplicativo da câmera. O botão "Ok" simplesmente não faz nada. O aplicativo da câmera permanece aberto e nada trava. Podemos cancelar, mas o arquivo nunca é gravado. O que exatamente precisamos fazer ACTION_IMAGE_CAPTUREpara gravar a foto tirada em um arquivo?

Edit: Isso é feito através da MediaStore.ACTION_IMAGE_CAPTUREintenção, apenas para ficar claro

Desenhou
fonte

Respostas:

144

Este é um bug bem documentado em algumas versões do Android. isto é, na experiência do Google com o Android, a captura de imagens não funciona conforme documentado. o que eu geralmente usei é algo assim em uma classe de utilitários.

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

}

então, quando inicio a captura de imagem, crio uma intenção que verifica o 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);

depois, nas atividades em que retorno, faço coisas diferentes com base no 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();
            }
    }

isso poupa a necessidade de escrever um novo aplicativo de câmera, mas esse código também não é ótimo. os grandes problemas são

  1. você nunca obtém imagens em tamanho real dos dispositivos com o bug. você obtém fotos com 512px de largura inseridas no provedor de conteúdo de imagens. em dispositivos sem o bug, tudo funciona como documento, você obtém uma imagem normal grande.

  2. você tem que manter a lista. como está escrito, é possível que os dispositivos sejam atualizados com uma versão do android (digamos , compilações do cyanogenmod ) que tenha o bug corrigido. se isso acontecer, seu código falhará. a correção é usar toda a impressão digital do dispositivo.

yanokwa
fonte
21
no Galaxy S: intent.getData () retorna um aplicativo de câmera nulo e, além disso, o Galaxy s insere uma nova foto na galeria por si só, mas nenhum uri retornou. Portanto, se você inserir uma foto do arquivo na galeria, haverá uma foto duplicada.
precisa
1
hey meu dispositivo não está aqui e eu tenho implementado por ur way.but minha candidatura ainda falha i don KNW a ajuda reason.please me
Geetanjali
usando esse código em dispositivos droid-x e sony xpheria. Ambos os dispositivos retornam o valor da intenção como nulo. também droidx retorna resultcode como 0, enquanto xpheria retorna resultcode como -1. Alguém sabe por que esse deveria ser o caso.
rOrlig
5
O link para o "erro bem documentado" que está no início da resposta do yanokwa está incorreto. Esse bug é sobre ligar o aplicativo da câmera sem putExtra (EXTRA_OUTPUT, ...)
Frank Harper
code.google.com/p/android/issues/detail?id=1480 Bem, como você explica isso então?
Pencilcheck
50

Sei que isso já foi respondido antes, mas sei que muitas pessoas se incomodam com isso, então vou adicionar um comentário.

Eu tive exatamente o mesmo problema no meu Nexus One. Era do arquivo que não existia no disco antes do aplicativo da câmera ser iniciado. Portanto, verifiquei se o arquivo existente antes iniciava o aplicativo da câmera. Aqui está um código de exemplo que eu usei:

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

Primeiro, crio um nome de arquivo único (um pouco) usando um hash MD5 e o coloco na pasta apropriada. Depois, verifico se ele existe (não deveria, mas é uma boa prática verificar de qualquer maneira). Se ele não existir, eu obtenho o diretório pai (uma pasta) e crio a hierarquia de pastas até ele (portanto, se as pastas que antecedem o local do arquivo não existirem, elas seguirão esta linha. Eu crio o arquivo.Quando o arquivo é criado, pego o Uri e o passo para a intenção e, em seguida, o botão OK funciona como esperado e tudo fica dourado.

Agora, quando o botão OK for pressionado no aplicativo da câmera, o arquivo estará presente no local especificado. Neste exemplo, seria /sdcard/Android/data/com.example.myapp/files/234asdioue23498ad.jpg

Não há necessidade de copiar o arquivo no "onActivityResult", conforme publicado acima.

Donn Felker
fonte
13
Verifique se você tem <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />no seu manifesto se estiver usando o Android 1.5 mais recente - passei algumas horas depurando o material da câmera e isso não foi incluído, para que minhas proteções sempre falhem.
swanson
Isso funciona bem para mim, mas a cada 10 imagens aproximadamente, o arquivo em branco não é substituído e eu termino com um arquivo de 0 byte onde a imagem deve estar. É muito chato e tem sido difícil de rastrear.
joey_g216
2
em alguns dispositivos, como o Nexus 7 com feijão de geléia, você precisa mudar Environment.getExternalStorageDirectory () getName () para usar .getAbsolutePath () em vez de .getName ().
Keith
35

Passei por várias estratégias de captura de fotos e sempre parece haver um caso, uma plataforma ou certos dispositivos, em que algumas ou todas as estratégias acima falham de maneiras inesperadas. Consegui encontrar uma estratégia que usa o código de geração de URI abaixo, que parece funcionar na maioria, se não em todos os casos.

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

Para contribuir ainda mais com a discussão e ajudar os recém-chegados, criei um aplicativo de amostra / teste que mostra várias estratégias diferentes para a implementação da captura de fotos. Contribuições de outras implementações são definitivamente encorajadas a adicionar à discussão.

https://github.com/deepwinter/AndroidCameraTester

inverno profundo
fonte
Isso foi ótimo. Ainda não testei isso em todos os meus dispositivos, mas suponho que você já o tenha feito um pouco. Há muito barulho nesse tópico, e essa resposta em particular é muito simples e limpa. Até agora, funciona no Nexus 5, onde recebi muitos relatórios de que ACTION_IMAGE_CAPTURE não estava funcionando sem EXTRA_OUTPUT. Espero que isso funcione em todos os aspectos, mas até agora tudo bem. Thx
Rich
1
@deepwinter - Obrigado senhor. Eu estava arrancando os cabelos, mas sua solução de resolução de conteúdo funciona bem em todos os cenários problemáticos que tive.
Billy Coover
Meio tarde para esta festa, mas esta tem sido a única solução para trabalhar para mim. Muitíssimo obrigado!
StackJP
posso recuperar o MediaStore.EXTRA_OUTPUT de alguma forma onActivityResultou preciso mesmo me lembrar? Eu teria que persistantly salvá-lo para que meu aplicativo ainda se lembra-lo se ele é destruído, enquanto uma foto é tirada ...
prom85
Ele não funciona no samsung galaxy note 3 com lolipop: ava.lang.NullPointerException: tentativa de invocar o método virtual 'java.lang.String android.net.Uri.getScheme ()' em uma referência a objeto nulo
Igor Janković
30

Eu tive o mesmo problema em que o botão OK no aplicativo da câmera não fez nada, tanto no emulador quanto no nexus one.

O problema foi resolvido após a especificação de um nome de arquivo seguro, sem espaços em branco, sem caracteres especiais, em MediaStore.EXTRA_OUTPUT Além disso, se você estiver especificando um arquivo que reside em um diretório que ainda não foi criado, será necessário criá-lo primeiro. O aplicativo da câmera não faz mkdir para você.

Yenchi
fonte
1
E certifique-se de usar em mkdirs()vez de mkdir()se o seu caminho tiver mais de um nível de diretório e desejar que todos eles sejam criados ( mkdirapenas cria o último, o diretório 'leaf'). Eu tive alguns problemas com isso, e essa resposta me ajudou.
Diana
podemos usar timestamps simples? ou pode também cometer alguns erros ?? apelos fazer tag mim quando respondeu :)
Rakeeb Rajbhandari
@ user2247689 Não tenho certeza sobre o nome do arquivo que você criou por "timestamps simples", mas contanto que eles não contenham caracteres especiais não permitidos pelo formato FAT no cartão SD, tudo bem, acho.
#
28

O fluxo de trabalho que você descreve deve funcionar como você o descreveu. Pode ajudar se você puder nos mostrar o código em torno da criação do Intent. Em geral, o padrão a seguir deve permitir que você faça o que está tentando.

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

Observe que é a Atividade da câmera que criará e salvará o arquivo e, na verdade, não faz parte do seu aplicativo, portanto, não haverá permissão de gravação na pasta do aplicativo. Para salvar um arquivo na pasta do aplicativo, crie um arquivo temporário no cartão SD e mova-o para a pasta do aplicativo no onActivityResultmanipulador.

Reto Meier
fonte
Isso deve / deve funcionar, mas o botão ok do aplicativo da câmera simplesmente não faz nada. Conseguimos fazê-lo gravando no cartão SD, então suspeito que seja um problema de permissão de arquivo (embora eu pensasse que um aplicativo tivesse permissões de gravação automaticamente no diretório pessoal). Obrigado pela ajuda!
Tirou
1
Seu aplicativo tem permissão de gravação no diretório pessoal - o aplicativo da câmera (que está tirando a foto) não. Você pode mover o arquivo no onActivityResult, pois está dentro do seu aplicativo. Como alternativa, você mesmo pode implementar uma Atividade da câmera, mas isso parece um exagero.
Reto Meier
Refazer o aplicativo da câmera parece ser uma abordagem comum, mas tediosa ... Quando usamos getDir ("imagens", MODE_WORLD_WRITEABLE), essa permissão não se aplica a nenhum arquivo que solicitamos que ele crie nesse diretório?
Tirou
Não necessariamente. A permissão é para a pasta, não necessariamente para os arquivos dentro dela.
Reto Meier
Testei o código em dispositivos Android com Android 2.2 e mais recente e todos eles salvaram a imagem no caminho fornecido. Então, acho que esse deve ser um comportamento padrão para obter imagens.
Janusz
7

Para acompanhar o comentário de Yenchi acima, o botão OK também não fará nada se o aplicativo da câmera não puder gravar no diretório em questão.

Isso significa que você não pode criar o arquivo em um local que possa ser gravado apenas pelo seu aplicativo (por exemplo, algo em getCacheDir())Algo sob getExternalFilesDir()deve funcionar, no entanto.

Seria bom se o aplicativo da câmera imprimisse uma mensagem de erro nos logs se não pudesse gravar no EXTRA_OUTPUTcaminho especificado , mas não encontrei uma.

Joe
fonte
4

para que a câmera grave no sdcard, mas mantenha um novo álbum no aplicativo da galeria, eu uso o seguinte:

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

Observe que você sempre precisará gerar um nome de arquivo exclusivo e substituir o 1111.jpg que escrevi. Isso foi testado com o nexus one. o uri é declarado na classe privada, portanto, no resultado da atividade, sou capaz de carregar a imagem do uri no imageView para visualização, se necessário.

nabulaer
fonte
De onde vem a coluna "_data"? não há constante para isso?
Matthias
3
ah, é developer.android.com/reference/android/provider/… Você realmente não deve usar cordas mágicas no código.
Matthias
1

Eu tive o mesmo problema e o corrigi com o seguinte:

O problema é que, quando você especifica um arquivo ao qual somente seu aplicativo tem acesso (por exemplo, ligando getFileStreamPath("file");)

É por isso que eu apenas certifiquei-me de que o arquivo fornecido realmente exista e que TODOS tenham acesso de gravação a ele.

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

Dessa forma, o aplicativo da câmera tem acesso de gravação ao Uri fornecido e o botão OK funciona bem :)

Goddchen
fonte
AndroidRuntime (emulador 2.1) lança NoSuchMethodError: java.io.File.setWritable
jwadsack
1

Eu recomendo que você siga o post de treinamento do Android para capturar uma foto. Eles mostram em um exemplo como tirar fotos pequenas e grandes. Você também pode baixar o código fonte aqui

JuanVC
fonte
0

Eu criei uma biblioteca simples que gerenciará a escolha de imagens de diferentes fontes (Galeria, Câmera), talvez salve-a em algum local (cartão SD ou memória interna) e retorne a imagem de volta, então fique à vontade para usá-la e melhorá-la - Android-ImageChooser .

svenkapudija
fonte
seu link não está mais funcionando !! por que, eu vi isso há 2 semanas, e eu queria usá-lo :(
Mohammad Ersan
0

O arquivo precisa ser gravável pela câmera, como Praveen apontou.

No meu uso, eu queria armazenar o arquivo no armazenamento interno. Eu fiz isso com:

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

Aqui cacheFileestá um arquivo global usado para se referir ao arquivo que está gravado. Em seguida, no método de resultado, a intenção retornada é nula. Em seguida, o método para processar a intenção é semelhante a:

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

Aqui getImageFile()está um método utilitário para nomear e criar o arquivo no qual a imagem deve ser armazenada e copyFile()é um método para copiar um arquivo.

Duncan
fonte
0

Você pode usar este código

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
fonte
0

É muito simples resolver este problema com o Código de Resultado da Atividade Simples, tente este método

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
fonte
O snippet incompleto acima não merece a simplicidade que reivindicou. Certamente, existem complexidades ocultas para o viajante desconhecido. Como a verificação de presença ou ausência do arquivo. Como murchar, está disponível ao público ou privado para o aplicativo. Como a necessidade de um provedor ou a obtenção de um tamanho apenas de miniatura. Essas são lacunas de informação que o viajante incauto precisa estar ciente.
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
fonte