Androidの特定のURLから画像をダウンロードして保存するにはどうすればよいですか?
最終メジャーアップデート:2016年3月31日
TL; DR別名、話をやめて、コードを教えてください!!
この投稿の最後にスキップし、
BasicImageDownloader
(ここではjavadocバージョン)をプロジェクトにコピーし、OnImageLoaderListener
インターフェースを実装すれば完了です。注:
BasicImageDownloader
エラーの可能性を処理し、問題が発生した場合にアプリがクラッシュするのを防ぎますが、ダウンロードした後処理(ダウンサイジングなど)は実行しませんBitmaps
。
この投稿は非常に多くの注目を集めているので、人々が非推奨のテクノロジー、悪いプログラミング慣行を使用したり、メインスレッドでネットワークを実行するための「ハック」を探すなどの愚かなことをしたりしないように、完全に作り直すことにしました。すべてのSSL証明書を受け入れます。
「ImageDownloader」という名前のデモプロジェクトを作成しました。これは、独自のダウンローダー実装、Androidの組み込みDownloadManager
、およびいくつかの人気のあるオープンソースライブラリを使用して画像をダウンロード(および保存)する方法を示しています。完全なソースコードを表示するか、GitHubでプロジェクトをダウンロードできます。
注:SDK 23+(Marshmallow)の権限管理はまだ調整していないため、プロジェクトはSDK 22(Lollipop)を対象としています。
私の中で結論この記事の最後に、私は共有することになり、私の愚見私が述べてきた画像のダウンロードの各特定の方法のための適切なユースケースについてを。
独自の実装から始めましょう(コードは投稿の最後にあります)。まず第一に、これは基本的なImageDownloaderであり、それだけです。指定されたURLに接続し、データを読み取り、それをとしてデコードしようとし、必要に応じBitmap
てOnImageLoaderListener
インターフェイスコールバックをトリガーするだけです。このアプローチの利点-それは単純であり、何が起こっているのかを明確に把握できます。必要なのは、メモリ/ディスクキャッシュの維持を気にせずに、いくつかの画像をダウンロード/表示して保存することだけである場合に最適な方法です。
注:大きな画像の場合は、縮小する必要がある場合があります。
-
Android DownloadManagerは、システムにダウンロードを処理させる方法です。実際には、画像だけでなく、あらゆる種類のファイルをダウンロードできます。ダウンロードをサイレントに実行してユーザーに表示しないようにすることも、ユーザーが通知領域にダウンロードを表示できるようにすることもできます。BroadcastReceiver
ダウンロードが完了した後に通知を受け取るためにを登録することもできます。セットアップは非常に簡単です。サンプルコードについては、リンクされたプロジェクトを参照してください。
DownloadManager
ダウンロードBitmap
したファイルをに設定するだけでなく、保存したファイルを読み取ってデコードする必要があるため、画像も表示する場合は、通常、を使用することはお勧めできませんImageView
。またDownloadManager
、ダウンロードの進行状況を追跡するためのアプリ用のAPIも提供していません。
-
さて、素晴らしいもの、つまりライブラリの紹介です。メモリ/ディスクキャッシュの作成と管理、画像のサイズ変更、画像の変換など、画像のダウンロードと表示だけではありません。
まず、Googleによって作成され、公式ドキュメントでカバーされている強力なライブラリであるVolleyから始めます。Volleyは、画像に特化していない汎用ネットワークライブラリでありながら、画像を管理するための非常に強力なAPIを備えています。
ボレーリクエストを管理するためにシングルトンクラスを実装する必要があります。
あなたはあなたImageView
をボレーのものに置き換えたいかもしれません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が最適です。
-
SquareのPicassoは、画像の読み込みをすべて行う有名なライブラリです。ピカソを使用して画像を表示するだけで、次のように簡単になります。
Picasso.with(myContext)
.load(url)
.into(myImageView);
デフォルトでは、ピカソはディスク/メモリキャッシュを管理するので、それについて心配する必要はありません。より詳細に制御するには、Target
インターフェースを実装し、それを使用して画像をロードします。これにより、Volleyの例と同様のコールバックが提供されます。例については、デモプロジェクトを確認してください。
Picassoでは、ダウンロードした画像に変換を適用することもできます。また、これらのAPIを拡張する他のライブラリもあります。また、非常にうまく機能RecyclerView
/ ListView
/ GridView
。
-
Universal Image Loaderは、画像管理を目的としたもう1つの非常に人気のあるライブラリです。ImageLoader
(初期化されると)独自のグローバルインスタンスを使用します。このインスタンスを使用して、1行のコードで画像をダウンロードできます。
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は、明示的に指示しない限り、メモリ/ディスクキャッシュを維持します。
注:著者は、2015年11月27日をもってプロジェクトを維持しなくなったと述べています。しかし、多くの貢献者がいるため、Universal ImageLoaderが存続することを期待できます。
-
FacebookのFrescoは、画像管理を新しいレベルに引き上げる最新かつ(IMO)最先端のライブラリですBitmaps
。Javaヒープ(Lollipopより前)をオフにすることから、アニメーション形式やプログレッシブJPEGストリーミングをサポートすることまで。
Frescoの背後にあるアイデアとテクニックの詳細については、この投稿を参照してください。
基本的な使い方はとても簡単です。呼び出す必要があるのはFresco.initialize(Context);
1回だけで、Application
クラスでの使用が望ましいことに注意してください。Frescoを複数回初期化すると、予期しない動作やOOMエラーが発生する可能性があります。
FrescoはDrawee
sを使用して画像を表示しますが、次のように考えることができますImageView
。
<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がニーズに合うはずです。JSON
データを送受信し、画像を処理するが、それらがアプリの主な目的ではない場合は、Volleyを使用してください。それを見逃した場合は、デモプロジェクトの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;
}
}
}
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加