Пишем функцию сохранения картинок на SD-карту
В процессе написания приложения для андроида у меня возникла задача сохранять произвольное изображение в файл на флешке. В этой статье я опишу, как я решил эту проблему, какие трудности встретились мне в процессе, и как они были решены. Хотелось бы отдельно отметить, что я .NET программист, судьба занесла меня в Java-мир только из-за необходимости создания небольших по размеру (поэтому monodroid сразу нет) и довольно простых с точки зрения интерфейса андроид-приложений. Это означает, что я тоже только учусь, а значит буду рад любым советом и замечаниям профессионалов.
Итак, предположим, что у нас есть ImageView, в котором содержится картинка, необходимая нам в виде файла. Первый же вопрос — куда сохранять эту картинку?
Самый правильный и гуглом одобренный подход будет сохранять картинку в папку кэша специально выделенную для вашей аппликации.
Получить абсолютный путь к ней можно вызовом функции:
context.getCacheDir();
Данная папка очищается при деинсталляции приложения, кроме того ее размер отслеживается системой, и при нехватке места внутренней памяти, файлы оттуда будут автоматически удалены андроидом.
Есть у данного решения и минусы. Самый главный — папка кэша приложения содержится во внутренней памяти устройства, которой вечно не хватает. Рекомендуется сохранять там лишь временные и не очень большие файлы, сохранение большого количества тяжелых картинок здесь нежелательно.
Лучше воспользоваться папкой кэша приложения на SD-карте, путь к которой можно получить функцией:
context.getExternalCacheDir();
Данная папка тоже будет очищена при деинсталляции приложения, но ее размер не отслеживается системой, поэтому перед сохранением туда файлов, ее состояние желательно проверять командой:
Environment.getExternalStorageState();
В моем случае по совокупности разных факторов оба стандартных решения показались мне неоптимальными, поэтому я просто сохраняю файлы в корне SD-карты, путь туда можно получить функцией:
Environment.getExternalStorageDirectory();
Теперь о самой процедуре сохранения, ее код приведен ниже.
ImageView iv; // ImageView, содержащий изображение, которое нужно сохранить String folderToSave = Environment.getExternalStorageDirectory().toString(); // папка куда сохранять, в данном случае - корень SD-карты private String SavePicture(ImageView iv, String folderToSave) < OutputStream fOut = null; Time time = new Time(); time.setToNow(); try < File file = new File(folderToSave, Integer.toString(time.year) + Integer.toString(time.month) + Integer.toString(time.monthDay) + Integer.toString(time.hour) + Integer.toString(time.minute) + Integer.toString(time.second) +".jpg"); // создать уникальное имя для файла основываясь на дате сохранения fOut = new FileOutputStream(file); Bitmap bitmap = (BitmapDrawable) iv.getDrawable().getBitmap(); bitmap.compress(Bitmap.CompressFormat.JPEG, 85, fOut); // сохранять картинку в jpeg-формате с 85% сжатия. fOut.flush(); fOut.close(); MediaStore.Images.Media.insertImage(getContentResolver(), file.getAbsolutePath(), file.getName(), file.getName()); // регистрация в фотоальбоме >catch (Exception e) // здесь необходим блок отслеживания реальных ошибок и исключений, общий Exception приведен в качестве примера < return e.getMessage(); >return ""; >
Несколько пояснений по коду.
File file = new File(folderToSave, Integer.toString(time.year) + Integer.toString(time.month+1) + Integer.toString(time.monthDay) + Integer.toString(time.hour) + Integer.toString(time.minute) + Integer.toString(time.second) +".jpg");
Не очень изящное, но простое как карандаш и реально работающее решение получить уникальное имя для файла в формате удобном для сортировки — «2011 11 17 20 31 49.jpg»
Здесь всего одна маленькая хитрость. Обратили внимание на time.month+1?
Дело в том, что Java считает месяцы с нуля, и ноябрь получается 10м, а не 11м, как все привыкли, месяцем. Непорядок.
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, fOut);
Тут тоже все самоочевидно, споры могут возникнуть лишь о процентах сжатия. Мне кажется, что 85% оптимальный вариант, но у каждого может быть свое мнение и свои условия задачи.
MediaStore.Images.Media.insertImage(getContentResolver(), file.getAbsolutePath(), file.getName(), file.getName());
Интересный финт, о котором многие забывают. Не все андроид-пользователи продвинуты настолько, чтобы отыскать сохраненные вами файлы, даже если вы им показали путь файловой системы. Но вот про приложение «Фотоальбом» знают точно все (это там, где только что снятые фоточки можно посмотреть). Добавьте вышеупомянутую строчку в ваш код, и картинка не только сохранится в нужную директорию, но еще и зарегистрируется в фотоальбоме, и пользователь всегда ее отыщет ее без всяких проблем.
Кстати, приведенная функция взята из реального работающего приложения Random Pictures, которое показывает случайные картинки из Интернета, а в платной версии программы — имеет возможность сохранения любой картинки к себе на флешку.

Бесплатная версия Random Picture Free на андроид маркете Random Pictures Free

