Androidで、ネットにある画像をImageViewで表示する(UrlImageView)
AndroidのImageViewで、インターネット上にある画像を表示する独自ImageViewを作成しました。
ソースはすべてgithubで管理してます。MIT Licenseで、自由にお使いください。
名前、UrlImageView。適当でごめんなさい。
https://github.com/sharakova/UrlImageView
利用方法とソースの内容も書きますが、若干違いがあるかも・・・。
このクラスの利点
- 画像のURLを指定するだけで、ネットにある画像を非同期で取得、キャッシュ、表示を簡単に実装できます。
- 本当に、ほとんど何も気にしなくても、利用できます。
- layoutファイルで、ImageViewからこのクラス(UrlImageView)に変更して、setImageUrl(String url);を呼び出すだけです。
- OnImageLoadListener のリスナ登録で、ネットから画像を読み込む前後で処理を実行できます。
- 読み込んだ画像は、一時的にAndroid内にキャッシュし、2度目の表示では高速に読み込む事ができます。
- サンプルソースのonDestroyで実行している ImageCache.deleteAll(getCacheDir()); で、Android内に保存したキャッシュを削除いたします。実行しなければ、アプリをアンインストールもしくはユーザの操作でキャッシュを削除するまでデータを保持します。
ちょっと内部的な話
- 複数のUrlImageViewを利用されると、画像の読み込み順番がLIFOの方がなにかと良かったので、画像を読み込む優先順位を変更しました。
- WorkerThreadを実装しました。スレッド数は5つ。
- GridViewなどで利用した場合、ユーザ操作で見たい画像がある程度先に読まれるようになった。
- 画像のキャッシュの圧縮率は90です。また、アニメーションGIFなどには対応してません。
- 画像のキャッシュデータをファイルに書き出す際に若干遅いので、今後の課題です。
- ファイルが存在しない場合(404 Not Found)とかの場合、処理が分けられないのも課題です。
<uses-permission android:name="android.permission.INTERNET"/>
layoutファイル
- レイアウトファイルでは、ImageViewと同様の設定が可能。設定例。
<jp.sharakova.android.urlimageview.UrlImageView android:id="@+id/imageView" android:layout_width="fill_parent" android:layout_height="fill_parent" />
Activity サンプル
package jp.sharakova.android.urlimageview.sample; import jp.sharakova.android.urlimageview.ImageCache; import jp.sharakova.android.urlimageview.R; import jp.sharakova.android.urlimageview.UrlImageView; import jp.sharakova.android.urlimageview.UrlImageView.OnImageLoadListener; import android.app.Activity; import android.os.Bundle; import android.widget.Toast; public class UrlImageViewSampleActivity extends Activity { UrlImageView mImageView; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mImageView = (UrlImageView)findViewById(R.id.imageView); mImageView.setImageUrl("http://k.yimg.jp/images/top/sp/logo.gif", imageLoadListener); } @Override public void onDestroy() { ImageCache.deleteAll(getCacheDir()); super.onDestroy(); } final private OnImageLoadListener imageLoadListener = new OnImageLoadListener() { @Override public void onStart(String url) { Toast.makeText(getApplicationContext(), "start", Toast.LENGTH_SHORT).show(); } @Override public void onComplete(String url) { Toast.makeText(getApplicationContext(), "end", Toast.LENGTH_SHORT).show(); } }; }
各ソースの説明
- UrlImageView.java
- ImageViewを拡張したViewクラス
- Channel.java
- WorkerThreadの生成
- Requestキュー管理 (Request Queue) LIFOを実装
- ImageCache.java
- メモリで画像データをキャッシュ。
- キャッシュディレクトリにファイルとして保存
- キャッシュデータの読み出し
- Request.java
- 読み込む画像のURL
- キャッシュディレクトリの保存
- WorkerThread.java
- ChannelからRequestを取得し、ネットから画像を読み込む
- Threadクラス
- ステータスの保持
UrlImageViewクラス
package jp.sharakova.android.urlimageview; import java.lang.ref.SoftReference; import android.content.Context; import android.graphics.Bitmap; import android.os.Handler; import android.util.AttributeSet; import android.widget.ImageView; public class UrlImageView extends ImageView { private final Context context; private Request request; private String url; private final Handler handler = new Handler(); private OnImageLoadListener listener = new OnImageLoadListener() { public void onStart(String url) { } public void onComplete(String url) { } }; public static interface OnImageLoadListener { public void onStart(String url); public void onComplete(String url); } private final Runnable threadRunnable = new Runnable() { public void run() { handler.post(imageLoadRunnable); } }; private final Runnable imageLoadRunnable = new Runnable(){ public void run(){ setImageLocalCache(); } }; private boolean setImageLocalCache() { SoftReference<Bitmap> image = ImageCache.getImage(context.getCacheDir(),url); if(image != null && image.get() != null) { setImageBitmap(image.get()); listener.onComplete(url); return true; } return false; } public UrlImageView(Context context) { super(context); this.context = context; } public UrlImageView(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; } public UrlImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.context = context; } public void setOnImageLoadListener(OnImageLoadListener listener) { this.listener = listener; } public void setImageUrl(String url, OnImageLoadListener listener) { setOnImageLoadListener(listener); setImageUrl(url); } public void setImageUrl(String url) { this.url = url; request = new Request(url, context.getCacheDir(), threadRunnable); if(setImageLocalCache()){ return; } listener.onStart(url); Channel.getInstance().putRequest(request); } }
Channelクラス
package jp.sharakova.android.urlimageview; import java.util.LinkedList; public class Channel { private static Channel instance; private static final int MAX_THREAD = 5; private final LinkedList<Request> requestQueue = new LinkedList<Request>(); private final WorkerThread[] threadPool; private Channel() { threadPool = new WorkerThread[MAX_THREAD]; for (int i = 0; i < threadPool.length; i++) { threadPool[i] = new WorkerThread("Worker-" + i, this); } } public static Channel getInstance() { if(instance == null) { instance = new Channel(); instance.startWorkers(); } return instance; } public synchronized void removeQueueAll() { requestQueue.clear(); } public void startWorkers() { for (WorkerThread thread : threadPool) { thread.start(); } } public void stopWorkers() { for (WorkerThread thread : threadPool) { thread.stop(); } } public synchronized void putRequest(Request request) { requestQueue.addFirst(request); notifyAll(); } public synchronized Request takeRequest() { while (requestQueue.size() <= 0) { try { wait(); } catch (InterruptedException e) { } } return requestQueue.poll(); } }
ImageCacheクラス
package jp.sharakova.android.urlimageview; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.ref.SoftReference; import java.util.HashMap; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; public class ImageCache { private static HashMap<String,SoftReference<Bitmap>> cache = new HashMap<String,SoftReference<Bitmap>>(); private static String getFileName(String url) { int hash = url.hashCode(); return String.valueOf(hash); } public static void saveBitmap(File cacheDir, String url, Bitmap bitmap) { cache.put(url, new SoftReference<Bitmap>(bitmap)); String fileName = getFileName(url); File localFile = new File(cacheDir, fileName); FileOutputStream fos = null; try { fos = new FileOutputStream(localFile); bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos); } catch (Exception e) { e.printStackTrace(); } catch (OutOfMemoryError e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.close(); } catch (IOException e1) { Log.w("save", "finally"); } } } } public static SoftReference<Bitmap> getImage(File cacheDir, String url) { SoftReference<Bitmap> ref = cache.get(url); if(ref != null && ref.get() != null) { return ref; } String fileName = getFileName(url); File localFile = new File(cacheDir, fileName); SoftReference<Bitmap> bitmap = null; try { bitmap = new SoftReference<Bitmap>(BitmapFactory.decodeFile(localFile.getPath())); } catch (Exception e) { e.printStackTrace(); } catch (OutOfMemoryError e) { e.printStackTrace(); } return bitmap; } public static void deleteAll(File cacheDir) { if (!cacheDir.isDirectory()){ return; } File[] files = cacheDir.listFiles(); for (File file : files) { if (file.isFile()){ file.delete(); } } } }
Requestクラス
package jp.sharakova.android.urlimageview; import java.io.File; public class Request { private final String url; private final File cacheDir; private final Runnable runnable; private int status = Status.WAIT; public interface Status { int WAIT = 0; int LOADING = 1; int LOADED = 2; } public Request(String url, File cacheDir, Runnable runnable) { this.url = url; this.cacheDir = cacheDir; this.runnable = runnable; } public synchronized void setStatus(int status) { this.status = status; } public int getStatus() { return status; } public String getUrl() { return url; } public File getCacheDir() { return cacheDir; } public Runnable getRunnable() { return (runnable != null)? runnable : getDefaultRunnable(); } private Runnable getDefaultRunnable() { return new Runnable() { public void run() { } }; } }
WorkerThreadクラス
package jp.sharakova.android.urlimageview; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.lang.ref.SoftReference; import java.net.HttpURLConnection; import java.net.URL; import android.graphics.Bitmap; import android.graphics.BitmapFactory; public class WorkerThread extends Thread { private final Channel channel; public WorkerThread(String name, Channel channel) { super(name); this.channel = channel; } public void run() { while (true) { Request request = channel.takeRequest(); request.setStatus(Request.Status.LOADING); SoftReference<Bitmap> image = ImageCache.getImage(request.getCacheDir(), request.getUrl()); if(image == null || image.get() == null) { image = getImage(request.getUrl()); if(image != null && image.get() != null) { ImageCache.saveBitmap(request.getCacheDir(), request.getUrl(), image.get()); } } request.setStatus(Request.Status.LOADED); request.getRunnable().run(); } } private SoftReference<Bitmap> getImage(String url) { try { byte[] byteArray = getByteArrayFromURL(url); return new SoftReference<Bitmap>(BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length)); } catch (Exception e) { e.printStackTrace(); return null; } catch (OutOfMemoryError e) { e.printStackTrace(); return null; } } private byte[] getByteArrayFromURL(String strUrl) { byte[] byteArray = new byte[1024]; HttpURLConnection con = null; InputStream in = null; ByteArrayOutputStream out = null; int size = 0; try { URL url = new URL(strUrl); con = (HttpURLConnection) url.openConnection(); con.setUseCaches(true); con.setRequestMethod("GET"); con.connect(); in = con.getInputStream(); out = new ByteArrayOutputStream(); while ((size = in.read(byteArray)) != -1) { out.write(byteArray, 0, size); } return out.toByteArray(); } catch (Exception e) { e.printStackTrace(); return new byte[0]; } catch (OutOfMemoryError e) { e.printStackTrace(); return new byte[0]; } finally { try { if (con != null) con.disconnect(); if (in != null) in.close(); if (out != null) out.close(); } catch (Exception e) { e.printStackTrace(); } } } }