Android ACTION_IMAGE_CAPTURE Намерение

163

Мы пытаемся использовать собственное приложение камеры, чтобы позволить пользователю сделать новый снимок. Это прекрасно работает, если мы опускаем EXTRA_OUTPUT extraи возвращаем маленькое растровое изображение. Однако, если мы putExtra(EXTRA_OUTPUT,...)намерены до его запуска, все будет работать, пока вы не попробуете нажать кнопку «ОК» в приложении камеры. Кнопка «ОК» просто ничего не делает. Приложение камеры остается открытым и ничего не блокируется. Мы можем отменить его, но файл никогда не будет записан. Что именно мы должны сделать, ACTION_IMAGE_CAPTUREчтобы записать снимок, сделанный в файл?

Изменить: это делается с помощью MediaStore.ACTION_IMAGE_CAPTUREнамерения, просто чтобы быть ясно,

Нарисовался
источник

Ответы:

144

Это хорошо документированная ошибка в некоторых версиях Android. то есть, в сборках Google для Android, захват изображений не работает, как описано в документации. то, что я обычно использовал, это что-то вроде этого в классе утилит.

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

}

затем, когда я запускаю захват изображения, я создаю намерение, которое проверяет ошибку.

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

затем в деятельности, к которой я возвращаюсь, я делаю разные вещи в зависимости от устройства.

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

это избавляет вас от необходимости писать новое приложение для камеры, но этот код тоже не очень хорош. большие проблемы

  1. Вы никогда не получите полноразмерные изображения с устройств с ошибкой. вы получаете изображения шириной 512 пикселей, которые вставляются в контент-провайдер изображений. на устройствах без ошибок все работает как документ, вы получаете большую нормальную картинку.

  2. Вы должны поддерживать список. как написано, устройства могут быть прошиты версией Android (скажем , сборки cyanogenmod ), в которой исправлена ​​ошибка. если это произойдет, ваш код не работает. Исправление заключается в использовании всего отпечатка устройства.

yanokwa
источник
21
на Galaxy S: intent.getData () возвращает значение null, и, кроме того, приложение камеры на Galaxy s самостоятельно вставляет новую фотографию в галерею, но не возвращает uri. Поэтому, если вы вставите фотографию из файла в галерею, там будет дублированная фотография.
Арсений
1
эй, мое устройство не указано здесь, и я реализовал ваш путь. но мое приложение все еще падает, я не знаю причину. Пожалуйста, помогите мне
Geetanjali
используя этот код на устройствах droid-x и sony xpheria. Оба устройства возвращают значение намерения как ноль. также droidx возвращает код результата как 0, тогда как xpheria возвращает код результата как -1. Кто-нибудь знает, почему это должно быть так.
Рорлиг
5
Ссылка на «хорошо задокументированную ошибку», которая находится в начале ответа yanokwa, неверна. Эта ошибка связана с
Фрэнк Харпер
code.google.com/p/android/issues/detail?id=1480 Ну, как вы тогда это объясните?
Pencilcheck
50

Я знаю, что на этот вопрос уже отвечали, но я знаю, что многие люди запутались в этом, поэтому я собираюсь добавить комментарий.

У меня точно такая же проблема случилась на моем Nexus One. Это было из файла, не существующего на диске до запуска приложения камеры. Поэтому я позаботился о том, чтобы файл существовал до запуска приложения камеры. Вот пример кода, который я использовал:

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