Описанная функция сохранения файла работает только в платной версии. Ее цена — 45 рублей, но для хабражителей — конечно же выкладывается бесплатно. (Хорошая традиция, надо сказать!) Ссылка от автора в комментариях.
Работа с изображениями
Одним из наиболее распространенных источников ресурсов являются файлы изображений. Android поддерживает следующие форматы файлов: .png (предпочтителен), .jpg (приемлем), .gif (нежелателен). Для графических файлов в проекте уже по умолчанию создана папка res/drawable . По умолчанию она уже содержит ряд файлов — пару файлов иконок:

При добавлении графических файлов в эту папку для каждого из них Android создает ресурс Drawable . После этого мы можем обратиться к ресурсу следующим образом в коде Java:
R.drawable.имя_файла
@[имя_пакета:]drawable/имя_файла
Например, добавим в проект в папку res/drawable какой-нибудь файл изображения. Для этого скопируем на жестком диске какой-нибудь файл с расширением png или jpg и вставим его в папку res/drawable (для копирования в проект используется простой Copy-Paste)
Далее нам будет предложено выбрать папку — drawable или drawable-24 . Для добавления обычных файлов изображений выберем drawable :

Здесь сразу стоит учесть, что файл изображения будет добавляться в приложение, тем самым увеличивая его размер. Кроме того, большие изображения отрицательно влияют на производительность. Поэтому лучше использовать небольшие и оптимизрованные (сжатые) графические файлы. Хотя, также стоит отметить, что все файлы изображений, которые добавляются в эту папку, могут автоматически оптимизироваться с помощью утилиты aapt во время построения проекта. Это позволяет уменьшить размер файла без потери качества.
При копировании файла нам будет предложено установить для него новое имя.

Можно изменить название файла, а можно оставить так как есть. В моем случае файл называется dubi2.png . И затем нажмем на кнопку Refactor. И после этого в папку drawable будет добавлен выбранный нами файл изображения.

Для работы с изображениями в Android можно использовать различные элементы, но непосредственно для вывода изображений предназначен ImageView . Поэтому изменим файл activity_main.xml следующим образом:
В данном случае для отображения файла в ImageView у элемента устанавливается атрибут android:src . В его значении указывается имя графического ресурса, которое совпадает с именем файла без расширения. И после этого уже в Preview или в режиме дизайнере в Android Studio можно будет увидеть применение изображения, либо при запуске приложения:

Если бы мы создавали ImageView в коде java и из кода применяли бы ресурс, то activity могла бы выглядеть так:
package com.example.viewapp; import androidx.appcompat.app.AppCompatActivity; import androidx.constraintlayout.widget.ConstraintLayout; import android.os.Bundle; import android.widget.ImageView; public class MainActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); ConstraintLayout constraintLayout = new ConstraintLayout(this); ImageView imageView = new ImageView(this); // применяем ресурс imageView.setImageResource(R.drawable.dubi2); ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams (ConstraintLayout.LayoutParams.WRAP_CONTENT , ConstraintLayout.LayoutParams.WRAP_CONTENT); layoutParams.leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID; layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID; imageView.setLayoutParams(layoutParams); constraintLayout.addView(imageView); setContentView(constraintLayout); >>
В данном случае ресурс drawable напрямую передается в метод imageView.setImageResource() , и таким образом устанавливается изображение. В результате мы получим тот же результат.
imageView.setImageResource(R.drawable.dubi2);
Однако может возникнуть необходимость как-то обработать ресурс перед использованием или использовать его в других сценариях. В этом случае мы можем сначала получить его как объект Drawable и затем использовать для наших задач:
package com.example.viewapp; import androidx.appcompat.app.AppCompatActivity; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.content.res.ResourcesCompat; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.widget.ImageView; public class MainActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); ConstraintLayout constraintLayout = new ConstraintLayout(this); ImageView imageView = new ImageView(this); Resources res = getResources(); Drawable drawable = ResourcesCompat.getDrawable(res, R.drawable.dubi2, null); // применяем ресурс imageView.setImageDrawable(drawable); ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams (ConstraintLayout.LayoutParams.WRAP_CONTENT , ConstraintLayout.LayoutParams.WRAP_CONTENT); layoutParams.leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID; layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID; imageView.setLayoutParams(layoutParams); constraintLayout.addView(imageView); setContentView(constraintLayout); >>
Для получения ресурса применяется метод ResourcesCompat.getDrawable() , в который передается объект Resources, идентификатор ресурса и тема. В данном случае тема нам не важна, поэтому для нее передаем значение null. Возвращается ресурс в виде объекта Drawable :
Drawable drawable = ResourcesCompat.getDrawable(res, R.drawable.dubi2, null);
Затем, например, можно также передать ресурс объекту ImageView через его метод setImageDrawable()
imageView.setImageDrawable(drawable);
Как реализовать скачивание картинки на телефон в Android Studio?
У меня есть несколько картинок, которые хранятся в ресурсах приложения, каким образом я могу реализовать скачивание этих картинок в память телефона?
- Вопрос задан более трёх лет назад
- 221 просмотр
2 комментария
Простой 2 комментария
Денис Загаевский @zagayevskiy Куратор тега Android
Зачем бы это?
Константин Липатов @Weys Автор вопроса
Денис Загаевский, Чтобы у пользователя была возможность сохранить картинку в память устройства.
Решения вопроса 1

