В уроке рассказывается как делать фотографии, используя существующие приложения.
Допустим вы разрабатываете приложение для службы погоды, которая строит глобальную карту погоды путем смешивания фотографий неба, сделанных с помощью встроенной камеры. Сама фотосъемка это лишь малая часть вашего приложения. Вы можете делать фотографии с минимальными усилиями, не изобретая велосипед. К счастью, большинство Android устройств включают в себя по крайней мере одно приложение для работы с камерой. В уроке мы покажем как их можно использовать для получения фото.
Получаем разрешение на камеру
Если получение снимков – важная часть вашего приложения, то показывайте его на Google Play только для устройств, которые имеют камеру. Для этого добавьте элемент <uses-features> в файл манифеста:
1 2 3 4 5 |
<manifest ... > <uses-feature android:name="android.hardware.camera" android:required="true" /> ... </manifest> |
Если для большинства функций приложения камера не нужна, установите атрибут android:required
в значение false
. Тогда Google Play позволит устройствам без камеры загружать ваше приложение. В таком случае вы должны программно проверять наличие камеры с помощью метода hasSystemFeature(PackageManager.FEATURE_CAMERA). Если камера недоступна, отключите все функции, которые ее используют.
Получаем фото с помощью других приложений
Чтобы использовать другие приложение для каких-либо действий, в Android применяются намерения – объекты типа Intent, в которых описывается, что конкретно вы хотите сделать. Этот процесс включает три составляющих: само намерение – объект типа Intent, вызов внешнего явления – объекта типа Activity и некоторый код, который обрабатывает данные, полученные от внешнего явления.
Пример выполнения намерения для получения фотографии с камеры:
1 2 3 4 5 6 7 8 |
static final int REQUEST_IMAGE_CAPTURE = 1; private void dispatchTakePictureIntent() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(getPackageManager()) != null) { startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE); } } |
Не забывайте, что для безопасного использования метода startActivityForResult() необходимо убедиться, что существует явление, способное обработать запрос. Это можно сделать с помощью метода resolveActivity(), который возвращает первое явление, способное обработать намерение. Если не провести такую проверку и явление для обработки не существует, при вызове startActivityForResult()
приложение аварийно завершится.
Получение миниатюр
Наверняка сам факт срабатывания затвора камеры это не предел желаний, и после съемки вы захотите получить картинку с камеры в ваше приложение, чтобы что-то с ней сделать.
Стандартное приложение “Камера” сжимает фото, которое возвращает намерение, в маленький объект типа Bitmap и передает в дополнительных данных с ключом "data"
. В следующем примере показано, как получить изображение и отобразить его в компоненте ImageView:
1 2 3 4 5 6 7 8 |
@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"); mImageView.setImageBitmap(imageBitmap); } } |
Примечание: миниатюра картинки из "data"
хороша для иконки или аватара, но вряд ли хороша для чего-то большего. Получение полноразмерного изображения требует немного больше работы.
Сохранение полноразмерной фотографии
Приложение Камера сохраняет полноразмерное изображение, если передать ему имя файла для сохранения. Вы должны передать имя со всеми спецификаторами, чтобы приложение могло сохранить фотографию.
В целом, все сделанные с помощью камеры фотографии должны располагаться в публичной директории внешнего хранилища, чтобы любое приложение могло получить к ним доступ. Для получения общей директории для хранения фотографий служит метод getExternalStoragePublicDirectory(), в который нужно передать аргумент DIRECTORY_PICTURES. Поскольку метод возвращает каталог, доступный для всех приложений, необходимо получить разрешения READ_EXTERNAL_STORAGE и WRITE_EXTERNAL_STORAGE. Права на запись разрешают также чтение, поэтому если вы хотите записывать в публичный каталог и читать из него, добавьте только разрешение на запись:
1 2 3 4 |
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest> |
Но если вы хотите получить фотографии, доступные только для вашего приложения, используйте директорию, которую возвращает метод getExternalFilesDir(). В Android 4.3 и ниже, запись в эту директорию также требует разрешения WRITE_EXTERNAL_STORAGE
. Начиная с Android 4.4, права на запись не требуются, поскольку директория недоступна из других приложений. Добавляйте разрешение на запись в том случае, если поддерживаете приложения с ранними версиями, включив атрибут maxSdkVersion:
1 2 3 4 5 |
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" /> ... </manifest> |
Примечание: файлы, сохраненные в директории, которую возвращает метод getExternalFilesDir(), удаляются вместе с приложением.
После выбора директории для файлов, необходимо выбрать устойчивое к коллизиям имя файла. Вы можете также сохранить директорию в переменной, чтобы использовать ее позже. Пример кода, который возвращает уникальное имя файла для новой фотографии, используя текущие дату и время:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
String mCurrentPhotoPath; private File createImageFile() throws IOException { // Создаем имя для файла String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES); File image = File.createTempFile( imageFileName, /* префикс */ ".jpg", /* суффикс */ storageDir /* директория */ ); // Сохранение файла: путь для использования в намерениях ACTION_VIEW mCurrentPhotoPath = "file:" + image.getAbsolutePath(); return image; } |
Теперь, когда у нас есть метод для получения имени файла, можно создать и выполнить намерение:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
static final int REQUEST_TAKE_PHOTO = 1; private void dispatchTakePictureIntent() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // Убеждаемся, что есть явление, работающее с камерой if (takePictureIntent.resolveActivity(getPackageManager()) != null) { // Создаем объект типа File, в котором будет храниться изображение File photoFile = null; try { photoFile = createImageFile(); } catch (IOException ex) { // Ошибка создания файла. Здесь обрабатываем исключение. ... } // Выполняется только если файл был создан if (photoFile != null) { takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO); } } } |
Добавление фотографии в галерею
После создания фотографии с помощью намерения, вы знаете, где она находится, поскольку сообщили куда ее сохранить. Для всех остальных случаев, простейший способ получить доступ к созданной фотографии – использовать системный медиа-провайдер.
Примечание: если вы использовали директорию из метода getExternalFilesDir()
, медиа-сканер не сможет получить доступ к этим файлам, поскольку они доступны только для вашего приложения.
Следующий пример показывает, как использовать системный медиа-сканер для того, чтобы добавить ваши фотографии в базу данных медиа-провайдера. После этого фотографии будут доступны в приложении Галерея, а также в других приложениях:
1 2 3 4 5 6 7 |
private void galleryAddPic() { Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); File f = new File(mCurrentPhotoPath); Uri contentUri = Uri.fromFile(f); mediaScanIntent.setData(contentUri); this.sendBroadcast(mediaScanIntent); } |
Масштабирование фотографий
Управление множеством полноразмерных фотографий требует хитрого подхода при ограниченной памяти. Если вы заметили, что приложение тратит всю доступную память уже после отображения нескольких фотографий, вы можете значительно уменьшить размер динамической памяти за счет расширения JPEG в массив памяти, который масштабируется в соответствии с размером компонента, в котором отображается картинка. Следующий пример демонстрирует данный подход:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
private void setPic() { // Получаем размеры компонента int targetW = mImageView.getWidth(); int targetH = mImageView.getHeight(); // Получаем размеры картинки BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions); int photoW = bmOptions.outWidth; int photoH = bmOptions.outHeight; // Определяем коэффициент сжатия картинки int scaleFactor = Math.min(photoW/targetW, photoH/targetH); // Перекодируем файл картинки в соответствии с нужными размерами bmOptions.inJustDecodeBounds = false; bmOptions.inSampleSize = scaleFactor; bmOptions.inPurgeable = true; Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions); mImageView.setImageBitmap(bitmap); } |