Примечание. На этой странице описывается класс Camera, который больше не поддерживается. Мы рекомендуем использовать CameraX или, в некоторых случаях, Camera2. CameraX и Camera2 поддерживают Android 5.0 (уровень API 21) и более поздние версии.
Чтобы узнать, как перейти с Camera1 на CameraX, прочитайте статью Migrate Camera1 to CameraX (Переход с Camera1 на CameraX).
Вы также можете использовать агентский навык для перехода с Camera1 на CameraX.
В этом уроке рассказывается, как сделать снимок, делегировав работу другому приложению камеры на устройстве. Если вы хотите создать собственную функцию камеры, прочитайте статью Управление камерой.
Предположим, вы разрабатываете краудсорсинговый сервис, который создает глобальную карту погоды, объединяя фотографии неба, сделанные пользователями вашего приложения. Интеграция фотографий – это лишь небольшая часть вашего приложения. Вы хотите просто фотографировать, а не изобретать камеру заново. К счастью, на большинстве устройств 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); }