Android 中加载图片的工作频繁且重复,找一款好的组件使用是很顺手的事情。开源框架 ImageLoader 用起来还不错,入门参考 http://blog.csdn.net/hhhccckkk/article/details/8898651
在项目中用了一段时间后,发现一些可以改进的地方:
(1)每次访问网络取图片,发现加载器总是会两次访问同一个地址,对于 GPRS 这样的蜗牛网速来说,这可不是什么好事。找找原因,起初以为是没有缓存在内存或SD卡,后来一一排除,原来是因为有段代码访问了两次,似乎开发者没有找到什么特别好的办法解决。本人尝试了几种办法,发现是可以改进的。
(2)我接受的方法是在 Application 中初始化加载器的时候要使用自己扩展的 ImageDecoder
protected void initImageCache() {
/**
* 初始化图片加载
*/
Logger.d(TAG, "Initializing image loader.");
File cacheDir = StorageUtils.getOwnCacheDirectory(getApplicationContext(), "myApplication/Cache");
ImageLoaderConfiguration.Builder builder = new ImageLoaderConfiguration.Builder(getApplicationContext());
builder.threadPoolSize(3); // 设置线程数量为3
builder.threadPriority(Thread.NORM_PRIORITY - 1); // 设定线程等级比普通低一点
builder.memoryCacheExtraOptions(200, 200); // 设定缓存在内存的图片大小最大为200x200
builder.memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024));
builder.discCache(new UnlimitedDiscCache(cacheDir)) ;
builder.discCacheExtraOptions(displayMetrics.widthPixels, displayMetrics.heightPixels, CompressFormat.JPEG, 80, null);
builder.denyCacheImageMultipleSizesInMemory(); // 拒绝缓存同一图片,有不同的大小
builder.discCacheFileNameGenerator(new Md5FileNameGenerator());
builder.imageDownloader(new MyImageLoader(getApplicationContext(),restClient));//new BaseImageDownloader(getApplicationContext()));//
builder.imageDecoder(new MyImageDecoder(true));
builder.enableLogging(); // 开启调试
// 设置默认显示情况
DisplayImageOptions.Builder displayImageOptionsBuilder = new DisplayImageOptions.Builder();
displayImageOptionsBuilder.showImageForEmptyUri(R.drawable.house_icon_photo4); // 空uri的情况
displayImageOptionsBuilder.cacheInMemory(true); // 缓存在内存
displayImageOptionsBuilder.cacheOnDisc(true); // 缓存在磁盘
displayImageOptionsBuilder.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2);
builder.defaultDisplayImageOptions(displayImageOptionsBuilder.build());
ImageLoader.getInstance().init(builder.build());
Logger.d(TAG, "Initialize image loader finished.");
}
(2)MyImageDecoder.java 扩展自 BaseImageDecoder
import java.io.IOException;
import java.io.InputStream;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory.Options;
import myapp.sdk.io.CloseShieldInputStream;
import com.nostra13.universalimageloader.core.assist.ImageSize;
import com.nostra13.universalimageloader.core.decode.BaseImageDecoder;
import com.nostra13.universalimageloader.core.decode.ImageDecodingInfo;
import com.nostra13.universalimageloader.utils.L;
public class MyImageDecoder extends BaseImageDecoder{
public MyImageDecoder(boolean loggingEnabled) {
this.loggingEnabled = loggingEnabled;
}
@Override
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
InputStream imageStream = getImageStream(decodingInfo);
// inputStream mark
imageStream.mark(imageStream.available()+1);
InputStream imageStreamClone = new CloseShieldInputStream(imageStream);
ImageFileInfo imageInfo = defineImageSizeAndRotation(imageStreamClone, decodingInfo.getImageUri());
Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
// imageStream = getImageStream(decodingInfo);//michael remove double load from server
// inputStream reset
imageStream.reset();
Bitmap decodedBitmap = decodeStream(imageStream, decodingOptions);
if (decodedBitmap == null) {
L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
} else {
decodedBitmap = considerExactScaleAndOrientaiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation, imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}
}
(3)CloseShieldInputStream.java 可以直接使用 apache common-io,但在 android 上为了克隆 InputStream 一点点功能,而引入整个 IO 包,100多KB,似乎不划算,所以单独复制了 IO 包中的几个文件:
import java.io.InputStream;
/***
* Proxy stream that prevents the underlying input stream from being closed.
* <p>
* This class is typically used in cases where an input stream needs to be
* passed to a component that wants to explicitly close the stream even if
* more input would still be available to other components.
*
* @version $Id: CloseShieldInputStream.java 587913 2007-10-24 15:47:30Z niallp $
* @since Commons IO 1.4
*/
public class CloseShieldInputStream extends ProxyInputStream {
/***
* Creates a proxy that shields the given input stream from being
* closed.
*
* @param in underlying input stream
*/
public CloseShieldInputStream(InputStream in) {
super(in);
}
/***
* Replaces the underlying input stream with a {@link ClosedInputStream}
* sentinel. The original input stream will remain open, but this proxy
* will appear closed.
*/
public void close() {
in = new ClosedInputStream();
}
}
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/***
* A Proxy stream which acts as expected, that is it passes the method
* calls on to the proxied stream and doesn't change which methods are
* being called.
* <p>
* It is an alternative base class to FilterInputStream
* to increase reusability, because FilterInputStream changes the
* methods being called, such as read(byte[]) to read(byte[], int, int).
*
* @author Stephen Colebourne
* @version $Id: ProxyInputStream.java 610010 2008-01-08 14:50:59Z niallp $
*/
public abstract class ProxyInputStream extends FilterInputStream {
/***
* Constructs a new ProxyInputStream.
*
* @param proxy the InputStream to delegate to
*/
public ProxyInputStream(InputStream proxy) {
super(proxy);
// the proxy is stored in a protected superclass variable named 'in'
}
/***
* Invokes the delegate's <code>read()</code> method.
* @return the byte read or -1 if the end of stream
* @throws IOException if an I/O error occurs
*/
public int read() throws IOException {
return in.read();
}
/***
* Invokes the delegate's <code>read(byte[])</code> method.
* @param bts the buffer to read the bytes into
* @return the number of bytes read or -1 if the end of stream
* @throws IOException if an I/O error occurs
*/
public int read(byte[] bts) throws IOException {
return in.read(bts);
}
/***
* Invokes the delegate's <code>read(byte[], int, int)</code> method.
* @param bts the buffer to read the bytes into
* @param st The start offset
* @param end The number of bytes to read
* @return the number of bytes read or -1 if the end of stream
* @throws IOException if an I/O error occurs
*/
public int read(byte[] bts, int st, int end) throws IOException {
return in.read(bts, st, end);
}
/***
* Invokes the delegate's <code>skip(long)</code> method.
* @param ln the number of bytes to skip
* @return the number of bytes to skipped or -1 if the end of stream
* @throws IOException if an I/O error occurs
*/
public long skip(long ln) throws IOException {
return in.skip(ln);
}
/***
* Invokes the delegate's <code>available()</code> method.
* @return the number of available bytes
* @throws IOException if an I/O error occurs
*/
public int available() throws IOException {
return in.available();
}
/***
* Invokes the delegate's <code>close()</code> method.
* @throws IOException if an I/O error occurs
*/
public void close() throws IOException {
in.close();
}
/***
* Invokes the delegate's <code>mark(int)</code> method.
* @param idx read ahead limit
*/
public synchronized void mark(int idx) {
in.mark(idx);
}
/***
* Invokes the delegate's <code>reset()</code> method.
* @throws IOException if an I/O error occurs
*/
public synchronized void reset() throws IOException {
in.reset();
}
/***
* Invokes the delegate's <code>markSupported()</code> method.
* @return true if mark is supported, otherwise false
*/
public boolean markSupported() {
return in.markSupported();
}
}
import java.io.InputStream;
/***
* Closed input stream. This stream returns -1 to all attempts to read
* something from the stream.
* <p>
* Typically uses of this class include testing for corner cases in methods
* that accept input streams and acting as a sentinel value instead of a
* <code>null</code> input stream.
*
* @version $Id: ClosedInputStream.java 601751 2007-12-06 14:55:45Z niallp $
* @since Commons IO 1.4
*/
public class ClosedInputStream extends InputStream {
/***
* A singleton.
*/
public static final ClosedInputStream CLOSED_INPUT_STREAM = new ClosedInputStream();
/***
* Returns -1 to indicate that the stream is closed.
*
* @return always -1
*/
public int read() {
return -1;
}
}
(4)ImageLoader 会多次反复发送加载请求,对网络也是个灾难,在扩展的 MyImageLoader 中修改网络连接超时和读取网络超时的时间值
import android.content.Context;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
public class MyImageLoader extends BaseImageDownloader {
public MyImageLoader(Context context) {
super(context,10000,60000);
}
}