Сначала я создаю уникальное (несколько) имя файла, используя хэш MD5, и помещаю его в соответствующую папку. Затем я проверяю, существует ли он (не должен, но в любом случае рекомендуется проверять). Если он не существует, я получаю родительский каталог (папку) и создаю иерархию папок до него (поэтому, если папки, ведущие к месту расположения файла, не существуют, они будут после этой строки. Затем после этого Я создаю файл. Как только файл создан, я получаю Uri и передаю его намерению, а затем кнопка ОК работает, как и ожидалось, и все это золотой.

Теперь, когда в приложении камеры нажата кнопка «ОК», файл будет находиться в указанном месте. В этом примере это будет /sdcard/Android/data/com.example.myapp/files/234asdioue23498ad.jpg

Нет необходимости копировать файл в «onActivityResult», как указано выше.

Донн Фелкер
источник
13
Убедитесь, что у вас есть <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />в манифесте, если вы используете более новую, чем Android 1.5 - потратил несколько часов на отладку камеры, и это не было включено, поэтому мои сохранения всегда будут неудачными.
Свонсон
Это хорошо работает для меня, но каждые примерно 10 изображений или около того, пустой файл не перезаписывается, и я получаю 0-байтовый файл, где должно быть изображение. Это очень раздражает и было трудно выследить.
joey_g216
2
на некоторых устройствах, таких как nexus 7 с желейными бобами, вам нужно изменить Environment.getExternalStorageDirectory (). getName (), чтобы использовать .getAbsolutePath () вместо .getName ()
Кит
35

Я прошел через несколько стратегий захвата фотографий, и всегда, кажется, был случай, платформа или определенные устройства, где некоторые или все вышеперечисленные стратегии потерпят неудачу неожиданным образом. Мне удалось найти стратегию, которая использует код генерации URI ниже, который, кажется, работает в большинстве, если не во всех случаях.

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

Чтобы внести свой вклад в обсуждение и помочь новичкам, я создал образец / тестовое приложение, которое показывает несколько различных стратегий реализации захвата фотографий. Вклады других реализаций определенно поощряются, чтобы добавить к обсуждению.

https://github.com/deepwinter/AndroidCameraTester

deepwinter
источник
Это было здорово. Я еще не проверил это на всех своих устройствах, но, полагаю, вы уже сделали это совсем немного. В этой теме так много шума, и этот конкретный ответ очень прост и понятен. До сих пор работает на Nexus 5, где я впервые получил много сообщений о том, что ACTION_IMAGE_CAPTURE не работает без EXTRA_OUTPUT. Я надеюсь, что это работает по всем направлениям, но пока все хорошо. Thx
Рич
1
@deepwinter - Спасибо, сэр. Я выдернул свои волосы, но ваше решение для работы с контентом хорошо работает на всех проблемных сценариях, которые у меня были.
Билли Кувер
В последнее время на этой вечеринке, но это было единственное решение, чтобы работать для меня. Огромное спасибо!
StackJP
могу ли я каким-то образом получить MediaStore.EXTRA_OUTPUT onActivityResultили я действительно должен сам это запомнить? Мне пришлось бы настойчиво сохранить его, чтобы мое приложение даже запомнило его, если оно будет уничтожено во время фотосъемки ...
prom85
Он не работает в Samsung Galaxy Note 3 с lolipop: ava.lang.NullPointerException: попытка вызвать виртуальный метод 'java.lang.String android.net.Uri.getScheme ()' для ссылки на пустой объект
Игорь Янкович
30

У меня была такая же проблема, когда кнопка OK в приложении камеры ничего не делала, как на эмуляторе, так и на Nexus One.

Проблема исчезла после указания безопасного имени файла без пробелов, без специальных символов. MediaStore.EXTRA_OUTPUT Кроме того, если вы указываете файл, который находится в каталоге, который еще не был создан, вы должны сначала создать его. Приложение камеры не делает MKDIR для вас.

Yenchi
источник
1
И убедитесь, что вы используете mkdirs()вместо, mkdir()если ваш путь имеет более одного уровня каталогов, и вы хотите, чтобы все они были созданы ( mkdirсоздает только последний, «листовой» каталог). У меня были некоторые проблемы с этим, и этот ответ помог мне.
Диана
мы можем использовать простые метки времени? или может также сделать некоторые ошибки ?? Просьба пометить меня, когда ответил :)
Rakeeb Rajbhandari
@ user2247689 Я не уверен в имени файла, который вы создали с помощью "простых отметок времени", но пока они не содержат специальных символов, не допускаемых форматом FAT на SD-карте, все должно быть в порядке, я думаю.
Йенчи
28

Рабочий процесс, который вы описываете, должен работать так, как вы его описали. Это может помочь, если вы покажете нам код создания Intent. В целом, следующий шаблон должен позволить вам делать то, что вы пытаетесь.

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

Обратите внимание, что именно Camera Activity будет создавать и сохранять файл, и на самом деле он не является частью вашего приложения, поэтому у него не будет разрешения на запись в папку вашего приложения. Чтобы сохранить файл в папке приложения, создайте временный файл на SD-карте и переместите его в папку приложения в onActivityResultобработчике.

