一.简单异步加载图片的方式(在listview中显示从网络下载的图片)
利用AsyncTask启动一个异步任务,在doInBackground方法中,根据图片的地址下载图像。当图像下载完毕后会将图像作为方法的返回结果提交到onPostExecute方法的参数中。在onPostExecute方法中将该图像放到ImageView中进行显示。
应用场合:图片加载量比较小的场合,可以使用这种方式进行加载。
当简单加载图像方式遇到了ListView可能产生的问题:
1)ListView的条目复用机制导致的图片加载的“动画”效果
2)随着ListView滚动,后台会启动大量的异步任务,影响程序的流畅度
3)下载图像时不能直接使用,而需要进行压缩处理
4)使用缓存技术。只有在迫不得已的情况下,才要去网络中获取图像
二.生产者消费者模型在listview中加载图片
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import com.example.mymusicplayers.Util.DiskLruCache.Editor;
import com.example.mymusicplayers.Util.DiskLruCache.Snapshot;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v4.util.LruCache;
import android.util.Log;
import android.widget.ImageView;
public class ImageLoader {
public static int Thread_Count;//线程池中线程的数量,应该与设备cpu的合数相同
public static Context c;//上下文
public static LruCache<String, Bitmap> MemCache;//内存缓存
public static LinkedBlockingDeque<Runnable> taskQueue;//双向队列可以根据需求选择后进先出,后加载的任务是用户最想要看到的任务,listview最下面的任务;
public static ExecutorService exec;//线程池
public static Handler pollHandler;//从任务队列中取下载任务的handler
public static Handler uiHandler;//任务下载完成之后,用来刷新listview条目的handler
public static Thread pollThread;//取下载任务的handler要依靠这个线程来存活;
public static boolean isFirstTime=true;//标示作用,用来保证imageloader只执行一次,进行一次初始化;
public static final int LOAD_FINISH=101;//
public static final int NEW_TASK =102;
public static DiskLruCache diskache;//磁盘缓存
public static void init(Context context){
if(!isFirstTime){
return;
}
c=context;
//获得线程数,机器是几核就起几个线程;
Thread_Count=getNunberOfCores();
//初始化内存缓存;
MemCache=new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory()/8)){
protected int sizeOf(String key, Bitmap value) {
return value.getHeight()*value.getWidth();
};
};
//设置磁盘缓存;
try { //存储目录, //程序版本号.智能清理旧版本的信息
diskache = DiskLruCache.open(directory(), appVersion(), 1, 1024*1024*10);
} catch (IOException e1) { //一个k对应几个value//分配的磁盘缓存从大小;
e1.printStackTrace();
}
//初始化任务队列
taskQueue=new LinkedBlockingDeque<Runnable>();
//初始化线程池有固定线程数量的线程池;
//后面加s的一般都是工具类;
exec=Executors.newFixedThreadPool(Thread_Count);
//初始化ui线程的handler;
uiHandler=new Handler(Looper.getMainLooper()){
public void handleMessage(Message msg) {
//TODO 不理解;
//加载完毕,通知ui线程 进行更新图片;
if(msg.what==LOAD_FINISH){
valueObject vo=(valueObject) msg.obj;
ImageView iv=vo.iv;
String url=vo.url;
Bitmap bitmap=vo.bitmap;
if(iv.getTag().toString().equals(url)){
iv.setImageBitmap(bitmap);
}
}else{
super.handleMessage(msg);
}
};
};
pollThread=new Thread(){
public void run() {
//创建一个looper
Looper.prepare();
//在工作线程起一个handler;
pollHandler=new Handler(){
public void handleMessage(Message msg) {
//工作线程的handler,有新的任务,把新的任务从消息队列中取出放入线程池;
if(msg.what==NEW_TASK){
//现在任务队列中放入了新的下载任务,
//取任务队列中取任务;
try {
Runnable task = taskQueue.takeFirst();//从队列中取出一个任务,
//将取出来的任务放到线程池中;
exec.execute( task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
super.handleMessage(msg);
}
}
};
//TODO 不理解;
Looper.loop();
}
};
pollThread.start();
Log.i("TAG", "isFirstTime>>>>>>>>>1>>>>>>>>>>>>>>>>>>>>>>"+isFirstTime);
isFirstTime=false;
}
private static File directory() {
String path = c.getCacheDir().getPath();
return new File(path,"imageLoadercache");
}
private static int appVersion() {
try {
PackageInfo info = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);//0表示没有附加属性;
return info.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
//获取url指向的图像,放到iv中显示;
public static void loadimage(final String url,final ImageView iv){
Log.i("TAG", "isFirstTime>>>>>>>>>2>>>>>>>>>>>>>>>>>>>>>>"+isFirstTime);
if(isFirstTime){
throw new RuntimeException("imageloader未初始化");
}
//先判断url所指向的图像是否在缓存中有保存;
//url转换成MD5格式的字符串;MD5:将任意长度的格式的转换成相同长度的字符串;
final String md5url=getMD5(url);
iv.setTag(md5url);
Bitmap bitmap=MemCache.get(md5url);
if(bitmap!=null){
Log.d("TAG","图像时从缓存中加载的");
iv.setImageBitmap(bitmap);
return;
}
try {
Snapshot snap = diskache.get(md5url);
if(snap!=null){
Log.i("TAG", "头像是从磁盘中获取的");
InputStream in=snap.getInputStream(0);//一个key对应一个值,取下标为0的位置的值;
bitmap=BitmapFactory.decodeStream(in);//把一个流对象编译成一个bitmap;
//把bitmap放入到内存中,
MemCache.put(md5url, bitmap);
iv.setImageBitmap(bitmap);
return;
}
} catch (IOException e1) {
e1.printStackTrace();
}
//如果缓存,内存都没有,从服务器下载,把这个任务添加到任务队列中,
taskQueue.add(new Runnable() {
public void run() {
try {
URL u=new URL(url);
HttpURLConnection conn=(HttpURLConnection) u.openConnection();
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.connect();
InputStream in = conn.getInputStream();
//要对图片进行压缩;
Bitmap bitmap=compress(in,iv);//根据iv的大小来压缩图像
in.close();
MemCache.put(md5url,bitmap);
//将压缩后的图片放到磁盘中
Editor editor=diskache.edit(md5url);
OutputStream os = editor.newOutputStream(0);
bitmap.compress(CompressFormat.JPEG, 100, os);//100表示不压缩,上面已经手动压缩过了,直接存在磁盘中就可以;
editor.commit();//提交
diskache.flush();//写日志;(是可选操作);
valueObject vo=new valueObject(iv, md5url, bitmap);
//UI线程提醒加载完毕,需要刷新了,
Message.obtain(uiHandler,LOAD_FINISH,vo).sendToTarget();
} catch (Exception e) {
e.printStackTrace();
}
}
});
//工作线程提醒有新的任务,
Message.obtain(pollHandler,NEW_TASK).sendToTarget();
}
/**
* 根据要显示的iv的大小,对图像进行压缩
* in 图像源
* iv 要显示图像的imageview
* 返回压缩过后的图像
*
* */
protected static Bitmap compress(InputStream in, ImageView iv) {
Bitmap bitmap=null;
try{
//先获得原始图像的尺寸大小;
//借助options来获得图像的大小
//in>>>>>byte[]
ByteArrayOutputStream out=new ByteArrayOutputStream();
byte[] buff=new byte[4096];
int len=-1;
while((len=in.read(buff))!=-1){
out.write(buff,0,len);//注意不能写buff.length;//
}
//获得原始图像的宽高
Options opts=new Options();//用来原始图像的宽和高;
opts.inJustDecodeBounds=true;//如果为true;下面的bm没有任何返回值,为空,只有opts中有数据
Bitmap bm=BitmapFactory.decodeByteArray(out.toByteArray(), 0, out.toByteArray().length, opts);//没有任何返回值,为空,只有opts中有数据//
// bitmap bm=BitmapFactory.decodeByteArray(data, offset, length, opts)
int width=opts.outWidth;//原始图像在宽高上的像素数
int height=opts.outHeight;
//要显示图像的控件的宽高,
int targetwidth=iv.getWidth();
int targetheight=iv.getHeight();
//计算量大的时候,还没来的及算出宽高,就开到执行过去了,
if(targetwidth==0||targetheight==0){
//手动设置指定的值,(80dp,100dp)
//或者设置为屏幕的宽高,
targetwidth=c.getResources().getDisplayMetrics().widthPixels;//设备的宽;
targetheight=c.getResources().getDisplayMetrics().heightPixels;//设备的高;
/*targetheight=80;
targetwidth=80;*/
}
//计算压缩比
int samplesize=1;
if(width*1.0/targetwidth>1||height*1.0/targetheight>1){//*1.0的目地是使运算结果为double类型而不是整数
samplesize=(int)Math.ceil(Math.max(width*1.0/targetwidth,height*1.0/targetheight));
}
//压缩图片
opts.inSampleSize=samplesize;
opts.inJustDecodeBounds=false;
bitmap=BitmapFactory.decodeByteArray(out.toByteArray(), 0, out.toByteArray().length, opts);
in.close();
}catch(Exception e){
e.printStackTrace();
}
return bitmap;
}
private static String getMD5(String url) {
String result="";
try {
MessageDigest md=MessageDigest.getInstance("md5");//使用md5方法进行加速;
md.update(url.getBytes());//把字符串转换成byte数组;
byte[] bytes = md.digest();
StringBuilder sb=new StringBuilder();
for(byte b:bytes){
String str=Integer.toHexString(b & 0XFF);//二进制转换成十六进制;
if(str.length()==1){
sb.append("0");
}
sb.append(str);
}
result=sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return result;
}
private static int getNunberOfCores() {
//找到表示cpu核数的路径
File file=new File("/sys/devices/system/cpu");
//过滤有几个"cpu+数"字的文件;
File[] files=file.listFiles(new FilenameFilter() {
//过滤文件,检查cpu是几核;有cpu0,cpu1,cpu2等,cpu后面gen数字,如果后面不是数字,都与cpu的核数无关;,有几个这样的文件,就有几核,可以起几个线程;
@Override
public boolean accept(File dir, String filename) {
if(filename.matches("cpu[0-9]")){
return true;
}else{
return false;
}
}
});
if(files.length>0){
return files.length;
}else{
return 1;
}
}
private static class valueObject{
ImageView iv;//发起下载任务的imageviw
String url;
Bitmap bitmap;
//此类的作用是保证加载的图片不闪图
public valueObject(ImageView iv, String url, Bitmap bitmap) {
super();
this.iv = iv;
this.url = url;
this.bitmap = bitmap;
}
}
}