Zamiar ACTION_IMAGE_CAPTURE Androida

163

Próbujemy użyć natywnej aplikacji aparatu, aby umożliwić użytkownikowi zrobienie nowego zdjęcia. Działa dobrze, jeśli pominiemy i zwrócimy EXTRA_OUTPUT extramały obraz bitmapy. Jeśli jednak putExtra(EXTRA_OUTPUT,...)zamierzamy go uruchomić, wszystko działa, dopóki nie spróbujesz wcisnąć przycisku „Ok” w aplikacji aparatu. Przycisk „Ok” po prostu nic nie robi. Aplikacja aparatu pozostaje otwarta i nic się nie blokuje. Możemy to anulować, ale plik nigdy nie zostanie zapisany. Co dokładnie musimy zrobić, aby uzyskaćACTION_IMAGE_CAPTURE aby zapisać zrobione zdjęcie do pliku?

Edycja: Odbywa się to przez MediaStore.ACTION_IMAGE_CAPTUREintencję, żeby było jasne

Rysował
źródło

Odpowiedzi:

144

jest to dobrze udokumentowany błąd w niektórych wersjach Androida. oznacza to, że w wersjach Androida z doświadczeniem Google przechwytywanie obrazu nie działa zgodnie z dokumentacją. to, czego zwykle używałem, to coś takiego w klasie narzędzi.

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

}

następnie, kiedy uruchamiam przechwytywanie obrazu, tworzę intencję, która sprawdza, czy nie ma błędu.

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

następnie w ramach czynności, do której wracam, robię różne rzeczy w zależności od urządzenia.

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

Dzięki temu nie musisz pisać nowej aplikacji aparatu, ale ten kod też nie jest świetny. są duże problemy

  1. nigdy nie otrzymujesz pełnowymiarowych obrazów z urządzeń z błędem. otrzymujesz obrazy o szerokości 512 pikseli, które są wstawiane do dostawcy zawartości obrazu. na urządzeniach bez błędu wszystko działa jak dokument, masz duży normalny obraz.

  2. musisz zachować tę listę. jak napisano, możliwe jest flashowanie urządzeń z wersją Androida (powiedzmy kompilacją CyanogenMod ), która ma naprawiony błąd. jeśli tak się stanie, twój kod ulegnie awarii. rozwiązaniem jest użycie całego odcisku palca urządzenia.

yanokwa
źródło
21
w Galaxy S: intent.getData () zwraca wartość null, a ponadto aplikacja aparatu w Galaxy S wstawia nowe zdjęcie do galerii, ale nie zwraca żadnego kodu URI. Więc jeśli wstawisz zdjęcie z pliku do galerii, będzie zduplikowane zdjęcie.
Arseniy
1
hej, mojego urządzenia nie ma na liście i wdrożyłem je zgodnie z twoją drogą. ale moja aplikacja nadal się zawiesza, nie znam powodu. proszę mi pomóc
Geetanjali
używając tego kodu na urządzeniach droid-x i sony xpheria. Oba urządzenia zwracają wartość intencji jako null. również droidx zwraca kod wyniku jako 0, podczas gdy xpheria zwraca kod wyniku jako -1. Każdy wie, dlaczego tak się dzieje.
rOrlig
5
Link do „dobrze udokumentowanego błędu”, który znajduje się na początku odpowiedzi yanokwa, jest nieprawidłowy. Że błąd jest o wywołanie aplikacji aparatu bez putExtra (EXTRA_OUTPUT, ...)
Frank Harper
code.google.com/p/android/issues/detail?id=1480 Jak więc to wyjaśnisz?
Pencilcheck
50

Wiem, że odpowiedź na to pytanie była już wcześniejsza, ale wiem, że wiele osób o to wpadło, więc dodam komentarz.

Dokładnie ten sam problem wystąpił na moim Nexusie One. Pochodziło to z pliku, który nie istniał na dysku przed uruchomieniem aplikacji aparatu. Dlatego upewniłem się, że plik istniejący przed uruchomieniem aplikacji aparatu. Oto przykładowy kod, którego użyłem:

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