Рето Мейер
источник
Это / должно / работать, но кнопка ОК в приложении камеры просто ничего не делает. У нас все получилось с записью на SD-карту, поэтому я подозреваю, что это проблема с правами доступа к файлам (хотя я думал, что приложение автоматически получило права на запись в свой личный каталог). Спасибо за помощь!
Дрю
1
Ваше приложение имеет разрешение на запись в свой личный каталог, а приложение камеры (которое делает снимок) - нет. Вы можете переместить файл в onActivityResult, хотя, как это в вашем приложении. В качестве альтернативы вы могли бы реализовать камеру Activity, но это кажется излишним.
Рето Мейер
Восстановление приложения камеры кажется распространенным, но утомительным подходом ... Когда мы используем getDir ("images", MODE_WORLD_WRITEABLE), не распространяется ли это разрешение на любой файл, который мы советуем ему создать в этом каталоге?
Дрю
Не обязательно. Разрешение для папки, не обязательно для файлов в ней.
Рето Мейер
Я протестировал код на устройствах Android с Android 2.2 и новее, и все они сохранили изображение по указанному пути. Поэтому я думаю, что это должно быть стандартным поведением для получения изображений.
Януш
7

Чтобы прокомментировать приведенный выше комментарий Йенчи, кнопка OK также ничего не сделает, если приложение камеры не может записать в каталог, о котором идет речь.

Это означает, что вы не можете создать файл в месте, доступном только для записи для вашего приложения (например, что-то в разделе « getCacheDir())Что-то под»).getExternalFilesDir() должно работать).

Было бы хорошо, если бы приложение камеры печатало сообщение об ошибке в журналах, если оно не могло записать по указанному EXTRA_OUTPUTпути, но я не нашел его.

Джо
источник
4

чтобы камера записывала на SDCard, но сохраняла новый альбом в приложении-галерее, я использую это:

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

Обратите внимание, что вам нужно будет каждый раз генерировать уникальное имя файла и заменять 1111.jpg, который я написал. Это было проверено с Nexus One. URI объявлен в закрытом классе, поэтому в результате активности я могу загрузить изображение из URI в imageView для предварительного просмотра, если это необходимо.

nabulaer
источник
откуда берется столбец "_data"? нет ли постоянной для этого?
Матиас
3
ах, это developer.android.com/reference/android/provider/… Вы действительно не должны использовать магические строки в коде.
Матиас
1

У меня была та же проблема, и я исправил ее следующим образом:

Проблема в том, что когда вы указываете файл, к которому имеет доступ только ваше приложение (например, при вызове getFileStreamPath("file");)

Вот почему я просто убедился, что данный файл действительно существует и что КАЖДЫЙ имеет к нему доступ для записи.

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

Таким образом, приложение камеры имеет доступ на запись к указанному Uri, и кнопка OK работает нормально :)

Goddchen
источник
AndroidRuntime (эмулятор 2.1) выдает ошибку NoSuchMethodError: java.io.File.setWritable
jwadsack
1

Я рекомендую вам следить за сообщением об обучении Android для захвата фотографии. Они показывают на примере, как делать маленькие и большие фотографии. Вы также можете скачать исходный код здесь

JuanVC
источник
0

Я создал простую библиотеку, которая будет управлять выбором изображений из разных источников (Галерея, Камера), возможно, сохранить его в каком-то месте (SD-карта или внутренняя память) и вернуть изображение обратно, поэтому, пожалуйста, используйте его и улучшайте - Android-ImageChooser ,

svenkapudija
источник
ваша ссылка больше не работает !! почему, я видел это 2 недели назад, и я хотел использовать это :(
Мохаммад Эрсан
0

Файл должен быть доступен для записи камерой, как указал Правин .

В моем использовании я хотел сохранить файл во внутренней памяти. Я сделал это с:

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

Вот cacheFileглобальный файл, используемый для ссылки на файл, который написан. Тогда в методе результата возвращенное намерение является нулем. Тогда метод обработки намерения выглядит так:

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

Вот getImageFile()служебный метод для присвоения имени и создания файла, в котором должно храниться изображение, и copyFile()метод для копирования файла.

Дункан
источник
0

Вы можете использовать этот код

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
источник
0

Решить эту проблему с помощью кода результата деятельности очень просто. Попробуйте этот метод.

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
источник
Приведенный выше неполный фрагмент не заслуживает той простоты, на которую он претендовал. Конечно, есть скрытые сложности для незнакомого путешественника. Например, сканирование на наличие или отсутствие файла. Например, засохло ли оно общедоступно или приватно для приложения. Например, необходимость в провайдере или получение только миниатюрного размера. Это информационные пробелы, о которых нужно знать неосторожным путешественникам.
правдаадюстр
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
    }
}
Ажеш Гупта
источник