在写适配器时从网络加载图片会遇到当停止滑动滑动列表框之后,列表框中的图片会不断的切换。
这是自定义适配器时重用convertView要注意的地方,这里的解决办法通常是在getView(…)方法中重用convertView时给ImageView设置一个标签setTag(…),然后在异步任务执行完之后,即在获取到图片数据之后,给ImageView设置图片数据之前判断一下此时的tag还是不是最后设置的tag了。是则显示。
下面的类在用调用loadImage(…)之前要先进行初始化init(…)。
public class ImageLoader {
//线程池中线程的数量,为了保证最好的并发性
//这个数量与设备CPU的盒数一样
private static int threadCount;
//线程池
private static ExecutorService exec;
//上下文对象
private static Context context;
//当线程池中的工作线程获得图像后
//需要将图像通过uiHandler提交到主线程
//进而在ImageView中进行展示
private static Handler uiHandler;
//当生产者向任务队列中添加了需要执行的任务后
//生产者会向该poolHandler发送一个Message
//通知它区域任务队列中取任务放到线程池中执行
private static Handler pollHandler;
//与pollHandler相依相偎的一个工作线程
//pollHandler把收到的Message都提交到该线程
//该线程的looper从MessagQueue中吧消息取出再返回给pollHandler执行
private static Thread pollThread;
//任务队列
//生产者将任务放到该队列中
//消费者从该队列中取任务执行
private static LinkedBlockingDeque<Runnable> tasks;
//为下载图片提供内存缓存
//其中键为图片的url地址转的MD5字符串,值为图片本身
private static LruCache<String, Bitmap> memCache;
//如果所有相关属性都未做初始化,则isFirst为true
//一旦做了初始化,isFirst的值就为false
private static boolean isFirst = true;
//信号量,用来控制线程池可取任务量
private static Semaphore pollLock;
//当主线程加载加载图像时,向pollhandler发送消息
//为了
private static Semaphore pollHandlerLock = new Semaphore(0);
//磁盘缓存对象
private static DiskLruCache diskCache;
/**
* ImageLoaderd 初始化方法
* 把上述所有属性都要进行赋值
* @param c
*/
public static void init(Context c){
if(!isFirst){
return;
}
isFirst = false;
context = c;
tasks = new LinkedBlockingDeque<Runnable>();
threadCount = getCoreNumbers();
//创建与线程池中任务数量相同
pollLock = new Semaphore(threadCount);
//创建线程池
exec = Executors.newFixedThreadPool(threadCount);
pollThread = new Thread(){
@Override
public void run() {
Looper.prepare();
pollHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//一旦该pollHandler收到消息就意味着任务队列中有了任务,就去取任务放到线程池中执行
try {
Runnable task = tasks.getLast();
exec.execute(task);
pollLock.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//释放许可
pollHandlerLock.release();
Looper.loop();
}
};
Log.i("TAg", "pollThread = " + pollThread);
pollThread.start();
//构建主线程的handler
uiHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
//将获得的图片放到ImageView中显示
//需要解决一个“反复”显示的问题
//TODO
TaskBean bean = (TaskBean) msg.obj;
if(bean.tag.equals((String)bean.iv.getTag())){
bean.iv.setImageBitmap(bean.bitMap);
}
}
};
//初始化内存缓存
memCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory()/8)){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getHeight()*value.getRowBytes();
}
};
//初始化磁盘缓存
try {
diskCache = DiskLruCache.open(getCacheDir(), 1, 1, 1024*1024*8);
} catch (IOException e) {
e.printStackTrace();
}
}
private static File getCacheDir() {
String dir = context.getCacheDir().getPath();
String name = "imageloadcache";
return new File(dir,name);
}
/**
* 加载指定位置的图形到imageview中显示
* @param iv
* @param url
*/
public static void loadImage(final ImageView iv,final String url){
Bitmap result = null;
final String tag = getMD5(url);
result = memCache.get(tag);
iv.setTag(tag);
if(result!=null){
Log.d("TAG", "图片从内存缓存中加载");
iv.setImageBitmap(result);
return;
}
try {
Snapshot snap = diskCache.get(tag);
if(snap!=null){
//说明磁盘缓存中y有
Log.d("TAG", "图片从磁盘缓存中取");
InputStream in = snap.getInputStream(0);
result = BitmapFactory.decodeStream(in);
memCache.put(tag, result);
iv.setImageBitmap(result);
in.close();
return;
}
} catch (IOException e1) {
e1.printStackTrace();
}
//如果缓存中么有,添加到任务队列中,去做下载
tasks.add(new Runnable() {
@Override
public void run() {
//去指定url指定下载图片
try {
URL u = new URL(url);
HttpURLConnection connect = (HttpURLConnection) u.openConnection();
connect.setRequestMethod("GET");
connect.setDoInput(true);
connect.connect();
InputStream in = connect.getInputStream();
Bitmap bitmap = compress(iv,in);
in.close();
//将下载的图片放到缓存中缓存
memCache.put(tag, bitmap);
//将下载的图片放到磁盘缓存中进行缓存
Editor editor = diskCache.edit(tag);
OutputStream out = editor.newOutputStream(0);
bitmap.compress(CompressFormat.JPEG, 100, out);
//提交
editor.commit();
//写diskLruCache的日志文件
diskCache.flush();
//用一个bean ,两个属性,一个属性引用Bitmap,一个引用imageView
TaskBean bean = new TaskBean();
bean.bitMap = bitmap;
bean.iv = iv;
bean.tag = tag;
Message.obtain(uiHandler, 101, bean).sendToTarget();
//释放一个许可,允许线程池继续去取任务
pollLock.release();
} catch (Exception e) {
}
}
});
//通知pollHandler去取任务
if(pollHandler==null){
//等待
//获取一个“许可”
try {
pollHandlerLock.acquire();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Message.obtain(pollHandler).sendToTarget();
}
/**
* 根据ImageView的大小对图片进行压缩并显示
* @param iv
* @param in
* @return
*/
protected static Bitmap compress(ImageView iv, InputStream in) {
//先尝试获得ImageView的大小
int width = iv.getWidth();//有可能得到0,当iv的宽高设定为warpcontent时
int height = iv.getHeight();//同上
if(width==0 || height==0){
//怎么办
//折中方式1)用固定尺寸100dp?150dp?
// 2)用设备屏幕的宽/高
//第一种方式,是使用TypedValue类,用法参考友录
//第二种方法
width = context.getResources().getDisplayMetrics().widthPixels;
height = context.getResources().getDisplayMetrics().heightPixels;
}
Bitmap bitMap = null;
try {
//获得图像实际的宽/高
ByteArrayOutputStream out = new ByteArrayOutputStream();
int len = -1;
while((len = in.read())!=-1){
out.write(len);
}
byte[] bytes = out.toByteArray();
Options opts = new Options();
opts.inJustDecodeBounds = true;
out.close();
bitMap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
int bitMapWidth = opts.outWidth;
int bitMapHeight = opts.outHeight;
//压缩的比例就取决于图片的宽高与前面计算的比值
int sampleSize = 1;
//如果图形的宽或图形高大于我希望的宽或者高就进行压缩
if(bitMapWidth*1.0/width>1 || bitMapHeight*1.0/height>1){
sampleSize = (int) Math.ceil(Math.max(bitMapHeight*1.0/height, bitMapWidth*1.0/width));
}
opts.inSampleSize = sampleSize;
opts.inJustDecodeBounds = false;
bitMap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return bitMap;
}
/**
* 把一个普通的字符串转成MD5格式的字符串
* @param url
* @return
*/
private static String getMD5(String str) {
StringBuffer sb = new StringBuffer();
try{
//获得摘要对象
MessageDigest md = MessageDigest.getInstance("MD5");
//转换str-->md5
md.update(str.getBytes());
byte[] bytes = md.digest();
for (byte b : bytes) {
//把每一个byte数据做一下格式化
String temp = Integer.toHexString(b & 0xFF);
if(temp.length()==1){
sb.append("0");
}
sb.append(temp);
}
}catch(Exception e){
e.printStackTrace();
}
return sb.toString();
}
/*
*安卓系统有一个路径/sys/devices/system
*该路径下有N多个文件来描述系统的资源
*其中与CPU相关的描述文件都在
* /sys/devices/system/cpu路径下面
* 如果设备CPU是一个核,他的描述文件就是
* /sys/devices/system/cpu/cpu0/xxxx
* 有多少cpux文件夹就有多少个cpu个数
* @return
*/
private static int getCoreNumbers() {
File[] files;
try {
File file = new File("/sys/devices/system");
FilenameFilter filter = new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
if(filename.contains("cpu"))
return true;
return false;
}
};
files = file.listFiles(filter);
} catch (Exception e) {
e.printStackTrace();
return 1;
}
return files.length;
}
private static class TaskBean{
Bitmap bitMap;
ImageView iv;
String tag;
}
/**
* 如果返回true,意味着初始化未完成
*
*
* @return
*/
public static boolean isFirst(){
return isFirst;
}
}