Примечание. На этой странице рассматривается класс Camera, который больше не поддерживается. Рекомендуем использовать CameraX или, в некоторых случаях, Camera2. CameraX и Camera2 поддерживают Android 5.0 (уровень API 21) и более поздние версии.
В этом уроке рассказывается, как сделать снимок, делегировав работу другому приложению камеры на устройстве. Если вы хотите создать собственную функцию камеры, прочитайте статью Управление камерой.
Предположим, вы разрабатываете краудсорсинговый сервис, который создает глобальную карту погоды, объединяя фотографии неба, сделанные пользователями вашего приложения. Интеграция фотографий – это лишь небольшая часть вашего приложения. Вы хотите просто фотографировать, а не изобретать камеру заново. К счастью, на большинстве устройств Android уже установлено хотя бы одно приложение камеры. В этом уроке вы узнаете, как сделать так, чтобы он сделал для вас снимок.
Как запросить доступ к функции камеры
Если основная функция вашего приложения – фотографирование, ограничьте его видимость в Google Play только устройствами с камерой. Чтобы указать, что для работы приложения требуется камера, добавьте в файл манифеста тег <uses-feature>:
<manifest ... > <uses-feature android:name="android.hardware.camera" android:required="true" /> ... </manifest>
Если приложение использует камеру, но может работать и без нее, задайте для атрибута android:required значение false. В этом случае Google Play разрешит скачивать ваше приложение на устройства без камеры. В этом случае вы должны самостоятельно проверять доступность камеры во время выполнения, вызывая метод hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY).
Если камера недоступна, отключите ее функции.
Получить значок
Если простое фотографирование не является конечной целью вашего приложения, то вы, вероятно, захотите получить изображение из приложения камеры и что-то с ним сделать.
Приложение "Камера" для Android кодирует фотографию в возвращаемом объекте Intent, который доставляется в метод onActivityResult() в виде небольшого объекта Bitmap в дополнительных данных под ключом "data". Приведенный ниже код получает это изображение и показывает его в 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); } }
Примечание. Это уменьшенное изображение из "data" может подойти для значка, но не более того. С полноразмерным изображением работать немного сложнее.
Сохранить полноразмерную фотографию
Приложение Android Camera сохраняет полноразмерную фотографию, если вы укажете файл для сохранения. Вы должны указать полное название файла, в который приложение камеры должно сохранить фотографию.
Как правило, любые фотографии, которые пользователь делает с помощью камеры устройства, должны сохраняться на устройстве в общедоступном внешнем хранилище, чтобы они были доступны всем приложениям. Правильный каталог для общих фотографий предоставляется методом getExternalStoragePublicDirectory() с аргументом DIRECTORY_PICTURES. Каталог, предоставляемый этим методом, является общим для всех приложений. В Android 9 (уровень API 28) и более ранних версиях для чтения и записи в этот каталог требуются разрешения READ_EXTERNAL_STORAGE и WRITE_EXTERNAL_STORAGE соответственно.
<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
В Android 10 (уровень API 29) и более поздних версиях для обмена фотографиями используется таблица MediaStore.Images.
Вам не нужно объявлять разрешения на доступ к хранилищу, если приложению требуется доступ только к фотографиям, сделанным пользователем в этом приложении.
Если вы хотите, чтобы фотографии были доступны только вашему приложению, используйте каталог, предоставляемый методом Context.getExternalFilesDir().
На устройствах с Android 4.3 и более ранними версиями для записи в этот каталог также требуется разрешение WRITE_EXTERNAL_STORAGE. Начиная с Android 4.4, разрешение больше не требуется, поскольку каталог недоступен для других приложений. Вы можете указать, что разрешение должно запрашиваться только в более ранних версиях Android, добавив атрибут maxSdkVersion:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> ... </manifest>
Примечание. Файлы, сохраненные в каталогах, которые предоставляют методы getExternalFilesDir() и getFilesDir(), удаляются, когда пользователь удаляет приложение.
Выбрав каталог для файла, создайте название файла, устойчивое к коллизиям.
Вы также можете сохранить путь в переменной-члене для последующего использования. Ниже приведен пример решения в методе, который возвращает уникальное название файла для новой фотографии с использованием отметки даты и времени.
В этом примере предполагается, что вы вызываете метод из 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; }
Теперь, когда у вас есть метод для создания файла фотографии, вы можете создать и вызвать 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); } } }
Примечание. Мы используем getUriForFile(Context, String, File), который возвращает URI content://. В более новых приложениях, предназначенных для Android 7.0 (уровень API 24) и выше, передача URI file:// за пределы пакета вызывает ошибку FileUriExposedException.
Поэтому мы предлагаем более общий способ хранения изображений с помощью FileProvider.
Теперь нужно настроить FileProvider. Добавьте поставщика в манифест приложения:
<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>
Убедитесь, что строка authorities соответствует второму аргументу getUriForFile(Context, String, File).
В разделе метаданных определения поставщика можно увидеть, что поставщик ожидает, что допустимые пути будут настроены в специальном файле ресурсов
<?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>
Компонент пути соответствует пути, который возвращается методом getExternalFilesDir() при вызове с параметром Environment.DIRECTORY_PICTURES.
Замените com.example.package.name на название пакета вашего приложения. Также ознакомьтесь с документацией по FileProvider, чтобы узнать, какие ещё спецификаторы путей можно использовать помимо external-path.
Добавьте фотографию в галерею.
Когда вы создаете фотографию с помощью намерения, вы должны знать, где находится ваше изображение, потому что вы указали, где его сохранить в первую очередь. Для всех остальных самый простой способ сделать фотографию доступной — сделать ее доступной из системного поставщика медиафайлов.
Примечание. Если вы сохранили фотографию в каталоге, предоставленном getExternalFilesDir(), сканер медиафайлов не сможет получить доступ к файлам, поскольку они являются частными для вашего приложения.
В следующем примере показано, как вызвать системный сканер медиафайлов, чтобы добавить фотографию в базу данных поставщика медиаконтента. После этого она станет доступна в приложении "Галерея" и других приложениях.
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); }
Декодирование масштабированного изображения
Управлять несколькими полноразмерными изображениями может быть сложно из-за ограниченного объема памяти. Если вы обнаружите, что после отображения всего нескольких изображений в приложении возникает нехватка памяти, вы можете значительно сократить объем используемой динамической памяти, расширив JPEG в массив памяти, который уже масштабирован в соответствии с размером целевого представления. В следующем примере показано, как это сделать.
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); }