Najpierw tworzę unikalną (nieco) nazwę pliku za pomocą skrótu MD5 i umieszczam ją w odpowiednim folderze. Następnie sprawdzam, czy istnieje (nie powinien, ale dobrą praktyką jest sprawdzenie i tak). Jeśli nie istnieje, otrzymuję katalog nadrzędny (folder) i tworzę do niego hierarchię folderów (dlatego jeśli foldery prowadzące do lokalizacji pliku nie istnieją, będą znajdować się po tym wierszu). Tworzę plik.Po utworzeniu pliku otrzymuję Uri i przekazuję go do celu, a następnie przycisk OK działa zgodnie z oczekiwaniami i wszystko jest złote.

Teraz po naciśnięciu przycisku OK w aplikacji aparatu plik będzie obecny w podanej lokalizacji. W tym przykładzie będzie to /sdcard/Android/data/com.example.myapp/files/234asdioue23498ad.jpg

Nie ma potrzeby kopiowania pliku w „onActivityResult”, jak napisano powyżej.

Donn Felker
źródło
13
Upewnij się, że masz <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />w swoim manifeście, jeśli używasz nowszego niż Android 1.5 - spędziłeś kilka godzin na debugowaniu elementów aparatu, ale nie było to uwzględnione, więc moje zapisy zawsze kończyły się niepowodzeniem.
Swanson
To działa dobrze dla mnie, ale co około 10 obrazów, pusty plik nie jest nadpisywany i otrzymuję plik 0-bajtowy, w którym powinien znajdować się obraz. To bardzo irytujące i trudne do wyśledzenia.
joey_g216
2
na niektórych urządzeniach, takich jak Nexus 7 z Jelly Bean, musisz zmienić Environment.getExternalStorageDirectory (). getName (), aby używał .getAbsolutePath () zamiast .getName ()
Keith
35

Przeszedłem przez wiele strategii robienia zdjęć i zawsze wydaje się, że istnieje przypadek, platforma lub określone urządzenia, w których niektóre lub wszystkie powyższe strategie zawiodą w nieoczekiwany sposób. Udało mi się znaleźć strategię, która wykorzystuje poniższy kod generowania URI, który wydaje się działać w większości, jeśli nie we wszystkich przypadkach.

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

Aby przyczynić się do dalszej dyskusji i pomóc nowicjuszom, stworzyłem przykładową / testową aplikację, która pokazuje kilka różnych strategii implementacji robienia zdjęć. Zdecydowanie zachęca się do dodania do dyskusji wkładów z innych implementacji.

https://github.com/deepwinter/AndroidCameraTester

głęboka zima
źródło
To było świetne. Jeszcze nie przetestowałem tego na wszystkich moich urządzeniach, ale zakładam, że zrobiłeś to już całkiem sporo. W tym temacie jest tyle hałasu, a ta konkretna odpowiedź jest tak prosta i przejrzysta. Jak dotąd działa na Nexusie 5, gdzie po raz pierwszy otrzymałem wiele raportów, że ACTION_IMAGE_CAPTURE nie działało bez EXTRA_OUTPUT. Mam nadzieję, że to działa we wszystkich przypadkach, ale jak na razie dobrze. Dzięki
Rich
1
@deepwinter - Dziękuję panu. Wyciągałem włosy, ale twoje rozwiązanie do rozpoznawania treści działa dobrze we wszystkich problematycznych scenariuszach, które miałem.
Billy Coover
Trochę późno na tej imprezie, ale to było jedyne rozwiązanie, które dla mnie zadziałało. Wielkie dzięki!
StackJP
czy mogę jakoś odzyskać MediaStore.EXTRA_OUTPUT w, onActivityResultczy naprawdę muszę sam go zapamiętać? Musiałbym wytrwale go zapisywać, aby moja aplikacja go zapamiętała, gdyby została zniszczona podczas
robienia
Nie działa na Samsung Galaxy Note 3 z Lolipop: ava.lang.NullPointerException: Próba wywołania wirtualnej metody 'java.lang.String android.net.Uri.getScheme ()' na zerowym odwołaniu do obiektu
Igor Janković
30

