duyojiぶろぐ

技術系ときどき日常系

UrlImageViewの動きを早くしてみた

Android開発でURLの画象を読み込むときにお世話になっているsharakovaさんの
UrlImageView(元のUrlImageViewのgithub)のコードを少しいじってキャッシュ時の動きを早くしてみました。

動きが重くなっていた場所

sharakovaさんのREADMEで
「画像のキャッシュデータをファイルに書き出す際に若干もたつくので、今後の課題です。」と
書いてあるとおり今回作るアプリではキャッシュ時に呼んでいるImageCache#saveBitmapメソッド内の
以下の部分が動きが重くなっていた原因でした。

bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);

理由はよくわかりませんが、Bitmapデータを圧縮するのに時間かかるんだなー
と考えたので圧縮しないで、データを都度書きこむ形にしようと思い今回UrlImageViewを
改造しました。

改造した部分

ImageCache.java

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 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));					
	}

	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();
			cache.clear();
		}
		return bitmap;
	}

	public static void memoryCacheClear() {
		cache.clear();
	}

	public static void deleteAll(File cacheDir) {
		if (!cacheDir.isDirectory()) {
			return;
		}
		File[] files = cacheDir.listFiles();
		for (File file : files) {
			if (file.isFile()) {
				if (!file.delete()) {
					Log.v("file", "file delete false");
				}
			}
		}
	}

	public static long dirSize(File cacheDir) {
		long size = 0L;
		if (cacheDir == null) {
			return size;
		}
		if (cacheDir.isDirectory()) {
			for (File file : cacheDir.listFiles()) {
				size += file.length();
			}
		} else {
			size = cacheDir.length();
		}
		return size;
	}
}

WorkerThread.java

package jp.sharakova.android.urlimageview;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
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 final class WorkerThread extends Thread {
	private final Channel channel;

	public WorkerThread(String name, Channel channel) {
		super(name);
		this.channel = channel;
	}

	@Override
	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);				
				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(Request request) {
		try {
			return new SoftReference<Bitmap>(getBitmapFromURL(request));
		} catch (Exception e) {
			e.printStackTrace();
			return null;			
		} catch (OutOfMemoryError e) {
			e.printStackTrace();
			return null;
		}
	}
	
	
	private Bitmap getBitmapFromURL(Request request) throws IOException {
		String fileName = ImageCache.getFileName(request.getUrl());
		File localFile = new File(request.getCacheDir(), fileName);
		FileOutputStream fos = null;
		
		HttpURLConnection con = null;
		InputStream in = null;

		try {
			URL url = new URL(request.getUrl());
			con = (HttpURLConnection) url.openConnection();
			con.setUseCaches(true);
			con.setRequestMethod("GET");
			con.setReadTimeout(500000);
			con.setConnectTimeout(50000);
			con.connect();
			in = con.getInputStream();
			
			//ImageCache#saveBitmap内で行っているbitmap.compress(Bitmap.CompressFormat.PNG, 90, fos)が重いためこちらに変更
			byte[] buf = new byte[1024];				
			int len = 0;
			fos = new FileOutputStream(localFile);
			while((len = in.read(buf)) > -1){
				fos.write(buf, 0, len);
			}				
			fos.flush();
		} finally {
			try {
				if (con != null)
					con.disconnect();
				if (in != null)
					in.close();
				if (fos != null)
					fos.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		return BitmapFactory.decodeFile(localFile.getPath());
	}

}

ImageCache#saveBitmapメソッド内では引数で渡されてきたbitmapを
HashMapに保存するだけの動きに変更しました。

WorkerThreadではgetBitmapFromURLメソッドの中身を少し手を加えて
以下の「in」にinputStreamのインスタンスを保存するところは同じですが、

URL url = new URL(request.getUrl());
con = (HttpURLConnection) url.openConnection();
con.setUseCaches(true);
con.setRequestMethod("GET");
con.setReadTimeout(500000);
con.setConnectTimeout(50000);
con.connect();
in = con.getInputStream();

その後以下のようにinputStreamインスタンスからbitmapをすぐに生成するのではなく
まず一度端末内にlocalFile(Fileのインスタンス)で指定したところにFileOutputStreamで
データを書きこんで、最後にBitmapを戻り値として返すためにBitmapFactory.decodeFileで
画象を保存したパスを指定してBitmapを返している。

byte[] buf = new byte[1024];				
int len = 0;
fos = new FileOutputStream(localFile);
while((len = in.read(buf)) > -1){
    fos.write(buf, 0, len);
}				
fos.flush();

・・・略

return BitmapFactory.decodeFile(localFile.getPath());

改造したUrlImageViewはgithubに

改造したUrlImageViewは僕のgithub
アップしておきました。

URL https://github.com/duyoji/UrlImageView