1、Android批量图片加载经典系列采用二级缓存、异步加载网络图片一、问题描述Android应用中常常波及从网络中加载大量图片,为提高加载速度和效率,减少网络流量都会采用二级缓存和异步加载机制,所谓二级缓存就是通过先从内存中获取、再从文献中获取,最终才会访问网络。内存缓存(一级)本质上是Map集合以key-value对方式存储图片url和Bitmap信息,由于内存缓存会导致堆内存泄露, 管理相对复杂某些,可采用第三方组件,对于有经验可自己编写组件,而文献缓存比较简朴一般自己封装一下即可。下面就通过案例看怎样实现网络图片加载优化。二、案例简介案例新闻列表图片三、重要关键组件下面先看看实现一级缓存(
2、内存)、二级缓存(磁盘文献)所编写组件1、MemoryCache在内存中存储图片(一级缓存),采用了1个map来缓存图片代码如下:public class MemoryCache / 最大缓存数 private static final int MAX_CACHE_CAPACITY = 30; /用Map软引用Bitmap对象,保证内存空间足够状况下不会被垃圾回收 private HashMapString,SoftReference mCacheMap = new LinkedHashMapString,SoftReference() private static final long se
3、rialVersionUID = 1L;/当缓存数量超过规定大小(返回true)会清除最早放入缓存 protected boolean removeEldestEntry(Map.EntryString,SoftReference eldest) return size() MAX_CACHE_CAPACITY; ; /* * 从缓存里取出图片 * param id * return 假如缓存有,并且该图片没被释放,则返回该图片,否则返回null */ public Bitmap get(String id) if(!mCacheMap.containsKey(id) return null;
4、 SoftReference ref = mCacheMap.get(id); return ref.get(); /* * 将图片加入缓存 * param id * param bitmap */ public void put(String id,Bitmap bitmap) mCacheMap.put(id,new SoftReference(bitmap); /* * 清除所有缓存 */ public void clear() try for(Map.EntryString,SoftReferenceentry :mCacheMap.entrySet() SoftReference s
5、r = entry.getValue(); if(null != sr) Bitmap bmp = sr.get(); if(null != bmp) bmp.recycle(); mCacheMap.clear(); catch (Exception e) e.printStackTrace(); 2、FileCache在磁盘中缓存图片(二级缓存),代码如下 public class FileCache /缓存文献目录 private File mCacheDir; /* * 创立缓存文献目录,假如有SD卡,则使用SD,假如没有则使用系统自带缓存目录 * param context * pa
6、ram cacheDir 图片缓存一级目录 */public FileCache(Context context,File cacheDir,String dir)if(android.os.Environment.getExternalStorageState().equals、(android.os.Environment.MEDIA_MOUNTED) mCacheDir = new File(cacheDir,dir); else mCacheDir = context.getCacheDir();/ 怎样获取系统内置缓存存储途径 if(!mCacheDir.exists() mCach
7、eDir.mkdirs(); public File getFile(String url) File f=null; try /对url进行编辑,处理中文途径问题 String filename = URLEncoder.encode(url,utf-8); f = new File(mCacheDir,filename); catch (UnsupportedEncodingException e) e.printStackTrace(); return f; public void clear()/清除缓存文献 File files = mCacheDir.listFiles(); fo
8、r(File f:files)f.delete();3、编写异步加载组件AsyncImageLoaderandroid中采用单线程模型即应用运行在UI主线程中,且Android又是实时操作系统规定及时响应否则出现ANR错误,因而对于耗时操作规定不能阻塞UI主线程,需要启动一种线程处理(如本应用中图片加载)并将线程放入队列中,当运行完毕后再告知UI主线程进行更改,同步移除任务这就是异步任务,在android中实现异步可通过本系列一中所用到AsyncTask或者使用thread+handler机制,在这里是完全是通过代码编写实现,这样咱们可以更清晰看到异步通信实现本质,代码如下public cla
9、ss AsyncImageLoader private MemoryCache mMemoryCache;/内存缓存 private FileCache mFileCache;/文献缓存 private ExecutorService mExecutorService;/线程池/记录已经加载图片ImageView private Map mImageViews = Collections .synchronizedMap(new WeakHashMap();/保留正在加载图片url private List mTaskQueue = new ArrayList(); /* * 默认采用一种大小
10、为5线程池 * param context * param memoryCache 所采用高速缓存 * param fileCache 所采用文献缓存 */ public AsyncImageLoader(Context context,MemoryCache memoryCache,FileCache fileCache) mMemoryCache = memoryCache; mFileCache = fileCache; mExecutorService = Executors.newFixedThreadPool(5);/建立一种容量为5固定尺寸线程池(最大正在运行线程数量) /* *
11、 根据url加载对应图片 * param url * return 先从一级缓存中取图片有则直接返回,假如没有则异步从文献(二级缓存)中取,假如没有再从网络端获取 */ public Bitmap loadBitmap(ImageView imageView,String url) /先将ImageView记录到Map中,体现该ui已经执行过图片加载了 mImageViews.put(imageView,url); Bitmap bitmap = mMemoryCache.get(url);/先从一级缓存中获取图片 if(bitmap = null) enquequeLoadPhoto(url
12、,imageView);/再从二级缓存和网络中获取 return bitmap; /* * 加入图片下载队列 * param url */ private void enquequeLoadPhoto(String url,ImageView imageView) /假如任务已经存在,则不重新添加 if(isTaskExisted(url) return; LoadPhotoTask task = new LoadPhotoTask(url,imageView); synchronized (mTaskQueue) mTaskQueue.add(task);/将任务添加到队列中 mExecut
13、orService.execute(task);/向线程池中提交任务,假如没有到达上限(5),则运行否则被阻塞 /* * 判断下载队列中与否已经存在该任务 * param url * return */ private boolean isTaskExisted(String url) if(url = null) return false; synchronized (mTaskQueue) int size = mTaskQueue.size(); for(int i=0;isize;i+) LoadPhotoTask task = mTaskQueue.get(i); if(task !
14、= null & task.getUrl().equals(url) return true; return false; /* * 从缓存文献或者网络端获取图片 * param url */ private Bitmap getBitmapByUrl(String url) File f = mFileCache.getFile(url);/获得缓存图片途径 Bitmap b = ImageUtil.decodeFile(f);/获得文献Bitmap信息 if (b != null)/不为空体现获得了缓存文献 return b; return ImageUtil.loadBitmapFrom
15、Web(url,f);/同网络获得图片 /* * 判断该ImageView与否已经加载过图片了(可用于判断与否需要进行加载图片) * param imageView * param url * return */ private boolean imageViewReused(ImageView imageView,String url) String tag = mImageViews.get(imageView); if (tag = null | !tag.equals(url) return true; return false; private void removeTask(Loa
16、dPhotoTask task) synchronized (mTaskQueue) mTaskQueue.remove(task); class LoadPhotoTask implements Runnable private String url; private ImageView imageView; LoadPhotoTask(String url,ImageView imageView) this.url = url; this.imageView = imageView; Override public void run() if (imageViewReused(imageV
17、iew,url) /判断ImageView与否已经被复用 removeTask(this);/假如已经被复用则删除任务 return; Bitmap bmp = getBitmapByUrl(url);/从缓存文献或者网络端获取图片 mMemoryCache.put(url,bmp);/ 将图片放入到一级缓存中 if (!imageViewReused(imageView,url) /若ImageView未加图片则在ui线程中显示图片 BitmapDisplayer bd = new BitmapDisplayer(bmp,imageView,url); Activity a = (Activ
18、ity) imageView.getContext(); a.runOnUiThread(bd);/在UI线程调用bd组件run措施,实现为ImageView控件加载图片 removeTask(this);/从队列中移除任务 public String getUrl() return url; /* * *由UI线程中执行该组件run措施 */ class BitmapDisplayer implements Runnable private Bitmap bitmap; private ImageView imageView; private String url; public Bitma
19、pDisplayer(Bitmap b,ImageView imageView,String url) bitmap = b; this.imageView = imageView; this.url = url; public void run() if (imageViewReused(imageView,url) return; if (bitmap != null) imageView.setImageBitmap(bitmap); /* * 释放资源 */ public void destroy() mMemoryCache.clear(); mMemoryCache = null;
20、 mImageViews.clear(); mImageViews = null; mTaskQueue.clear(); mTaskQueue = null; mExecutorService.shutdown(); mExecutorService = null; 编写完毕之后,对于异步任务执行只需调用AsyncImageLoader中loadBitmap()措施即可非常以便,对于AsyncImageLoader组件代码最佳结合注释好好理解一下,这样对于Android中线程之间异步通信就会有深刻认识。4、工具类ImageUtilpublic class ImageUtil /* * 从网络
21、获取图片,并缓存在指定文献中 * param url 图片url * param file 缓存文献 * return */ public static Bitmap loadBitmapFromWeb(String url,File file) HttpURLConnection conn = null; InputStream is = null; OutputStream os = null; try Bitmap bitmap = null; URL imageUrl = new URL(url); conn = (HttpURLConnection) imageUrl.openCon
22、nection(); conn.setConnectTimeout(30000); conn.setReadTimeout(30000); conn.setInstanceFollowRedirects(true); is = conn.getInputStream(); os = new FileOutputStream(file); copyStream(is,os);/将图片缓存到磁盘中 bitmap = decodeFile(file); return bitmap; catch (Exception ex) ex.printStackTrace(); return null; fin
23、ally try if(os != null) os.close(); if(is != null) is.close(); if(conn != null) conn.disconnect(); catch (IOException e) public static Bitmap decodeFile(File f) try return BitmapFactory.decodeStream(new FileInputStream(f),null,null); catch (Exception e) return null; private static void copyStream(In
24、putStream is,OutputStream os) final int buffer_size = 1024; try byte bytes = new bytebuffer_size; for (;) int count = is.read(bytes,0,buffer_size); if (count = -1) break; os.write(bytes,0,count); catch (Exception ex) ex.printStackTrace(); 四、测试应用组件之间时序图:1、编写MainActivitypublic class MainActivity exten
25、ds Activity ListView list; ListViewAdapter adapter; Override public void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.main); list=(ListView)findViewById(R.id.list); adapter=new ListViewAdapter(this,mStrings); list.setAdapter(adapter); public void on
26、Destroy() list.setAdapter(null); super.onDestroy(); adapter.destroy(); private String mStrings= ,.;2、编写适配器public class ListViewAdapter extends BaseAdapter private Activity mActivity; private String data; private static LayoutInflater inflater=null; private AsyncImageLoader imageLoader;/异步组件 public L
27、istViewAdapter(Activity mActivity,String d) this.mActivity=mActivity; data=d; inflater = (LayoutInflater)mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); MemoryCache mcache=new MemoryCache();/内存缓存 File sdCard = android.os.Environment.getExternalStorageDirectory();/获得SD卡 File cacheDir = n
28、ew File(sdCard,jereh_cache );/缓存根目录 FileCache fcache=new FileCache(mActivity,cacheDir,news_img);/文献缓存 imageLoader = new AsyncImageLoader(mActivity,mcache,fcache); public int getCount() return data.length; public Object getItem(int position) return position; public long getItemId(int position) return
29、 position; public View getView(int position,View convertView,ViewGroup parent) ViewHolder vh=null; if(convertView=null) convertView = inflater.inflate(R.layout.item,null); vh=new ViewHolder(); vh.tvTitle=(TextView)convertView.findViewById(R.id.text); vh.ivImg=(ImageView)convertView.findViewById(R.id
30、.image); convertView.setTag(vh); else vh=(ViewHolder)convertView.getTag(); vh.tvTitle.setText(标题信息测试 +position); vh.ivImg.setTag(dataposition); /异步加载图片,先从一级缓存、再二级缓存、最终网络获取图片 Bitmap bmp = imageLoader.loadBitmap(vh.ivImg,dataposition); if(bmp = null) vh.ivImg.setImageResource(R.drawable.default_big); else vh.ivImg.setImageBitmap(bmp); return convertView; private class ViewHolder TextView tvTitle; ImageView ivImg; public void destroy() imageLoader.destroy();