Ответ 1
Изменить с 30.12.2015 - Окончательное руководство по загрузке изображений
последнее крупное обновление: 31 марта 2016 г.
TL; DR a.k.a. перестаньте говорить, просто дай мне код!
Пропустить в конец этого сообщения, скопировать
BasicImageDownloader
(версия javadoc здесь) в ваш проект, реализуйте интерфейсOnImageLoaderListener
и все готово.Примечание: хотя
BasicImageDownloader
обрабатывает возможные ошибки и предотвратит сбой вашего приложения, если что-то пойдет не так, оно не будет работать любую пост-обработку (например, сокращение) загруженногоBitmaps
.
Поскольку этот пост получил довольно много внимания, я решил полностью переработать его, чтобы люди не использовали устаревшие технологии, плохо программировали или просто делали глупые вещи - например, искали "хаки" для запуска сети на основной поток или принять все сертификаты SSL.
Я создал демонстрационный проект под названием "Image Downloader", который демонстрирует, как загружать (и сохранять) изображение с помощью моей собственной реализации загрузчика, встроенного в Android DownloadManager
, а также некоторых популярных библиотек с открытым исходным кодом. Вы можете просмотреть полный исходный код или загрузить проект на GitHub.
Примечание. Я еще не настроил управление разрешениями для SDK 23+ (Marshmallow), поэтому проект нацелен на SDK 22 (Lollipop).
В моем заключении в конце этого сообщения я поделюсь своим скромным мнением о правильном использовании для каждого конкретного способа загрузки изображений, о котором я упоминал.
Начните с собственной реализации (вы можете найти код в конце сообщения). Прежде всего, это Basic ImageDownloader и это. Все, что он делает, - это подключение к указанному URL-адресу, считывание данных и попытка его декодирования как Bitmap
, инициирование обратных вызовов интерфейса OnImageLoaderListener
, когда это необходимо.
Преимущество этого подхода - это просто, и у вас есть четкий обзор того, что происходит. Хороший способ пойти, если все, что вам нужно, это загрузить/отобразить и сохранить некоторые изображения, в то время как вы не заботитесь о сохранении кеша памяти/диска.
Примечание: в случае больших изображений вам может потребоваться масштабировать их вниз.
-
Android DownloadManager - это способ позволить системе обрабатывать загрузку для вас. Он действительно способен загружать любые файлы, а не только изображения. Вы можете разрешить загрузке беззвучно и невидимо для пользователя, или вы можете разрешить пользователю видеть загрузку в области уведомлений. Вы также можете зарегистрировать BroadcastReceiver
, чтобы получить уведомление после завершения загрузки. Настройка довольно проста, обратитесь к связанному проекту для кода примера.
Использование DownloadManager
обычно не является хорошей идеей, если вы также хотите отображать изображение, так как вам нужно будет прочитать и декодировать сохраненный файл, а не просто загрузить загруженный Bitmap
в ImageView
. DownloadManager
также не предоставляет API для вашего приложения, чтобы отслеживать ход загрузки.
-
Теперь введение большого материала - библиотеки. Они могут делать гораздо больше, чем просто загружать и отображать изображения, в том числе: создание и управление кешем памяти/диска, изменение размеров изображений, их преобразование и многое другое.
Я начну с Volley, мощной библиотеки, созданной Google и охватываемой официальной документацией. Будучи универсальной сетевой библиотекой, не специализирующейся на изображениях, Volley имеет довольно мощный API для управления изображениями.
Вам нужно будет реализовать класс Singleton для управления запросами Volley, и вам хорошо идти.
Возможно, вы захотите заменить свой ImageView
на Volley NetworkImageView
, поэтому загрузка в основном станет однострочным:
((NetworkImageView) findViewById(R.id.myNIV)).setImageUrl(url, MySingleton.getInstance(this).getImageLoader());
Если вам нужно больше управления, вот что это значит создать ImageRequest
с Volley:
ImageRequest imgRequest = new ImageRequest(url, new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
//do stuff
}
}, 0, 0, ImageView.ScaleType.CENTER_CROP, Bitmap.Config.ARGB_8888,
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
//do stuff
}
});
Следует отметить, что Volley обладает отличным механизмом обработки ошибок, предоставляя класс VolleyError
, который поможет вам определить точную причину ошибки. Если ваше приложение много работает и управляет изображениями, это не его основная цель, а Volley это идеально подходит для вас.
-
Квадрат Picassoэто хорошо известная библиотека, которая будет делать все для загрузки изображений. Просто показывать изображение с помощью Picasso так же просто, как:
Picasso.with(myContext)
.load(url)
.into(myImageView);
По умолчанию Picasso управляет кешем диска/памяти, поэтому вам не нужно беспокоиться об этом. Для большего контроля вы можете реализовать интерфейс Target
и использовать его для загрузки изображения - это обеспечит обратные вызовы, похожие на пример Volley. Проверьте демонстрационный проект для примеров.
Picasso также позволяет применять преобразования к загруженному изображению, и есть даже другие библиотеки, расширяющие эти API. Также отлично работает в RecyclerView
/ListView
/GridView
.
-
Универсальный загрузчик изображений - еще одна очень популярная библиотека, предназначенная для управления имиджем. Он использует свой собственный ImageLoader
, который (после инициализации) имеет глобальный экземпляр, который можно использовать для загрузки изображений в одной строке кода:
ImageLoader.getInstance().displayImage(url, myImageView);
Если вы хотите отслеживать ход загрузки или получить доступ к загружаемому Bitmap
:
ImageLoader.getInstance().displayImage(url, myImageView, opts,
new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
//do stuff
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
//do stuff
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
//do stuff
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
//do stuff
}
}, new ImageLoadingProgressListener() {
@Override
public void onProgressUpdate(String imageUri, View view, int current, int total) {
//do stuff
}
});
Аргумент opts
в этом примере - это объект DisplayImageOptions
. Подробнее см. Демонстрационный проект.
Подобно Volley, UIL предоставляет класс FailReason
, который позволяет вам проверить, что пошло не так при загрузке. По умолчанию UIL поддерживает кеш памяти/диска, если вы явно не говорите ему об этом.
Примечание: автор упомянул, что он больше не поддерживает проект с 27 ноября 2015 года. Но поскольку есть много участников, мы можем надеяться, что Universal Image Loader будет жить.
-
Facebook Fresco - это новейшая и (ИМО) самая передовая библиотека, которая выводит управление изображениями на новый уровень: от хранения Bitmaps
от кучи java (до Lollipop) до поддержки анимированных форматов и progressive Потоковая передача JPEG.
Чтобы узнать больше о идеях и методах Fresco, обратитесь к этот пост.
Основное использование довольно простое. Обратите внимание, что вам нужно вызвать Fresco.initialize(Context);
только один раз, предпочтительнее в классе Application
. Инициализация Fresco более одного раза может привести к непредсказуемому поведению и ошибкам OOM.
Fresco использует Drawee
для отображения изображений, вы можете думать о них как о ImageView
s:
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/drawee"
android:layout_width="match_parent"
android:layout_height="match_parent"
fresco:fadeDuration="500"
fresco:actualImageScaleType="centerCrop"
fresco:placeholderImage="@drawable/placeholder_grey"
fresco:failureImage="@drawable/error_orange"
fresco:placeholderImageScaleType="fitCenter"
fresco:failureImageScaleType="centerInside"
fresco:retryImageScaleType="centerCrop"
fresco:progressBarImageScaleType="centerInside"
fresco:progressBarAutoRotateInterval="1000"
fresco:roundAsCircle="false" />
Как вы можете видеть, многие вещи (включая опции преобразования) уже определены в XML, поэтому все, что вам нужно сделать, чтобы отобразить изображение, - это однострочный:
mDrawee.setImageURI(Uri.parse(url));
Fresco предоставляет расширенный API настройки, который при обстоятельствах может быть довольно сложным и требует, чтобы пользователь внимательно прочитал документы (да, иногда вам нужно RTFM).
Я включил примеры для прогрессивного JPEG и анимированных изображений в проект образца.
Заключение - "Я узнал о том, что мне нужно использовать сейчас?"
Обратите внимание, что следующий текст отражает мое личное мнение и должен не следует воспринимать как постулат.
- Если вам нужно только загрузить/сохранить/отобразить некоторые изображения, не планируйте использовать их в
Recycler-/Grid-/ListView
и не нуждайтесь в целых кучах изображений для готовности к отображению, BasicImageDownloader должны соответствовать вашим потребностям. - Если ваше приложение сохраняет изображения (или другие файлы) в результате пользователя или автоматическое действие, и вам не нужно часто отображаемые изображения, используйте Android DownloadManager.. li >
- В случае, если ваше приложение много сетей, передает/принимает
JSON
данные, работает с изображениями, но это не основная цель приложения, идите с Volley. - Ваше приложение ориентировано на изображение/медиа, вы хотите применить некоторые преобразования к изображениям и не хотите беспокоиться о сложном API: используйте Picasso (Примечание: не предоставляется какой-либо API для отслеживания состояния промежуточной загрузки) или Универсальный загрузчик изображений
- Если ваше приложение связано с изображениями, вам нужны расширенные функции, такие как отображение анимированных форматов, и вы готовы читать документы, идите с Fresco.
Если вы пропустили это, ссылка Github для демонстрационного проекта.
И здесь BasicImageDownloader.java
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.Set;
public class BasicImageDownloader {
private OnImageLoaderListener mImageLoaderListener;
private Set<String> mUrlsInProgress = new HashSet<>();
private final String TAG = this.getClass().getSimpleName();
public BasicImageDownloader(@NonNull OnImageLoaderListener listener) {
this.mImageLoaderListener = listener;
}
public interface OnImageLoaderListener {
void onError(ImageError error);
void onProgressChange(int percent);
void onComplete(Bitmap result);
}
public void download(@NonNull final String imageUrl, final boolean displayProgress) {
if (mUrlsInProgress.contains(imageUrl)) {
Log.w(TAG, "a download for this url is already running, " +
"no further download will be started");
return;
}
new AsyncTask<Void, Integer, Bitmap>() {
private ImageError error;
@Override
protected void onPreExecute() {
mUrlsInProgress.add(imageUrl);
Log.d(TAG, "starting download");
}
@Override
protected void onCancelled() {
mUrlsInProgress.remove(imageUrl);
mImageLoaderListener.onError(error);
}
@Override
protected void onProgressUpdate(Integer... values) {
mImageLoaderListener.onProgressChange(values[0]);
}
@Override
protected Bitmap doInBackground(Void... params) {
Bitmap bitmap = null;
HttpURLConnection connection = null;
InputStream is = null;
ByteArrayOutputStream out = null;
try {
connection = (HttpURLConnection) new URL(imageUrl).openConnection();
if (displayProgress) {
connection.connect();
final int length = connection.getContentLength();
if (length <= 0) {
error = new ImageError("Invalid content length. The URL is probably not pointing to a file")
.setErrorCode(ImageError.ERROR_INVALID_FILE);
this.cancel(true);
}
is = new BufferedInputStream(connection.getInputStream(), 8192);
out = new ByteArrayOutputStream();
byte bytes[] = new byte[8192];
int count;
long read = 0;
while ((count = is.read(bytes)) != -1) {
read += count;
out.write(bytes, 0, count);
publishProgress((int) ((read * 100) / length));
}
bitmap = BitmapFactory.decodeByteArray(out.toByteArray(), 0, out.size());
} else {
is = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
}
} catch (Throwable e) {
if (!this.isCancelled()) {
error = new ImageError(e).setErrorCode(ImageError.ERROR_GENERAL_EXCEPTION);
this.cancel(true);
}
} finally {
try {
if (connection != null)
connection.disconnect();
if (out != null) {
out.flush();
out.close();
}
if (is != null)
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
if (result == null) {
Log.e(TAG, "factory returned a null result");
mImageLoaderListener.onError(new ImageError("downloaded file could not be decoded as bitmap")
.setErrorCode(ImageError.ERROR_DECODE_FAILED));
} else {
Log.d(TAG, "download complete, " + result.getByteCount() +
" bytes transferred");
mImageLoaderListener.onComplete(result);
}
mUrlsInProgress.remove(imageUrl);
System.gc();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public interface OnBitmapSaveListener {
void onBitmapSaved();
void onBitmapSaveError(ImageError error);
}
public static void writeToDisk(@NonNull final File imageFile, @NonNull final Bitmap image,
@NonNull final OnBitmapSaveListener listener,
@NonNull final Bitmap.CompressFormat format, boolean shouldOverwrite) {
if (imageFile.isDirectory()) {
listener.onBitmapSaveError(new ImageError("the specified path points to a directory, " +
"should be a file").setErrorCode(ImageError.ERROR_IS_DIRECTORY));
return;
}
if (imageFile.exists()) {
if (!shouldOverwrite) {
listener.onBitmapSaveError(new ImageError("file already exists, " +
"write operation cancelled").setErrorCode(ImageError.ERROR_FILE_EXISTS));
return;
} else if (!imageFile.delete()) {
listener.onBitmapSaveError(new ImageError("could not delete existing file, " +
"most likely the write permission was denied")
.setErrorCode(ImageError.ERROR_PERMISSION_DENIED));
return;
}
}
File parent = imageFile.getParentFile();
if (!parent.exists() && !parent.mkdirs()) {
listener.onBitmapSaveError(new ImageError("could not create parent directory")
.setErrorCode(ImageError.ERROR_PERMISSION_DENIED));
return;
}
try {
if (!imageFile.createNewFile()) {
listener.onBitmapSaveError(new ImageError("could not create file")
.setErrorCode(ImageError.ERROR_PERMISSION_DENIED));
return;
}
} catch (IOException e) {
listener.onBitmapSaveError(new ImageError(e).setErrorCode(ImageError.ERROR_GENERAL_EXCEPTION));
return;
}
new AsyncTask<Void, Void, Void>() {
private ImageError error;
@Override
protected Void doInBackground(Void... params) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(imageFile);
image.compress(format, 100, fos);
} catch (IOException e) {
error = new ImageError(e).setErrorCode(ImageError.ERROR_GENERAL_EXCEPTION);
this.cancel(true);
} finally {
if (fos != null) {
try {
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
@Override
protected void onCancelled() {
listener.onBitmapSaveError(error);
}
@Override
protected void onPostExecute(Void result) {
listener.onBitmapSaved();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public static Bitmap readFromDisk(@NonNull File imageFile) {
if (!imageFile.exists() || imageFile.isDirectory()) return null;
return BitmapFactory.decodeFile(imageFile.getAbsolutePath());
}
public interface OnImageReadListener {
void onImageRead(Bitmap bitmap);
void onReadFailed();
}
public static void readFromDiskAsync(@NonNull File imageFile, @NonNull final OnImageReadListener listener) {
new AsyncTask<String, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(String... params) {
return BitmapFactory.decodeFile(params[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null)
listener.onImageRead(bitmap);
else
listener.onReadFailed();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, imageFile.getAbsolutePath());
}
public static final class ImageError extends Throwable {
private int errorCode;
public static final int ERROR_GENERAL_EXCEPTION = -1;
public static final int ERROR_INVALID_FILE = 0;
public static final int ERROR_DECODE_FAILED = 1;
public static final int ERROR_FILE_EXISTS = 2;
public static final int ERROR_PERMISSION_DENIED = 3;
public static final int ERROR_IS_DIRECTORY = 4;
public ImageError(@NonNull String message) {
super(message);
}
public ImageError(@NonNull Throwable error) {
super(error.getMessage(), error.getCause());
this.setStackTrace(error.getStackTrace());
}
public ImageError setErrorCode(int code) {
this.errorCode = code;
return this;
}
public int getErrorCode() {
return errorCode;
}
}
}