Uwaga: ta strona dotyczy wycofanej klasy Camera. Zalecamy korzystanie z Aparatu X lub – w określonych przypadkach – z Aparatu 2. Aparaty CameraX i Aparat 2 obsługują Androida 5.0 (poziom interfejsu API 21) i nowsze wersje.
Z tej lekcji dowiesz się, jak zrobić zdjęcie przez przekazanie pracy do innej aplikacji aparatu urządzenia. (Jeśli wolisz utworzyć własną funkcję kamery, zobacz Sterowanie kamerą).
Załóżmy, że wdrażasz usługę pogodową, która tworzy globalną mapę pogodową przez łączenie zdjęć nieba z urządzeń z Twoją aplikacją kliencką. Integracja zdjęć to tylko mała część aplikacji. Chcesz robić zdjęcia bez zbędnych komplikacji, a nie wymyślać nowego aparatu. Na szczęście większość urządzeń z Androidem ma już co najmniej jedną aplikację aparatu. Zainstalowano. Z tej lekcji dowiesz się, jak zrobić zdjęcie.
Wysyłanie prośby 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 Twoja aplikacja używa kamery do działania, ale jej nie wymaga, ustaw
android:required
do false
. Jeśli to zrobisz, Google Play zezwoli na urządzenia
bez kamery, aby pobrać aplikację. 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 nie jest dostępna, wyłącz funkcje kamery.
Wybierz miniaturę
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.
Aparat w Androidzie koduje w odpowiedzi zdjęcie.
Dostarczono Intent
do
onActivityResult()
jako małe Bitmap
w dodatkach.
w kluczu "data"
. Poniższy kod umożliwia pobranie tego obrazu i wyświetlenie go w
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
Aparat w Androidzie zapisuje zdjęcie w pełnym rozmiarze, jeśli dodasz do niego plik. Ty musi zawierać pełną nazwę pliku, w którym aplikacja aparatu ma zapisać zdjęcie.
Ogólnie wszystkie zdjęcia, które użytkownik robi aparatem 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 przy użyciu tej metody jest wspólny dla wszystkich aplikacji. Android 9 (poziom interfejsu API
28) lub niższym, odczyt i zapis w tym katalogu wymaga
READ_EXTERNAL_STORAGE
oraz
WRITE_EXTERNAL_STORAGE
uprawnień:
<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 pozostały prywatne tylko w Twojej aplikacji, możesz zamiast tego użyć
katalog udostępniony przez
Context.getExternalFilesDir()
W Androidzie 4.3 i starszych wersjach zapisywanie w tym katalogu wymaga także
WRITE_EXTERNAL_STORAGE
uprawnienia. Począwszy od Androida 4.4 uprawnienia nie są już wymagane, ponieważ katalog
nie jest dostępne dla innych aplikacji, więc zadeklaruj, że uprawnienia powinny być wysyłane tylko na
starszych wersji Androida przez dodanie
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, w którym katalogu ma się znajdować plik, musisz utworzyć nazwę pliku odporną na kolizje.
Możesz również zapisać ścieżkę w zmiennej uczestnika do późniejszego użycia. Oto przykładowe rozwiązanie:
w metodzie, która zwraca unikalną nazwę pliku dla nowego zdjęcia z użyciem sygnatury daty i godziny.
(W tym przykładzie przyjęto, że ta metoda jest wywoływana z poziomu obiektu 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; }
Gdy ta metoda jest dostępna do utworzenia pliku zdjęcia, możesz utworzyć i wywołać
Intent
lubi to:
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
getUriForFile(Context, String, File)
, który zwraca identyfikator URI content://
. Najnowsze aplikacje kierowane na Androida 7.0 (poziom interfejsu API
24) lub wyższym, przekazanie identyfikatora URI file://
przez granicę pakietu powoduje
FileUriExposedException
Dlatego udostępniamy 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 jest zgodny z drugim argumentem funkcji getUriForFile(Context, String, File)
.
W sekcji metadanych dostawcy widać, że dostawca tego oczekuje
odpowiednie ścieżki do skonfigurowania w specjalnym 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()
po wywołaniu z
Environment.DIRECTORY_PICTURES
Pamiętaj, aby zastąpić com.example.package.name
rzeczywistą nazwą pakietu
do aplikacji. Zapoznaj się też z dokumentacją usługi
FileProvider
za
obszerny opis specyfikatorów ścieżek, których możesz używać oprócz external-path
.
Dodaj zdjęcie 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 może uzyskać dostępu do plików, ponieważ są one prywatne w 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 skalowanego obrazu
Zarządzanie wieloma zdjęciami w pełnym rozmiarze może być trudne z powodu ograniczonej pamięci. Jeśli zauważysz, że w aplikacji zaczyna brakować pamięci po wyświetleniu tylko kilku obrazów, można znacznie zmniejszyć ilości dynamicznej stosu wykorzystywanej przez rozwinięcie pliku JPEG do tablicy pamięci, która została już przeskalowana do do rozmiaru docelowego widoku. Prezentację tej metody przedstawia przykładowa metoda.
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); }