Стек Java\Android
Причем тут Android Studio?
Делается это следующим образом: получаете контекст приложения, по id ресурса получаете InputStream, из полученного InputStream копируете байты в OutputStream свежесозданного файла, куда хотите сохранить картинку.
Ну и не забывайте указать в манифесте приложения разрешение на доступ к файлам на SD карте и внутренней памяти устройства. Перед процессом копирования запрашиваете у пользователя доступ вашего приложения к данному разрешению.
Или Вам готовый сниппет нужен?
Сохранение изображения Android
Работаю на android всего неделю. Знаю Java SE. Надо получить графику ImageView и сохранить ее в Bitmap и этот Bitmap сохранить во внутренней памяти. Мог бы и Drawable извлечь, но графика вся в методе onClick. Плюс файл не хочет сохраняться. Как достичь нужного мне результата? Есть у кого идеи?
Вот сам код:
1 2 3 4 5 6 7 8 9 10
Bitmap bitmap; File image = new File(Environment.getDataDirectory().getPath(),"image.png"); FileOutputStream streamsave = null; try { Bitmap img = Bitmap.createBitmap(graphic.getResources().getDisplayMetrics(), graphic.getWidth(), graphic.getHeight(), Bitmap.Config.ARGB_8888); streamsave = new FileOutputStream(image); img.compress(Bitmap.CompressFormat.PNG, 85, streamsave); streamsave.flush(); streamsave.close(); } catch (IOException e) { }catch (NullPointerException e) { }
Лучшие ответы ( 1 )
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:
Android: Сохранение и загрузка изображения в/из SQLite
Доброго времени суток. Необходимо записать в базу только что нарисованное изображение на canvas. В.
Сохранение изображения в БД
Как в ASP.NET сделать загрузку изображения из файла, его вывод в Image и сохранение в базу данных.

Сохранение изображения
Почему, когда я пытаюсь сохранить изображение из буфера обменаDim img As New.
Сохранение изображения
возможно ли изображение сформированное DirecX распечатать или сохранить в файл? не делая снимок с.
2883 / 2295 / 769
Регистрация: 12.05.2014
Сообщений: 7,978
гадать по куску кода?
разрешения в манифесте для начала проверь
и у меня то самое чувство, когда кто-то пытается сделать непонятно что, но рассказать он не считает нужным
Регистрация: 26.09.2015
Сообщений: 16
Паблито, Даже не знаю, что в манифесте этому view надо прописать. Вся графа в onDraw, в канвасе, тип drawOval и прочее. Как этот canvas заполучить в Bitmap виде? Что именно нужно вам показать для ответа?
2883 / 2295 / 769
Регистрация: 12.05.2014
Сообщений: 7,978

Сообщение было отмечено максим2000 как решение
Решение
предположим view это ссылка на нужное нам вью, с которого надо сделать битмап
1. создаем в активити метод
1 2 3 4 5 6 7 8 9 10 11
private void saveAsBitmap(View view, String filename) { view.setDrawingCacheEnabled(true); Bitmap bitmap = view.getDrawingCache(); try { FileOutputStream out = openFileOutput(filename, MODE_PRIVATE); bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); // ставить 85 бесполезно, PNG - это формат сжатия без потерь out.close(); } catch (Exception ignored) { } bitmap.recycle(); }
если метод размещать не в акитивити, а в левом классе, то придется передавать в метод еще и ссылку на контекст что бы можно было вызвать методы openFileOutput / openFileInput
2. где-то в коде когда надо записать файл, вызываем наш метод
saveAsBitmap(view, "image.png");
Когда надо будет получить ссылку на файл или FileInputStream, вызываем соответственно методы
1 2 3
File file = getFileStreamPath("image.png"); // или FileInputStream inputStream = openFileInput("image.png");
Регистрация: 26.09.2015
Сообщений: 16
Паблито, При нажатии на кнопку, вызывающую метод, среди процессов вылетает такое
1 2 3 4 5 6 7 8 9
01-03 17:44:43.395 30000-9389/? E/DatabaseUtils: Writing exception to parcel java.lang.SecurityException: Permission Denial: get/set setting for user asks to run as user -2 but is calling from user 0; this requires android.permission.INTERACT_ACROSS_USERS_FULL at com.android.server.am.ActivityManagerService.handleIncomingUser(ActivityManagerService.java:13140) at android.app.ActivityManager.handleIncomingUser(ActivityManager.java:2038) at com.android.providers.settings.SettingsProvider.callFromPackage(SettingsProvider.java:607) at android.content.ContentProvider$Transport.call(ContentProvider.java:279) at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:273) at android.os.Binder.execTransact(Binder.java:388) at dalvik.system.NativeStart.run(Native Method)
Для простоты проект скину
Вложения
| Архив WinRAR.rar (7.10 Мб, 14 просмотров) |