Miałem ten sam problem, w którym przycisk OK w aplikacji aparatu nic nie robił, zarówno na emulatorze, jak i na Nexusie 1.

Problem zniknął po określeniu bezpiecznej nazwy pliku bez spacji i znaków specjalnych. MediaStore.EXTRA_OUTPUT Ponadto, jeśli określasz plik znajdujący się w katalogu, który nie został jeszcze utworzony, musisz go najpierw utworzyć. Aplikacja aparatu nie wykonuje za Ciebie mkdir.

Yenchi
źródło
1
I upewnij się, że mkdirs()zamiast tego używasz, mkdir()jeśli twoja ścieżka ma więcej niż jeden poziom katalogu i chcesz, aby wszystkie zostały utworzone ( mkdirtworzy tylko ostatni, katalog „liść”). Miałem z tym pewne problemy i ta odpowiedź mi pomogła.
Diana
czy możemy użyć prostych znaczników czasu? czy też może popełnić jakieś błędy ?? proszę, oznacz mnie, gdy
odpowiesz
@ user2247689 Nie jestem pewien co do nazwy pliku, którą utworzyłeś za pomocą „prostych znaczników czasu”, ale dopóki nie zawierają one znaków specjalnych niedozwolonych przez format FAT na karcie SD, myślę, że powinno być w porządku.
Yenchi,
28

Opisywany przepływ pracy powinien działać tak, jak go opisałeś. Może pomóc, jeśli pokażesz nam kod wokół tworzenia intencji. Ogólnie rzecz biorąc, następujący wzór powinien pozwolić ci zrobić to, co próbujesz.

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

Zauważ, że to działanie aparatu będzie tworzyło i zapisywało plik, a nie jest to w rzeczywistości część twojej aplikacji, więc nie będzie miał uprawnień do zapisu w folderze aplikacji. Aby zapisać plik w folderze aplikacji, utwórz plik tymczasowy na karcie SD i przenieś go do folderu aplikacji w onActivityResultmodule obsługi.

Reto Meier
źródło
To / powinno / działać, ale przycisk OK aplikacji aparatu po prostu nic nie robi. Zrobiliśmy to, zapisując na karcie SD, więc podejrzewam, że jest to problem z uprawnieniami do plików (chociaż myślałem, że aplikacja automatycznie ma uprawnienia do zapisu w swoim katalogu osobistym). dzięki za pomoc!
Drew
1
Twoja aplikacja ma uprawnienia do zapisu w swoim katalogu osobistym - aplikacja aparatu (która robi zdjęcie) nie. Możesz jednak przenieść plik w onActivityResult, ponieważ znajduje się on w Twojej aplikacji. Alternatywnie możesz samodzielnie wdrożyć działanie kamery, ale wydaje się to przesada.
Reto Meier
Ponowne wykonanie aplikacji aparatu wydaje się być powszechnym, ale żmudnym podejściem ... Kiedy używamy getDir ("images", MODE_WORLD_WRITEABLE), czy to uprawnienie nie ma zastosowania do żadnego pliku, który kazaliśmy mu utworzyć w tym katalogu?
Drew
Niekoniecznie. Uprawnienie dotyczy folderu, a nie plików, które się w nim znajdują.
Reto Meier
Przetestowałem kod na urządzeniach z Androidem 2.2 i nowszym i wszystkie zapisały obraz w podanej ścieżce. Więc myślę, że powinno to być standardowe zachowanie, aby uzyskać obrazy.
Janusz
7

Aby odpowiedzieć na powyższy komentarz Yenchi, przycisk OK również nie zrobi nic, jeśli aplikacja aparatu nie może zapisać w danym katalogu.

