Uwaga: ta strona dotyczy klasy Camera, która została wycofana. Zalecamy korzystanie z CameraX lub, w przypadku określonych zastosowań, z Camera2. Zarówno CameraX, jak i Camera2 obsługują Androida 5.0 (poziom interfejsu API 21) i nowsze.
Z tej lekcji dowiesz się, jak zrobić zdjęcie, powierzając to zadanie innej aplikacji do zdjęć na urządzeniu. (Jeśli wolisz tworzyć własne funkcje kamery, zapoznaj się z artykułem Sterowanie kamerą).
Załóżmy, że wdrażasz usługę pogodową, która tworzy globalną mapę pogodową przez łączenie zdjęć nieba zrobionych przez urządzenia z Twoją aplikacją kliencką. Integracja zdjęć to tylko niewielka część aplikacji. Chcesz robić zdjęcia bez zbędnych komplikacji, a nie wymyślać nowego aparatu. Na szczęście na większości urządzeń z Androidem jest już zainstalowana co najmniej 1 aplikacja aparatu. Z tej lekcji dowiesz się, jak zrobić zdjęcie.
Prośba o funkcję aparatu
Jeśli podstawową funkcją aplikacji jest robienie zdjęć, ogranicz widoczność aplikacji w Google Play do urządzeń z kamerą. Aby poinformować, że aplikacja wymaga kamery, umieść tag <uses-feature>
w pliku manifestu:
<manifest ... > <uses-feature android:name="android.hardware.camera" android:required="true" /> ... </manifest>
Jeśli aplikacja używa aparatu, ale nie wymaga go do działania, ustaw android:required
na false
. W ten sposób Google Play zezwoli na pobieranie aplikacji z urządzeń bez aparatu. Następnie Twoim zadaniem jest sprawdzenie dostępności kamery w czasie wykonywania kodu przez wywołanie funkcji hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
.
Jeśli kamera jest niedostępna, wyłącz funkcje kamery.
Pobieranie miniatury
Jeśli samo zrobienie zdjęcia nie jest szczytem ambicji Twojej aplikacji, prawdopodobnie chcesz odzyskać obraz z aplikacji aparatu i zrobić z nim coś użytecznego.
Aplikacja Aparat na Androida koduje zdjęcie w zwrotnym Intent
dostarczonym do onActivityResult()
jako mały Bitmap
w dodatkach pod kluczem "data"
. Poniższy kod pobiera to zdjęcie i wyświetla je w elemencie ImageView
.
Kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { val imageBitmap = data.extras.get("data") as Bitmap imageView.setImageBitmap(imageBitmap) } }
Java
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { Bundle extras = data.getExtras(); Bitmap imageBitmap = (Bitmap) extras.get("data"); imageView.setImageBitmap(imageBitmap); } }
Uwaga: ta miniatura z "data"
może być odpowiednia na ikonę, ale nie na wiele więcej. Praca z pełnowymiarowymi obrazami wymaga nieco więcej wysiłku.
Zapisywanie zdjęcia w pełnym rozmiarze
Aplikacja Aparat na Androidzie zapisuje zdjęcie w pełnej wielkości, jeśli podasz jej plik do zapisu. Musisz podać pełną nazwę pliku, w którym aplikacja aparatu ma zapisać zdjęcie.
Ogólnie wszystkie zdjęcia zrobione przez użytkownika za pomocą aparatu urządzenia powinny być zapisywane na urządzeniu w publicznej pamięci zewnętrznej, aby były dostępne dla wszystkich aplikacji. Prawidłowy katalog dla udostępnionych zdjęć jest udostępniany przez getExternalStoragePublicDirectory()
za pomocą argumentu DIRECTORY_PICTURES
. Katalog udostępniony za pomocą tej metody jest wspólny dla wszystkich aplikacji. W przypadku Androida 9 (poziom API 28) i starszych odczytywanie i zapisywanie w tym katalogu wymaga odpowiednio uprawnień READ_EXTERNAL_STORAGE
i WRITE_EXTERNAL_STORAGE
:
<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
W Androidzie 10 (poziom interfejsu API 29) i wyższych odpowiednim katalogiem do udostępniania zdjęć jest tabela MediaStore.Images
.
Nie musisz deklarować żadnych uprawnień dostępu do pamięci, o ile aplikacja potrzebuje dostępu tylko do zdjęć zrobionych przez użytkownika za pomocą aplikacji.
Jeśli jednak chcesz, aby zdjęcia były dostępne tylko dla Twojej aplikacji, możesz użyć katalogu udostępnianego przez Context.getExternalFilesDir()
.
W Androidzie 4.3 lub starszym zapisywanie w tym katalogu wymaga też uprawnienia WRITE_EXTERNAL_STORAGE
. Od Androida 4.4 to uprawnienie nie jest już wymagane, ponieważ inne aplikacje nie mają dostępu do tego katalogu. Możesz więc zadeklarować, że uprawnienie powinno być wymagane tylko w wersjach Androida niższych niż 4.4, dodając atrybut maxSdkVersion
:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> ... </manifest>
Uwaga: pliki zapisane w katalogach udostępnionych przez getExternalFilesDir()
lub getFilesDir()
są usuwane, gdy użytkownik odinstaluje aplikację.
Gdy zdecydujesz się na katalog dla pliku, musisz utworzyć nazwę pliku odporną na kolizje.
Możesz też zapisać ścieżkę w zmiennej członkowskiej, aby użyć jej później. Oto przykładowe rozwiązanie
w metodie, która zwraca unikalną nazwę pliku dla nowego zdjęcia za pomocą sygnatury daty i godziny.
(w tym przykładzie zakładamy, że wywołujesz metodę z poziomu Context
).
Kotlin
lateinit var currentPhotoPath: String @Throws(IOException::class) private fun createImageFile(): File { // Create an image file name val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date()) val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES) return File.createTempFile( "JPEG_${timeStamp}_", /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ).apply { // Save a file: path for use with ACTION_VIEW intents currentPhotoPath = absolutePath } }
Java
String currentPhotoPath; private File createImageFile() throws IOException { // Create an image file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File image = File.createTempFile( imageFileName, /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ); // Save a file: path for use with ACTION_VIEW intents currentPhotoPath = image.getAbsolutePath(); return image; }
Dzięki tej metodzie możesz teraz tworzyć pliki zdjęć i wywoływać je w ten sposób:Intent
Kotlin
private fun dispatchTakePictureIntent() { Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -> // Ensure that there's a camera activity to handle the intent takePictureIntent.resolveActivity(packageManager)?.also { // Create the File where the photo should go val photoFile: File? = try { createImageFile() } catch (ex: IOException) { // Error occurred while creating the File ... null } // Continue only if the File was successfully created photoFile?.also { val photoURI: Uri = FileProvider.getUriForFile( this, "com.example.android.fileprovider", it ) takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI) startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE) } } } }
Java
private void dispatchTakePictureIntent() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // Ensure that there's a camera activity to handle the intent if (takePictureIntent.resolveActivity(getPackageManager()) != null) { // Create the File where the photo should go File photoFile = null; try { photoFile = createImageFile(); } catch (IOException ex) { // Error occurred while creating the File ... } // Continue only if the File was successfully created if (photoFile != null) { Uri photoURI = FileProvider.getUriForFile(this, "com.example.android.fileprovider", photoFile); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE); } } }
Uwaga: używamy funkcji getUriForFile(Context, String, File)
, która zwraca identyfikator URI content://
. W przypadku nowszych aplikacji kierowanych na Androida 7.0 (poziom interfejsu API 24) lub nowszego przekazywanie identyfikatora URI file://
przez granicę pakietu powoduje błądFileUriExposedException
.
Dlatego przedstawiamy teraz bardziej ogólny sposób przechowywania obrazów za pomocą FileProvider
.
Teraz musisz skonfigurować FileProvider
. W manifeście aplikacji dodaj dostawcę:
<application> ... <provider android:name="androidx.core.content.FileProvider" android:authorities="com.example.android.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data> </provider> ... </application>
Upewnij się, że ciąg znaków authorities pasuje do drugiego argumentu funkcji getUriForFile(Context, String, File)
.
W sekcji metadanych definicji dostawcy możesz zobaczyć, że dostawca oczekuje, że ścieżki kwalifikujące się do użycia zostaną skonfigurowane w dedykowanym pliku zasobów
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="my_images" path="Pictures" /> </paths>
Komponent ścieżki odpowiada ścieżce zwracanej przez funkcję getExternalFilesDir()
, gdy zostanie wywołana z parametrem Environment.DIRECTORY_PICTURES
.
Pamiętaj, aby zastąpić com.example.package.name
rzeczywistą nazwą pakietu aplikacji. Zapoznaj się też z dokumentacją FileProvider
, aby uzyskać obszerny opis wskaźników ścieżki, których możesz używać zamiast external-path
.
Dodawanie zdjęcia do galerii
Gdy tworzysz zdjęcie za pomocą intencji, wiesz, gdzie się ono znajduje, ponieważ na początku określasz, gdzie je zapisać. W przypadku innych użytkowników najłatwiejszym sposobem udostępnienia zdjęcia jest udostępnienie go za pomocą dostawcy multimediów systemu.
Uwaga: jeśli zdjęcie zostało zapisane w katalogu udostępnionym przez getExternalFilesDir()
, skaner multimediów nie ma do niego dostępu, ponieważ jest ono prywatne dla Twojej aplikacji.
Ten przykładowy sposób pokazuje, jak wywołać skaner multimediów systemu, aby dodać zdjęcie do bazy danych dostawcy multimediów, dzięki czemu będzie ono dostępne w aplikacji Galeria na Androidzie oraz w innych aplikacjach.
Kotlin
private fun galleryAddPic() { Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent -> val f = File(currentPhotoPath) mediaScanIntent.data = Uri.fromFile(f) sendBroadcast(mediaScanIntent) } }
Java
private void galleryAddPic() { Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); File f = new File(currentPhotoPath); Uri contentUri = Uri.fromFile(f); mediaScanIntent.setData(contentUri); this.sendBroadcast(mediaScanIntent); }
Dekodowanie powiększonego obrazu
Zarządzanie wieloma obrazami w pełnej wielkości może być trudne, gdy masz ograniczoną pamięć. Jeśli okaże się, że po wyświetleniu zaledwie kilku obrazów aplikacji brakuje pamięci, możesz znacznie zmniejszyć ilość używanej dynamicznej pamięci sterty, rozszerzając plik JPEG do tablicy pamięci, która jest już dostosowana do rozmiaru widoku docelowego. Poniżej przedstawiamy przykładową metodę, która pokazuje tę technikę.
Kotlin
private fun setPic() { // Get the dimensions of the View val targetW: Int = imageView.width val targetH: Int = imageView.height val bmOptions = BitmapFactory.Options().apply { // Get the dimensions of the bitmap inJustDecodeBounds = true BitmapFactory.decodeFile(currentPhotoPath, bmOptions) val photoW: Int = outWidth val photoH: Int = outHeight // Determine how much to scale down the image val scaleFactor: Int = Math.max(1, Math.min(photoW / targetW, photoH / targetH)) // Decode the image file into a Bitmap sized to fill the View inJustDecodeBounds = false inSampleSize = scaleFactor inPurgeable = true } BitmapFactory.decodeFile(currentPhotoPath, bmOptions)?.also { bitmap -> imageView.setImageBitmap(bitmap) } }
Java
private void setPic() { // Get the dimensions of the View int targetW = imageView.getWidth(); int targetH = imageView.getHeight(); // Get the dimensions of the bitmap BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; BitmapFactory.decodeFile(currentPhotoPath, bmOptions); int photoW = bmOptions.outWidth; int photoH = bmOptions.outHeight; // Determine how much to scale down the image int scaleFactor = Math.max(1, Math.min(photoW/targetW, photoH/targetH)); // Decode the image file into a Bitmap sized to fill the View bmOptions.inJustDecodeBounds = false; bmOptions.inSampleSize = scaleFactor; bmOptions.inPurgeable = true; Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath, bmOptions); imageView.setImageBitmap(bitmap); }