Android中用双缓存技术,加载网络图片
最近在学校参加一个比赛,写的一个Android应用,里面要加载大量的网络图片,可是用传统的方法图片一多就会造成程序出现内存溢出而崩溃.因为自己也在学习中,所以看了很多博客和视频,然后参照这些大神的写源码,自己写了一个加载网络图片工具类.
里面要用到一个经典的图片缓存库DiskLruCache 下载地址为: DiskLruCache下载
下面是使用这个类实现的 双缓存网络图片加载
- public class DiskLruCacheUtils {
- private static DiskLruCacheUtils diskLruCacheUtils;
- private DiskLruCache diskLruCache; //LRU 磁盘缓存
- private LruCache<String, Bitmap> lruCache; //LRU 内存缓存
- private Context context;
- public DiskLruCacheUtils() {
- }
- public static DiskLruCacheUtils getInstance() {
- if (diskLruCacheUtils == null) {
- diskLruCacheUtils = new DiskLruCacheUtils();
- }
- return diskLruCacheUtils;
- }
- public void open(Context context, String disk_cache_subdir, int disk_cache_size) {
- try {
- this.context = context;
- // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
- // LruCache通过构造函数传入缓存值,以KB为单位。
- int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
- // 使用最大可用内存值的1/8作为缓存的大小。
- int cacheSize = maxMemory / 8;
- lruCache = new LruCache<>(cacheSize);
- /**
- * open()方法接受四个参数:
- * 第一个参数: 指定缓存地址
- * 第二个参数: 指定当前引用程序的版本号
- * 第三个参数: 指定同一个key可以对应多少个缓存文件,基本都是传1
- * 第四个参数: 指定最多可以缓存的字节数. 通常是10MB
- */
- diskLruCache = DiskLruCache.open(getCacheDir(disk_cache_subdir), getAppVersion(), 1, disk_cache_size);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- /**
- * 获取磁盘缓存
- * @param url
- * @return
- */
- public InputStream getDiskCache(String url) {
- String key = hashkeyForDisk(url);
- try {
- DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
- if (snapshot != null) {
- return snapshot.getInputStream(0);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
- }
- /**
- * 下载图片并缓存到内存和磁盘中
- * @param url
- * @param callBack
- */
- public void putCache(final String url, final CallBack callBack){
- new AsyncTask<String,Void,Bitmap>(){
- @Override
- protected Bitmap doInBackground(String... params) {
- String key = hashkeyForDisk(params[0]);
- // System.out.println("Key = "+key);
- DiskLruCache.Editor editor = null;
- Bitmap bitmap = null;
- URL url = null;
- try {
- url = new URL(params[0]);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setReadTimeout(30*1000);
- conn.setConnectTimeout(30*1000);
- ByteArrayOutputStream baos = null;
- if (conn.getResponseCode()==HttpURLConnection.HTTP_OK){
- BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
- baos = new ByteArrayOutputStream();
- byte[] bytes = new byte[1024];
- int len = -1;
- while ((len = bis.read(bytes)) != -1) {
- baos.write(bytes, 0, len);
- }
- bis.close();
- baos.close();
- conn.disconnect();
- }
- if (baos !=null){
- bitmap = decodeSampleadBitmapFromStream(baos.toByteArray(),300,300);
- // bitmap = BitmapFactory.decodeByteArray(baos.toByteArray(),0,baos.toByteArray().length);
- addBitmapToCache(params[0],bitmap); // 添加到内存缓存
- editor = diskLruCache.edit(key); // 加入磁盘缓存
- // System.out.println(url.getFile());
- //位图压缩后输出(参数1: 压缩格式, 参数2: 质量(100 表示不压缩,30 表示压缩70%),参数3: 输出流)
- bitmap.compress(Bitmap.CompressFormat.JPEG,30,editor.newOutputStream(0));
- editor.commit();//提交
- }
- } catch (Exception e) {
- try {
- editor.abort();//放弃写入
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- e.printStackTrace();
- }
- return bitmap;
- }
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- super.onPostExecute(bitmap);
- callBack.response(bitmap);
- }
- }.execute(url);
- }
- /**
- * 关闭磁盘缓存
- */
- public void close(){
- if (diskLruCache!=null&& !diskLruCache.isClosed()){
- try {
- diskLruCache.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 刷新磁盘缓存
- */
- public void flush(){
- if (diskLruCache!=null){
- try {
- diskLruCache.flush();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 回调接口
- * @param <T>
- */
- public interface CallBack<T>{
- public void response(T entity);
- }
- /**
- * 位图重新采样
- *
- * @param reqWidth 自定义的宽高
- * @param reqHeight
- * @return
- */
- public static Bitmap decodeSampleadBitmapFromStream(byte[] bytes, int reqWidth, int reqHeight) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;//只解析边界,不加载到内存中
- BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
- options.inSampleSize = calculatInSampleSize(options, reqWidth, reqHeight);//设置采样比为计算出的采样比例
- options.inJustDecodeBounds = false;
- return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);//重新解析图片
- }
- //添加缓存的对象
- public void addBitmapToCache(String url,Bitmap bitmap){
- String key = hashkeyForDisk(url);
- if (getBitmapFromMenCache(key)==null){
- lruCache.put(key,bitmap);
- }
- }
- //从缓存中获取对象
- public Bitmap getBitmapFromMenCache(String url){
- String key = hashkeyForDisk(url);
- return lruCache.get(key);
- }
- /**
- * 计算位图的采样比例大小
- *
- * @param options
- * @param reqWidth 需要的宽高
- * @param reqHeight
- * @return
- */
- private static int calculatInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
- //获取位图的原宽高
- final int w = options.outWidth;
- final int h = options.outHeight;
- int inSampleSize = 1;
- //如果原图的宽高比需要的图片宽高大
- if (w > reqWidth || h > reqHeight) {
- if (w > h) {
- inSampleSize = Math.round((float) h / (float) reqHeight);
- } else {
- inSampleSize = Math.round((float) w / (float) reqWidth);
- }
- }
- return inSampleSize;
- }
- /**
- * MD5加密计算
- *
- * @param key
- * @return
- */
- private String hashkeyForDisk(String key) {
- String cachekey;
- try {
- final MessageDigest mDigest = MessageDigest.getInstance("MD5");
- mDigest.update(key.getBytes());
- cachekey = bytesToHexString(mDigest.digest());
- } catch (NoSuchAlgorithmException e) {
- cachekey = String.valueOf(key.hashCode());
- }
- return cachekey;
- }
- private String bytesToHexString(byte[] bytes) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < bytes.length; i++) {
- String hex = Integer.toHexString(0xff & bytes[i]);
- if (hex.length() == 1) {
- sb.append(0);
- }
- sb.append(hex);
- }
- return sb.toString();
- }
- /**
- * 获取缓存的地址
- *
- * @param name
- * @return
- */
- private File getCacheDir(String name) {
- String cachePath = Environment.getExternalStorageState()
- == Environment.MEDIA_MOUNTED || !Environment.isExternalStorageRemovable() ?
- context.getExternalCacheDir().getPath() : context.getCacheDir().getPath();
- return new File(cachePath + File.separator + name);
- }
- /**
- * 获取App的版本号
- *
- * @return
- */
- private int getAppVersion() {
- try {
- return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
- } catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
- }
- return 1;
- }
- }
decodeSampleadBitmapFromStream(byte[] bytes, int reqWidth, int reqHeight)这个函数的实现可以参照 郭大神的博客:Android高效加载大图、多图方案,有效避免程序OOM。
自己也是小白,好多都是复制粘贴,嘿嘿 ! 这里就不进行代码的分析了(其实好多我也不懂...),下面就自己上demo把:
现将上面的DiskLruCache,
在项目中创建一个libcore.io包,将这.jar文件复制进去,然后实现上边的代码(有点多哈!直接复制过去把!). 我这里直接创建了一个DiskLruCacheUtils类里面就是上面的代码! 还是截个图:↓↓↓↓
- private DiskLruCacheUtils diskLruCacheUtils;//创建对象
- private static final String DISK_CACHE_SUBDIR = "temp"; //设置图片缓存的文件
- private static final int DISK_CACHE_SIZE= 100*1024*1024; // 设置SD卡缓存的大小
然后在他们的声明周期中:
- @Override
- protected void onResume() {
- super.onResume();
- diskLruCacheUtils = DiskLruCacheUtils.getInstance();
- diskLruCacheUtils.open(this,DISK_CACHE_SUBDIR,DISK_CACHE_SIZE);//打开缓存
- }
- @Override
- protected void onPause() {
- super.onPause();
- diskLruCacheUtils.flush(); //刷新缓存
- }
- @Override
- protected void onStop() {
- super.onStop();
- diskLruCacheUtils.close(); //关闭缓存
- }
加了这代码就可以真正的使用这个工具类了.
但是这样还不行,还得写个图片加载方法:
- private void loadBitmap(String url, final ImageView imageView) {
- if (imageView.getTag().equals(url)) {
- //从内存缓存中取图片
- Bitmap bitmap = diskLruCacheUtils.getBitmapFromMenCache(url);
- if (bitmap == null) {
- //如果内存中为空 从磁盘缓存中取
- InputStream in = diskLruCacheUtils.getDiskCache(url);
- if (in == null) {
- //如果缓存中都为空,就通过网络加载,并加入缓存
- diskLruCacheUtils.putCache(url, new DiskLruCacheUtils.CallBack<Bitmap>() {
- @Override
- public void response(Bitmap entity) {
- // System.out.println("网络中下载...");
- imageView.setImageBitmap(entity);
- }
- });
- } else {
- System.out.println("磁盘中取出...");
- bitmap = BitmapFactory.decodeStream(in);
- diskLruCacheUtils.addBitmapToCache(url, bitmap);
- imageView.setImageBitmap(bitmap);
- }
- } else {
- // System.out.println("内存中取出...");
- imageView.setImageBitmap(bitmap);
- }
- }
- }
然后在你需要加载图片的地方使用该方法就OK, 看起复杂其实还挺简单的 ...(复制过去不就行了...)
直接上Demo:
这是activity_main.xml文件
下面上布局文件 挺简单的 RecyclerView+CardView:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context="zhengliang.com.bitmaplrucache.MainActivity">
- <android.support.v7.widget.RecyclerView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/rlv_list"
- >
- </android.support.v7.widget.RecyclerView>
- </RelativeLayout>
挺简单的就一个 RecyclerView 因为要加载很多图片所以就用这个了,(哈哈! 我喜欢他的瀑布流! 爽到爆炸啊...)
这是item.xml文件
- <?xml version="1.0" encoding="utf-8"?>
- <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="2dp"
- app:cardBackgroundColor="@color/colorAccent"
- app:cardCornerRadius="2dp"
- android:background="@color/colorAccent"
- >
- <ImageView
- android:id="@+id/pic"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:scaleType="centerCrop"
- />
- </android.support.v7.widget.CardView>
就一个CardView ,里面放了一个ImageView
MainActivity类中代码如下:
因为这里没有图片资源所以自己用Volley框架写了一个获取图片资源的getImageUrl()方法里面返回一些图片资源的URL地址
(找图片真的很恼火啊,一条一条的把图片地址复制过来不是我的风范啊! 就在百度图片中经过千辛万苦扒了个图片API接口下来,哈哈 有图片咯!)
- <pre name="code" class="java">public class MainActivity extends AppCompatActivity{
- private List<String> data;
- private DiskLruCacheUtils diskLruCacheUtils;
- private static final String DISK_CACHE_SUBDIR = "temp";
- private static final int DISK_CACHE_SIZE= 100*1024*1024;
- private RecyclerView rlvlist;
- private MyAdapter myAdapter;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- initViews();
- getImageUrl("http://image.baidu.com/channel/listjson?pn=0&rn=200&tag1=美女&tag2=小清新&ie=utf8");
- }
- private void initViews() {
- data = new ArrayList<String>();
- this.rlvlist = (RecyclerView) findViewById(R.id.rlv_list);
- rlvlist.setLayoutManager(new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL));
- }
- @Override
- protected void onResume() {
- super.onResume();
- diskLruCacheUtils = DiskLruCacheUtils.getInstance();
- diskLruCacheUtils.open(this,DISK_CACHE_SUBDIR,DISK_CACHE_SIZE);
- }
- @Override
- protected void onPause() {
- super.onPause();
- diskLruCacheUtils.flush();
- }
- @Override
- protected void onStop() {
- super.onStop();
- diskLruCacheUtils.close();
- }
- public void getImageUrl(String url){
- final RequestQueue mQueue = Volley.newRequestQueue(this);
- JsonObjectRequest stringRequest = new JsonObjectRequest(url, null,
- new Response.Listener<JSONObject>() {
- @Override
- public void onResponse(JSONObject jsonObject) {
- // System.out.println(jsonObject);
- try {
- JSONArray jsonArray = jsonObject.getJSONArray("data");
- for (int i = 0; i <jsonArray.length() ; i++) {
- JSONObject item = jsonArray.getJSONObject(i);
- String url = item.getString("image_url");
- String name = item.getString("tags");
- data.add(url);
- myAdapter = new MyAdapter(data,MainActivity.this,diskLruCacheUtils);
- rlvlist.setAdapter(myAdapter);
- myAdapter.notifyDataSetChanged();
- }
- } catch (JSONException e) {
- e.printStackTrace();
- }
- }
- }, new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError volleyError) {
- }
- }
- );
- mQueue.add(stringRequest);
- if (data.size()==200){
- getImageUrl("http://image.baidu.com/channel/listjson?pn=0&rn=200&tag1=美女&tag2=全部&ie=utf8");
- }
- }
- public void getImageUrl2(String url){
- final RequestQueue mQueue = Volley.newRequestQueue(this);
- JsonObjectRequest stringRequest = new JsonObjectRequest(url, null,
- new Response.Listener<JSONObject>() {
- @Override
- public void onResponse(JSONObject jsonObject) {
- // System.out.println(jsonObject);
- try {
- JSONArray jsonArray = jsonObject.getJSONArray("imgs");
- for (int i = 0; i <jsonArray.length() ; i++) {
- JSONObject item = jsonArray.getJSONObject(i);
- String url = item.getString("hoverURL");
- String name = item.getString("fromPageTitle");
- data.add(url);
- myAdapter = new MyAdapter(data,MainActivity.this,diskLruCacheUtils);
- rlvlist.setAdapter(myAdapter);
- myAdapter.notifyDataSetChanged();
- }
- } catch (JSONException e) {
- e.printStackTrace();
- }
- }
- }, new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError volleyError) {
- }
- }
- );
- mQueue.add(stringRequest);
- }
- }
然后是就是实现RecyclerView 的Adapter,因为网络图片的加载都要在Adapter中,所以loadBitmap()方法我就直接写在这里了 废话少说直接上代码
- public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
- private List<String> list;
- private Context context;
- private DiskLruCacheUtils diskLruCacheUtils;
- public MyAdapter(List<String> list, Context context, DiskLruCacheUtils diskLruCacheUtils) {
- this.list = list;
- this.context = context;
- this.diskLruCacheUtils = diskLruCacheUtils;
- }
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_view,parent,false);
- return new ViewHolder(view);
- }
- @Override
- public void onBindViewHolder(ViewHolder holder, int position) {
- holder.pic.setTag(list.get(position));
- loadBitmap(list.get(position),holder.pic);
- System.out.println(position);
- }
- @Override
- public int getItemCount() {
- return list==null?0:list.size();
- }
- public static class ViewHolder extends RecyclerView.ViewHolder {
- public ImageView pic;
- public ViewHolder(View itemView) {
- super(itemView);
- pic = (ImageView) itemView.findViewById(R.id.pic);
- }
- }
- private void loadBitmap(String url, final ImageView imageView) {
- if (imageView.getTag().equals(url)) {
- //从内存缓存中取图片
- Bitmap bitmap = diskLruCacheUtils.getBitmapFromMenCache(url);
- if (bitmap == null) {
- //如果内存中为空 从磁盘缓存中取
- InputStream in = diskLruCacheUtils.getDiskCache(url);
- if (in == null) {
- //如果缓存中都为空,就通过网络加载,并加入缓存
- diskLruCacheUtils.putCache(url, new DiskLruCacheUtils.CallBack<Bitmap>() {
- @Override
- public void response(Bitmap entity) {
- // System.out.println("网络中下载...");
- imageView.setImageBitmap(entity);
- }
- });
- } else {
- System.out.println("磁盘中取出...");
- bitmap = BitmapFactory.decodeStream(in);
- diskLruCacheUtils.addBitmapToCache(url, bitmap);
- imageView.setImageBitmap(bitmap);
- }
- } else {
- // System.out.println("内存中取出...");
- imageView.setImageBitmap(bitmap);
- }
- }
- }
- }
大工告成 看看效果
第一进入时全是从 显示"网络中下载..." 因为RecyclerView和ListView一样,超出屏幕的Item都会被回收,当再次滑动回到上次的位置就会重新获取item,并且会重新获取图片,.
下面看看滑动回去打印的log
全是显示从内存中取出,并没有再重网络中下载,说明刚才的图片都缓存到内存中了,这样就加快的图片的显示,还节省了流量!(这年头流量伤不起啊!)
下面再看看关闭应用再打开是什么效果吧!
全部显示的是从磁盘中取出... 因为关闭应用,这个时候内存中缓存的图片就会被清空. 这个时候就会自动看SD中是否有缓存了. 并且从SD中取出的图片会再一次缓存到内存中去...我这里是加载的200张图片,完全没有问题,嘿嘿...
Android中用双缓存技术,加载网络图片的更多相关文章
- Android笔记之使用Glide加载网络图片、下载图片
Glide简介 不想说太多,真的很方便:P)可以节省我不少时间 GitHub地址:https://github.com/bumptech/glide 加载网络图片到ImageView Glide.wi ...
- Android三种基本的加载网络图片方式(转)
Android三种基本的加载网络图片方式,包括普通加载网络方式.用ImageLoader加载图片.用Volley加载图片. 1. [代码]普通加载网络方式 ? 1 2 3 4 5 6 7 8 9 10 ...
- Android图片管理组件(双缓存+异步加载)
转自:http://www.oschina.net/code/snippet_219356_18887?p=3#comments ImageManager2这个类具有异步从网络下载图片,从sd读取本地 ...
- [android]完美的解决方案ListView加载网络图片反弹问题
为什么 先说为什么有照片反弹. 使用convertView对ListView的每一个item优化,item的复用能够有效减少内存的占用.使ListView滑动更为流畅. 但会带来一个问题,当最顶部的i ...
- Android笔记之使用ImageView加载网络图片以及保存图片到本地并更新图库
ImageView显示网络图片 findViewById(R.id.btnLoad).setOnClickListener(new View.OnClickListener() { @Override ...
- ListView异步加载网络图片完美版之双缓存技术
本示例参考学习了一个国外的示例:http://code.google.com/p/android-imagedownloader/,有兴趣的同学下载研究一下. 问题描述:在这一篇博客中将会为大家讲解如 ...
- wemall app商城源码Android之ListView异步加载网络图片(优化缓存机制)
wemall-mobile是基于WeMall的android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之L ...
- Android Volley入门到精通:使用Volley加载网络图片
在上一篇文章中,我们了解了Volley到底是什么,以及它的基本用法.本篇文章中我们即将学习关于Volley更加高级的用法,如何你还没有看过我的上一篇文章的话,建议先去阅读Android Volley完 ...
- (BUG已修改,最优化)安卓ListView异步加载网络图片与缓存软引用图片,线程池,只加载当前屏之说明
原文:http://blog.csdn.net/java_jh/article/details/20068915 迟点出更新的.这个还有BUG.因为软引应不给力了.2.3之后 前几天的原文有一个线程管 ...
随机推荐
- 51nod 最长公共子序列Lcs
有深入 了解了一点 . 51nod 可以用来加深 算法理解程度 ,
- vi中的批量替换
举个例子啊: 将文件tihuan(假设此文本中字符a)中的所有字符a换成字符w,其命令为: 1.vi tihuan 2.按esc键 3.按shift+: 4.在:后输入 %s/a/w/g 就ok ...
- 删除目录下所有gif的图片
find -name "*.gif" -exec rm -fv {} \;
- 【LeetCode OJ】Palindrome Partitioning
Problem Link: http://oj.leetcode.com/problems/palindrome-partitioning/ We solve this problem using D ...
- HDU 4405
http://acm.hdu.edu.cn/showproblem.php?pid=4405 题意:飞行棋,可以跳,从0走到n,问期望步数 题解:dp[i]表示从i走到n的期望,逆推,因为每次都要走一 ...
- 初步探讨WPF的ListView控件(涉及模板、查找子控件)
本文结合模板的应用初步介绍ListView的应用 一.Xaml中如何建立数据资源 大部分数据都会来自于后台代码,如何Xaml同样的建立数据源呢?比如建立一个学生List: 首先引入命名空间: xmln ...
- I.MX6 ubuntu-core-14.04 Apache php mysql Qt5
/*************************************************************************** * I.MX6 ubuntu-core-14. ...
- LeetCode Median of Two Sorted Arrays 找中位数(技巧)
题意: 给两个有序(升or降)的数组,求两个数组合并之后的中位数. 思路: 按照找第k大的思想,很巧妙.将问题的规模降低,对于每个子问题,k的规模至少减半. 考虑其中一个子问题,在两个有序数组中找第k ...
- Evaluate Reverse Polish Notation
Evaluate the value of an arithmetic expression in Reverse Polish Notation. Valid operators are +, -, ...
- jquery 平滑滚动页面到某个锚点
$(document).ready(function() { $("a.topLink").click(function() { $ ...