Oznacza to, że nie możesz utworzyć pliku w miejscu, które może zapisywać tylko Twoja aplikacja (na przykład coś w sekcji getCacheDir())Coś poniżejgetExternalFilesDir() powinno jednak działać.

Byłoby miło, gdyby aplikacja aparatu wydrukowała komunikat o błędzie w dziennikach, gdyby nie mogła zapisać na określonej EXTRA_OUTPUTścieżce, ale jej nie znalazłem.

Joe
źródło
4

aby aparat zapisywał na sdcard, ale przechowywać w nowym albumie w aplikacji galerii, której używam:

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

Pamiętaj, że za każdym razem będziesz musiał wygenerować unikalną nazwę pliku i zastąpić napisany przeze mnie 1111.jpg. Zostało to przetestowane na Nexus One. uri jest zadeklarowany w klasie prywatnej, więc w wyniku działania mogę załadować obraz z uri do imageView w celu wyświetlenia podglądu, jeśli jest to konieczne.

nabulaer
źródło
skąd pochodzi kolumna „_data”? czy nie ma dla niego stałej?
Matthias
3
ah, to developer.android.com/reference/android/provider/… Naprawdę nie powinieneś używać magicznych ciągów w kodzie.
Matthias
1

Miałem ten sam problem i naprawiłem go w następujący sposób:

Problem polega na tym, że kiedy określisz plik, do którego ma dostęp tylko Twoja aplikacja (np. Przez wywołanie getFileStreamPath("file"); )

Dlatego właśnie upewniłem się, że dany plik naprawdę istnieje i KAŻDY ma do niego prawo zapisu.

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

W ten sposób aplikacja aparatu ma dostęp do zapisu do danego Uri, a przycisk OK działa dobrze :)

Goddchen
źródło
AndroidRuntime (emulator 2.1) zgłasza NoSuchMethodError: java.io.File.setWritable
jwadsack
1

Zalecam śledzenie postu szkoleniowego Androida w celu zrobienia zdjęcia. Pokazują na przykładzie, jak robić małe i duże zdjęcia. Możesz także pobrać kod źródłowy stąd

JuanVC
źródło
0

Stworzyłem prostą bibliotekę, która będzie zarządzać wyborem obrazów z różnych źródeł (Galeria, Aparat), być może zapisze je w jakimś miejscu (karta SD lub pamięć wewnętrzna) i zwróci obraz, więc proszę go swobodnie używać i ulepszać - Android-ImageChooser .

svenkapudija
źródło
Twój link już nie działa !! dlaczego widziałem to 2 tygodnie temu i chciałem go użyć :(
Mohammad Ersan
0

Jak zauważył Praveen , aparat musi mieć możliwość zapisu do pliku .

W moim przypadku chciałem przechowywać plik w pamięci wewnętrznej. Zrobiłem to z:

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

Oto cacheFileplik globalny używany w odniesieniu do zapisywanego pliku. Następnie w metodzie wynikowej zwrócona intencja ma wartość null. Wtedy metoda przetwarzania intencji wygląda następująco:

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

Oto getImageFile()metoda narzędziowa do nazwania i tworzenia pliku, w którym powinien być przechowywany obraz, i copyFile()jest to metoda kopiowania pliku.

Duncan
źródło
0

Możesz użyć tego kodu

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
źródło
0

Rozwiązanie tego problemu za pomocą kodu wyniku działania jest bardzo proste. Wypróbuj tę metodę

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
źródło
Powyższy niekompletny fragment kodu nie zasługuje na prostotę, o której twierdził. Z pewnością istnieją ukryte zawiłości dla nieznanego podróżnika. Na przykład skanowanie w poszukiwaniu obecności lub braku pliku. Takich jak uschnięcie jest publicznie dostępne lub prywatne dla aplikacji. Na przykład potrzeba dostawcy lub uzyskanie rozmiaru tylko miniatury. Są to luki informacyjne, o których nieostrożny podróżnik powinien wiedzieć.
trueadjustr
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
źródło