このチュートリアルでは、この状況を説明する画像ダウンローダーを作成します。 ネットワークからダウンロードした画像のピクトグラムをListViewに取り込みます。 バックグラウンドで画像をロードする作成された非同期プロセスは、アプリケーションを高速化します。
画像ダウンローダー
インターネットからの画像のダウンロードは非常に簡単で、フレームワークの関連するHTTPクラスを使用して行われます。 実装の1つを次に示します。
static Bitmap downloadBitmap( String url) {
final AndroidHttpClient client = AndroidHttpClient.newInstance( "Android" );
final HttpGet getRequest = new HttpGet(url);
try {
HttpResponse response = client.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.w( "ImageDownloader" , "Error " + statusCode + " while retrieving bitmap from " + url);
return null ;
}
final HttpEntity entity = response.getEntity();
if (entity != null ) {
InputStream inputStream = null ;
try {
inputStream = entity.getContent();
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
} finally {
if (inputStream != null ) {
inputStream.close();
}
entity.consumeContent();
}
}
} catch (Exception e) {
// IOException IllegalStateException
getRequest.abort();
Log.w( "ImageDownloader" , "Error while retrieving bitmap from " + url, e.toString());
} finally {
if (client != null ) {
client.close();
}
}
return null ;
}
* This source code was highlighted with Source Code Highlighter .
クライアントとHTTPリクエストが作成されました。 要求が成功すると、応答ストリームに画像が含まれ、最終的なアイコンに変わります。 アプリケーションが適切に機能するには、そのマニフェストにINTERNETが必要です。
注意: BitmapFactory.decodeStreamの以前のバージョンのバグは、低速接続中のコードの実行を妨げる可能性があります。 この問題を回避するには、FlushedInputStream(inputStream)を使用します。 このヘルパークラスの例を次に示します。
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
@Override
public long skip( long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in .skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int byte = read();
if ( byte < 0) {
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}
これにより、ファイルの最後に到達したときにskip()が実際にそのバイト数をスキップすることが保証されます。
ListAdapterの ListViewメソッドでこのメソッドを直接使用すると、非常に断続的なスクロールが発生します。
さらに、これは非常に悪い考えです。なぜなら、 AndroidHttpClientは、メインスレッドと一緒に起動できません。 代わりに、このコードは「このスレッドはHTTP要求を禁止します」というエラーを表示します(「このスレッドはHTTP要求を失います」)。 ホイールにスティックを本当に挿入したい場合は、 DefaultHttpClientを使用することをお勧めします。
非同期タスクについて
AsyncTaskクラスは、ユーザーインターフェイススレッドから直接新しいタスクを起動する最も簡単な方法の1つを提供します。 これらのタスクを作成するImageDownloaderクラスを作成しましょう。 ダウンロードした画像をURLからImageViewに割り当てるダウンロード方法を提供します :
public class ImageDownloader {
public void download( String url, ImageView imageView) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
task.execute(url);
}
}
/* BitmapDownloaderTask, */
}
* This source code was highlighted with Source Code Highlighter .
BitmapDownloaderTaskは、実際に画像を揺さぶる非同期タスクです。 executeの使用を開始すると 、すぐに値が返され、このメソッドが非常に高速になります。 このクラスの実装例は次のとおりです。
class BitmapDownloaderTask extends AsyncTask< String , Void, Bitmap > {
private String url;
private final WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
// Actual download method, run in the task thread
protected Bitmap doInBackground( String ... params ) {
// params comes from the execute() call: params[0] is the url.
return downloadBitmap( params [0]);
}
@Override
// Once the image is downloaded, associates it to the imageView
protected void onPostExecute( Bitmap bitmap) {
if (isCancelled()) {
bitmap = null ;
}
if (imageViewReference != null ) {
ImageView imageView = imageViewReference. get ();
if (imageView != null</fonthttp://habrahabr.ru/edit/topic/99631/#>) {
imageView.setImageBitmap(bitmap);
}}}}
doInBackgroundメソッドは、実際には独自のプロセスでタスクを処理します。 この記事の冒頭で紹介したdownloadBitmapメソッドを使用するだけです。
onPostExecuteは、タスクが完了すると、呼び出されたUIスレッドで実行されます。 結果のイメージを、 ダウンロードからimageViewに関連付けられ、 BitmapDownloaderTaskに保存されたパラメーターとして取得します 。 imageViewはWeakReferenceとして保存されるため、ロードプロセス中に収集されたガベージを消去しても問題はありません。 これにより、 onPostExecuteで使用する前に、リンクとimageViewの両方がゼロ以外(つまり、コンパイルされていない)であることを確認する必要がある理由が説明されます 。
この例では、 AsyncTaskの簡単な使用方法を示しています。このメソッドを試してみると、これらの数行のコードがListViewのパフォーマンスを大幅に向上させることがわかります。
ただし、 ListViewの特定の動作により、実装で問題が発生します。 さらに、効率のため、 ListViewはスクロール中に画面に表示される画像を処理します。 1つのオブジェクトがスキップされると、 imageViewで複数回使用されます。 また、ロード中にアイコンが正しく表示され、ロード後に画像に置き換えられます。 しかし、問題は何ですか? ほとんどの同時実行アプリケーションと同様に、全体の問題は順序付けです。 私たちの場合、タスクのダウンロードがダウンロードを開始したのと同じ順序で終了するという保証はありません。 結果は、表示された画像がリスト内の前のオブジェクトを参照するようになる可能性があります。 ロードに時間がかかりました。 ダウンロードした画像が一度リンクされ、 ImageViewsがすべてに設定されている場合、これは問題ではありませんが、リストとして使用される一般的なケースでこれを修正しましょう。
同時実行管理
この問題を解決するには、ダウンロードを開始する順序を覚えておく必要があります。最後に開始したダウンロードが表示(?)に最も効果的だからです。 実際、各ImageViewが最後のダウンロードを記憶するのに十分です。 ロード中にImageViewと通信する特別なDrawableサブクラスを使用して、この追加情報を設定します。 DownloadedDrawableクラスのコードは次のとおりです。
static class DownloadedDrawable extends ColorDrawable {
private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
super(Color.BLACK);
bitmapDownloaderTaskReference =
new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
}
public BitmapDownloaderTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference. get ();
}
}
これはColorDrawableを使用した実装であり、 ImageViewの結果はロード時に黒い画面になります。 「ダウンロード中」の画像を使用して、ユーザーに通知できます。 ここでも、 WeakReferenceを使用して依存関係を減らすことに注意してください。
新しいクラスを考慮してコードを変更しましょう。 まず、 ダウンロードメソッドはこのクラスをインスタンス化し、それをimageViewに関連付ける必要があります。
public void download( String url, ImageView imageView) {
if (cancelPotentialDownload(url, imageView)) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
task.execute(url, cookie);
}
}
cancelPotentialDownloadメソッドは、間もなく開始される可能性のあるimageViewダウンロードを停止します。 これは、タスクを完了し、ロード後にのみ完了することができるonPostExecuteメソッドを待機するため、最新のダウンロードが常に表示されることを保証するのに十分ではないことに注意してください。
private static boolean cancelPotentialDownload( String url, ImageView imageView) {
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (bitmapDownloaderTask != null ) {
String bitmapUrl = bitmapDownloaderTask.url;
if ((bitmapUrl == null ) || (!bitmapUrl.equals(url))) {
bitmapDownloaderTask.cancel( true );
} else {
// The same URL is already being downloaded.
return false ;
}
}
return true ;
}
cancelPotentialDownloadは、 AsyncTaskクラスのcancelメソッドを使用して、既存のダウンロードを停止します。 ほとんどの場合trueを返すため、downloadでダウンロードを開始できます 。 これが望ましくない唯一のケースは、ダウンロードが同じURLから来た場合に、それを終了させます。 この実装では、 ImageViewがガベージを収集した場合、関連するダウンロードが停止しないことに注意してください。 これには、 RecyclerListenerを使用する必要があります。
このメソッドはgetBitmapDownloaderTaskヘルパー関数を使用します。
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null ) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null ;
}
最後に、 onViewExecuteを変更して、 ImageViewがこの特定の読み込みプロセスにまだ関連付けられている場合にのみアイコンを関連付けるようにする必要があります。
if (imageViewReference != null ) {
ImageView imageView = imageViewReference. get ();
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
// Change bitmap only if this process is still associated with it
if ( this == bitmapDownloaderTask) {
imageView.setImageBitmap(bitmap);
}
}
これらの変更により、 ImageDownloaderクラスは、期待していた基本的なサービスを提供するようになりました。 自由に使用するか、アプリケーションの柔軟性を提供する非同期テンプレートを使用してください。
デモンストレーション
このアプリケーションのソースコードは、Google Codeで入手できます 。 この記事で説明する3つの異なる実装を切り替えることができます。 さまざまな方法でキャッシュがどのように機能するかをより良く示すために、キャッシュサイズは10個の画像に制限されていたことを思い出してください。
未来のために
このコードは、並列処理や実装にない他の多くの機能に注意を集中させるために簡素化されています。 ImageDownloaderクラスは、特にListViewと組み合わせて使用すると、ユーザーが画面を上下にスクロールするのと同じ回数だけ同じ画像を表示する可能性が高いため、キャッシュの恩恵を受けます。 また、必要に応じて、サムネイルを追加して画像のサイズを変更できます。
実装ではダウンロードエラーとタイムアウトが正しく表示されます。この場合、単に空白の画像が生成されます。 必要に応じて、エラーを報告する画像を表示できます。
HTTPリクエストは非常にシンプルです。 一部のサイトに必要なオプションを追加したり、Cookieを有効にしたりできます。
この記事で使用される
AsyncTask
クラスは、ユーザーインターフェイスの負荷を軽減するための非常に便利な方法です。
Handler
クラスを使用して、同時ダウンロードの総数の制御など、アクションをより細かく制御することもできます。
UPD。 コメントをありがとう、修正しました。