android 网络加载图片,对图片资源进行优化,并且实现内存双缓存 + 磁盘缓存

经常会用到 网络文件 比如查看大图片数据 资源优化的问题,当然用开源的项目  Android-Universal-Image-Loader  或者 ignition 都是个很好的选择。

在这里把原来 写过的优化的代码直接拿出来,经过测试千张图片效果还是不错的。




工程目录:



至于 Activity 就是加载了 1个网格布局

/**
 *   实现 异步加载 和   2级缓存
 */
public class ImagedownActivity extends Activity {
    
    public static String filepath;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        filepath =   this.getCacheDir().getAbsolutePath();
        GridView gv=(GridView)findViewById(R.id.gridview01);
        //设置列数
        gv.setNumColumns(3);
        //配置适配器
        gv.setAdapter(new Myadapter(this));
    }
    
    @Override
    protected void onDestroy() {
    	// TODO Auto-generated method stub
    	//activity 销毁时,清除缓存
    	MyImageLoader.removeCache(filepath);
    	super.onDestroy();
    }
    
}

接下来 Myadapter.java(给网格每个item塞入图片 )在生成每个 item 异步请求网络获取image

public class Myadapter extends BaseAdapter {
	private Context context;
	private String  root ="http://192.168.0.100:8080/Android_list/";
	private String[] URLS;
	private final MyImageLoader  myImageLoader = new MyImageLoader(context);;
	
	/**
	 * adapter 初始化的时候早一堆数据
	 * 这里我请求的是自己搭的服务器
	 * @param context
	 */
	public  Myadapter(Context context){
		this.context =context;
		URLS = new String[999];
		for (int i = 0; i < 999; i++) {
			URLS[i] = root + (i+1)+".jpg";
		}	
	}
	
	
	@Override
	public int getCount() {
		return URLS.length;
	}

	@Override
	public Object getItem(int position) {
		return URLS[position];
	}

	@Override
	public long getItemId(int position) {
		return URLS[position].hashCode();
	}


	
	@Override
	public View getView(int position, View view, ViewGroup parent) {
		ImageView imageView;
		if(view==null){
			imageView=new ImageView(context);
			imageView.setLayoutParams(new GridView.LayoutParams(200,190));
			imageView.setAdjustViewBounds(false);
			imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
			imageView.setPadding(5, 5, 5, 5);
		}else{
		    imageView=(ImageView)view;
		}
		     myImageLoader.downLoad(URLS[position], (ImageView)imageView , context);
		     return imageView;
	}
}

MyImageLoader.java

public class MyImageLoader {

	//最大内存
	final static int memClass = (int) Runtime.getRuntime().maxMemory();  
	private Context context;
	
	// 是否缓存到硬盘
	private boolean  diskcache = true;

	// 定义一级 缓存的图片数
	private static final int catch_num = 10;
	
	// 定义二级缓存 容器  软引用
	private static ConcurrentHashMap<String, SoftReference<Bitmap>> current_hashmap = new ConcurrentHashMap<String, SoftReference<Bitmap>>();
	
	// 定义一级缓存容器  强引用       (catch_num ,0.75f,true) 默认参数                                                                                                                        2.加载因子默认        3.排序模式 true
	private static LinkedHashMap<String, Bitmap> link_hashmap = new LinkedHashMap<String, Bitmap>(catch_num ,0.75f,true) {

    // 必须实现的方法
		protected boolean removeEldestEntry(java.util.Map.Entry<String, Bitmap> eldest) {
			/** 当一级缓存中 图片数量大于 定义的数量 放入二级缓存中
			 */
			if (this.size() > catch_num) {
				// 软连接的方法 存进二级缓存中
				current_hashmap.put(eldest.getKey(), new SoftReference<Bitmap>(
						eldest.getValue()));
				//缓存到本地
    			cancheToDisk(eldest.getKey(),eldest.getValue() );
    			
				return true;
			}
			   return false;
		};
	};

	public MyImageLoader(Context context) {

	}
	
	
	/**   
	 *  外部调用此方法   进行下载图片  
	 */
	public void downLoad(String key , ImageView imageView,Context context){
	  // 先从缓存中找   。  
		context = this.context;
		
		Bitmap bitmap = getBitmapFromCache(key);
		if( null!= bitmap){	
			imageView.setImageBitmap(bitmap);
		    cancleDownload(key, imageView);     	//取消下载
		    return ;
		}    
		
		// 缓存中 没有  把当前的 imageView 给他 得到 task 
		if(cancleDownload(key, imageView)){     //没有任务进行。,。。开始下载
			ImageDownloadTask task = new ImageDownloadTask(imageView);
			Zhanwei_Image  zhanwei_image = new Zhanwei_Image(task);
			//先把占位的图片放进去
			imageView.setImageDrawable(zhanwei_image);
			// task执行任务
			task.execute(key); 
		}
	}
	
	
	/** 此方法 用于优化  : 用户直接 翻到 哪个 就先加载 哪个、
	 * @param key                - URL
	 * @param imageView          - imageView
     *  core: 给当前的 imageView 得到给他下载的 task
	 */
	
	private boolean cancleDownload(String key,ImageView imageView){
		// 给当前的 imageView 得到给他下载的 task
		ImageDownloadTask task = getImageDownloadTask(imageView);
		if(null != task){
			String down_key = task.key;
			  if( null == down_key || !down_key.equals(key)){
				  task.cancel(true);      	// imageview 和 url 的key不一样       取消下载   
			  }else{
				  return false;      //正在下载: 
			  }	 
	      }
             return true;            //没有正在下载
	}
	
	
	
//	public void getThisProcessMemeryInfo() {
//        int pid = android.os.Process.myPid();
//        android.os.Debug.MemoryInfo[] memoryInfoArray = activityManager.getProcessMemoryInfo(new int[] {pid});
//        System.out.println("本应用当前使用了" + (float)memoryInfoArray[0].getTotalPrivateDirty() / 1024 + "mb的内存");
//    }

	
	
	/**
	 * 从缓存中得到 图片的方法 1.先从一级 缓存找 linkhashmap 不是线程安全的 必须要加同步
	 */
	public Bitmap getBitmapFromCache(String key) {
         //1.先在一级缓存中找
         synchronized (link_hashmap) {
			Bitmap bitmap = link_hashmap.get(key);
			if (null != bitmap) {
				link_hashmap.remove(key);
				// 按照 LRU是Least Recently Used 近期最少使用算法 内存算法 就近 就 原则 放到首位
				link_hashmap.put(key, bitmap);
				System.out.println(" 在缓存1中找图片了 =" +key);
				return bitmap;
			}
		}
		
         // 2. 到二级 缓存找
		SoftReference<Bitmap> soft = current_hashmap.get(key);
		if (soft != null) {
			//得到 软连接 中的图片
			Bitmap soft_bitmap = soft.get();      
			if (null != soft_bitmap) {
				System.out.println(" 在缓存2中找图片了 =" +key);
				return soft_bitmap;
			}
		} else {
			// 没有图片的话 把这个key删除
			current_hashmap.remove(key);      
		}
		
		
		//3.都没有的话去从外部缓存文件读取
		if(diskcache){
			Bitmap bitmap = getBitmapFromFile(key);
			if(bitmap!= null){
				link_hashmap.put(key, bitmap);   //将图片放到一级缓存首位
			    return bitmap;
			}
		}
		
		return null;
	}

	
	/**
	 * 缓存到本地文件
	 * @param key
	 * @param bitmap
	 */
	public static void cancheToDisk(String key ,Bitmap bitmap ){
		//2.缓存bitmap至/data/data/packageName/cache/文件夹中
		try {
			String fileName = getMD5Str(key);
			String filePath = ImagedownActivity.filepath + "/" + fileName;
			System.out.println("缓存到本地===" + filePath);
			FileOutputStream fos = new FileOutputStream(filePath);
			bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
			
		} catch (Exception e) {
			
		}
	}
    
	
	/**
	 * 从外部文件缓存中获取bitmap
	 * @param url
	 * @return
	 */
	private Bitmap getBitmapFromFile(String url){
		Bitmap bitmap = null;
		String fileName = getMD5Str(url);
		if(fileName == null){
			return null;
		}	
		String filePath = ImagedownActivity.filepath + "/" + fileName;		
		try {
			FileInputStream fis = new FileInputStream(filePath);
			bitmap = BitmapFactory.decodeStream(fis);
			System.out.println("在本地缓存中找到图片==="+ filePath);
		} catch (FileNotFoundException e) {
			System.out.println("getBitmapFromFile==="+ e.toString());
			e.printStackTrace();
			bitmap = null;
		}
		return bitmap;
	}
	
	
	
	/**
     * 清理文件缓存
     * @param dirPath
     * @return
     */
    public static boolean removeCache(String dirPath) {
    	File dir = new File(dirPath);
        File[] files = dir.listFiles();
        if(files == null || files.length == 0) {
            return true;
        }
            int dirSize = 0;
            //这里删除所有的缓存
            int all_ = (int) ( 1 * files.length + 1);
            //对files 进行排序
            Arrays.sort(files, new FileLastModifiedSort());
            for (int i = 0; i < all_ ; i++) {
                    files[i].delete();
            }
        return true;
    }
	

    /**
     * 根据文件最后修改时间进行排序
     */
    private static class FileLastModifiedSort implements Comparator<File> {
        @Override
        public int compare(File lhs, File rhs) {
            if(lhs.lastModified() > rhs.lastModified()) {
                return 1;
            } else if(lhs.lastModified() == rhs.lastModified()) {
                return 0;
            } else {
                return -1;
            }
        }
    }
    

	/**  
     * MD5 加密  
     */   
    private static String getMD5Str(String str) {   
        MessageDigest messageDigest = null;   
        try {   
            messageDigest = MessageDigest.getInstance("MD5");   
            messageDigest.reset();   
            messageDigest.update(str.getBytes("UTF-8"));   
        } catch (NoSuchAlgorithmException e) {   
            System.out.println("NoSuchAlgorithmException caught!");   
            return null;
        } catch (UnsupportedEncodingException e) {   
            e.printStackTrace();
            return null;
        }   
   
        byte[] byteArray = messageDigest.digest();   
        StringBuffer md5StrBuff = new StringBuffer();   
        for (int i = 0; i < byteArray.length; i++) {               
            if (Integer.toHexString(0xFF & byteArray[i]).length() == 1)   
                md5StrBuff.append("0").append(Integer.toHexString(0xFF & byteArray[i]));   
            else   
                md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));   
        }   
        return md5StrBuff.toString();   
    }  
	
	
	// ------------------------ 异步加载----------------------------
	/**
	 *  占位的 图片 或者 颜色      用来绑定 相应的图片
	 */	 
   class Zhanwei_Image extends ColorDrawable{
	    //里面存放 相应 的异步 处理时加载好的图片 ----- 相应的 task
	    private final WeakReference<ImageDownloadTask>  taskReference;
		public Zhanwei_Image(ImageDownloadTask task){	
		    super(Color.BLUE);
			taskReference = new WeakReference<MyImageLoader.ImageDownloadTask>(task);	
		}  
		 // 返回去这个 task 用于比较
	    public ImageDownloadTask getImageDownloadTask(){
		  return taskReference.get();
	    }
	}
	
   
	// 根据 给 的 iamgeView、 得到里面的 task  用于和当前的 task比较是不是同1个
	private ImageDownloadTask getImageDownloadTask(ImageView imageView){
		if( null != imageView){
			    Drawable drawable = imageView.getDrawable();	
			if( drawable instanceof Zhanwei_Image)
				return ((Zhanwei_Image)drawable).getImageDownloadTask();
			
		}
		return null;
	}
	
	
	
	/**
	 * 把图片 添加到缓存中
	 */
	public void addBitmap(String key, Bitmap bitmap) {
		if (null != bitmap) {
			synchronized (link_hashmap) {         // 添加到一级 缓存中
				link_hashmap.put(key, bitmap);
			}
		}
	}
	
	
	/** 在后台 加载每个图片
	 *  第一个参数 第2个要进度条不 第三个返回结果 bitmap
	 */
	class ImageDownloadTask extends AsyncTask<String, Void, Bitmap> {

		private String key;
		private WeakReference<ImageView> imgViReference;

		public ImageDownloadTask(ImageView imageView) {
			//imageView 传进来 。。要给哪个iamgeView加载图片
			imgViReference = new WeakReference<ImageView>(
					imageView);
		}

		@Override
		protected Bitmap doInBackground(String... params){
			key = params[0];
			//调用下载函数 根据 url 下载      
			return downloadBitmap(key);
		}

		@Override
		protected void onPostExecute(Bitmap result) {
			if(isCancelled()){
				result = null;
			}
			
			System.out.println("result=="+ result.getByteCount()+"---memClassmemery="+memClass);
			
			if(null!= result){
				//保存到缓存中
				   addBitmap(key, result);
				   ImageView  imageView = imgViReference.get();
		    	   if( null != imageView){	
		          //向 imageView 里面放入 bitmap         
		    		    ImageDownloadTask task = getImageDownloadTask(imageView);
		    			
		           /**
		            *  判断 是不是 同一个 task( )
		            *  如果当前这个 task  ==  imageView 里面的那个 task 就是同1个
		            */
		    		if( this == task ){
		    			 imageView.setImageBitmap(result);
		    			 
		             }
		    	}
		    }
		}
	}

	
	/**
	 * 连接网络 客户端 下载图片
	 */
	private Bitmap downloadBitmap(String url) {
     
        final HttpClient client = AndroidHttpClient.newInstance("Android");
        final HttpGet getRequest = new HttpGet(url); 
        try {
            HttpResponse response = client.execute(getRequest);
            final int statusCode = response.getStatusLine().getStatusCode();
            
            if (statusCode != HttpStatus.SC_OK) {
            	
                Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url);
                return null;
            }

            final HttpEntity entity = response.getEntity();
            if (entity != null) {
                InputStream inputStream = null;
                try {
                	
                    inputStream = entity.getContent();                 
                    /**
                     *  1.没有压缩直接将生成的bitmap返回去
                     */
//                  return BitmapFactory.decodeStream(inputStream);
                    
                   /**
                    *  2.得到data后在这里把图片进行压缩
                    */
                     byte[] data = StreamTool.read(inputStream); 
                     return  BitmapManager.scaleBitmap(context, data, 0.3f);
//                   return BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
                } finally {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    entity.consumeContent();
                }
            }
		} catch (IOException e) {
			getRequest.abort();
		} catch (IllegalStateException e) {
			getRequest.abort();
		} catch (Exception e) {
            getRequest.abort();
        } finally {
            if ((client instanceof AndroidHttpClient)) {
                ((AndroidHttpClient) client).close();
            }
        }
        return null;
	}

}

StreamTool.java

public class StreamTool {
	
	public static  byte[] read(InputStream in) throws Exception{
		ByteArrayOutputStream  out_byte = new ByteArrayOutputStream();
		byte[] buff = new byte[1024];
		int len=0;
		while((len = in.read(buff))!= -1){
			 //写到内存中  字节流
			out_byte.write( buff, 0 , len);
		}	
		out_byte.close();	
		// 把内存数据返回
		return  out_byte.toByteArray();	
	}
}


BitmapManager.java ( 这个类里面对 网络资源的图片 进行了优化)

public class BitmapManager {
	
	/**
     * 按屏幕适配Bitmap
     */
	public static Bitmap scaleBitmap(Context context, byte[] data , float percent) {

	  //这里我不获取了,假设是下面这个分辨率
    	  int screenWidth =   540;
	  int screenrHeight = 950;
        //设置 options
        BitmapFactory.Options options = new BitmapFactory.Options();
        /**
         *  BitmapFactory.Options这个类,有一个字段叫做 inJustDecodeBounds.SDK中对这个成员的说明是这样的:
         *  If set to true, the decoder will return null (no bitmap), but the out…
         *  也就是说,如果我们把它设为true,那么BitmapFactory.decodeFile(String path, Options opt)并不会真的返回一个Bitmap给你,
         *  它仅仅会把它的宽,高取回来给你,这样就不会占用太多的内存,也就不会那么频繁的发生OOM了。
         */
        options.inJustDecodeBounds = true;
  
        //读取
        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
        
        int imgWidth = options.outWidth;
        int imgHeight = options.outHeight;

        //如果比你设置的宽高大  就进行缩放,
        if(imgWidth > screenWidth * percent || imgHeight > screenrHeight * percent) {
            options.inSampleSize = calculateInSampleSize(options, screenWidth, screenrHeight, percent);
        }
        
        
        /**
         * If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller 
         * to query the bitmap without having to allocate the memory for its pixels.
         * 
         * 如果设置成 true,这个编码将会返回1个null , 但是那个区域仍将被设置(也就是存在),允许(调用者)去查询那个没有分配 内存的像素  bitmap 
         */
        options.inJustDecodeBounds = false;
       
        /**
         *  Android的Bitmap.Config给出了bitmap的一个像素所对应的存储方式,
         *  有RGB_565,ARGB_8888,ARGB_4444,ALPHA_8四种。RGB_565表示的是红绿蓝三色分别用5,6,5个比特来存储,
         *  一个像素占用了5+6+5=16个比特。ARGB_8888表示红绿蓝和半透明分别用8,8,8,8个比特来存储,
         *  一个像素占用了8+8+8+8=32个比特。这样的话如果图片是以RGB_8888读入的,那么占用内存的大小将是RGB_565读入方式的2倍。
         *  通常我们给Imagview加载图片是通过setDrawable或者在xml文件中用android:src来设置
         *  默认的加载图片大小的方式是以RGB_8888读入的。
         * 
         */
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        
        /**
         * If this is set to true, then the resulting bitmap will allocate its pixels such that they can be purged 
         * if the system needs to reclaim memory.
         * 
         * 如果设置成 true, 这个结果bitmap 将会被分配像素,这样他们就能被 系统回收了,当系统需要回收内存的时候
         */
        options.inPurgeable = true;
        
        /**
         * This field works in conjuction with inPurgeable.
         * 这个方法是在   inPurgeable 的基础上工作的
         */
        options.inInputShareable = true;
        
    
        bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
        
        System.out.println("data==="+  data.length +"  change == bitmap byte "+ bitmap.getByteCount());
        return bitmap;
    }
    
    
    
    //                                                    options       reqWidth 屏幕宽      reqHeight屏幕高      你的view是屏幕的多大
    public static int calculateInSampleSize(BitmapFactory.Options options, int screenWidth, int screenHeight ,float percent) {
        
    	// 原始图片宽高
        final int height = options.outHeight;
        final int width = options.outWidth;
        // 倍数
        int inSampleSize = 1;
  
        if (height > screenHeight * percent || width > screenWidth * percent) {
        	
            // 计算目标宽高与原始宽高的比值
        	final int inSampleSize_h = Math.round((float) height / (float)( screenHeight * percent));
            
            final int inSampleSize_w = Math.round((float) width / (float)( screenWidth * percent));
                
            // 选择两个比值中较小的作为inSampleSize的
            inSampleSize = inSampleSize_h < inSampleSize_w ? inSampleSize_h : inSampleSize_w;
           
            System.out.println("inSampleSize===="+ inSampleSize);
            // 
            if(inSampleSize < 1) {
                inSampleSize = 1;
            }
        }
       //简单说这个数字就是 缩小为原来的几倍,根据你的image需要占屏幕多大动态算的(比如你用的权重设置layout)
        return inSampleSize;
    }
}

这个是代码输出的最多给这个进程分配的内存 128M



可以看到我上面的bitmapManager 里面有个   options.inPreferredConfig   注释写的很清楚,可以上去看一下,接下来贴几种格式的效果图

rgb565  和  argb_444  所占的内存              (54000)



看一下 argb_8888                    (  108000)



当然可能仔细看的人会看到我一开始截的 鸣人的效果图 上半部分 和 下半部分的颜色会有点问题。上面的rgb_565 生成的,和原图色彩可能会有点出入。

但是内存真心少了一半,所以各种取舍就看个人了,代码注释都谢的很清楚了。


至于 : MyImageLoaderLru.java  其实就是    MyImageLoader.java

先贴出代码不同地方的代码 : 就是在强引用的地方  把  LinkedHashMap 换成了 LruCache

	 // 获取单个进程可用内存的最大值  
     // 方式一:使用ActivityManager服务(计量单位为M)  
        /*int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();*/  
     // 方式二:使用Runtime类(计量单位为Byte)  
      final static int memClass = (int) Runtime.getRuntime().maxMemory();  
	 // 3. 定义一级缓存容器  强引用       (catch_num /2,0.75f,true) 默认参数                                                                                                                        2.加载因子默认        3.排序模式 true
	  final static int  max = memClass/5;
     
	  // LruCache 用强引用将  图片放入     LinkedHashMap 
	  private static LruCache<String, Bitmap> lrucache = new LruCache<String, Bitmap>(max) {
    	  protected int sizeOf(String key, Bitmap value) {  
              if(value != null) {  
                  // 计算存储bitmap所占用的字节数  
                  return value.getRowBytes() * value.getHeight();  
              } else {  
                  return 0;  
              }  
          }  
            
          @Override  
          protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {  
              if(oldValue != null) {  
                  // 当硬引用缓存容量已满时,会使用LRU算法将最近没有被使用的图片转入软引用缓存  
            	  current_hashmap.put(key, new SoftReference<Bitmap>(oldValue));  
              }  
          }  
	};


1. 强引用:LruCache 后面再说,其实他内的内部封装的就是1个 LinkedHashMap 。LinkedHashMap 是线程不安全的,所以上面都会用到同步。

2. 软引用:ConcurrentHashMap 是线程安全的,并且支持高并发很有效率,这个后面也会说到,为什么要用 软引用 SoftReference,这个是在系统将要oom时,就会回收

                    软引用的对象资源,所以才会用到他,防止程序出异常 。

3. 磁盘缓存: 这个经常会看到网易新闻等,应用有些界面你看了很多图片,往上翻很多, 其实没有再次访问网络,会将部分image缓存在sdcard里。

4. 其中1个优化: 当比如用户快速滑动到 最底部,其实是最先加载显示给用户的部分的内容的,这样就是用户看到哪加载哪,1个是快,1个是避免资源浪费。


原理: 当用户进入界面加载图片 ,首先会从1级缓存强引用中找,找不到回去2级缓存软引用中找,找不到再去sdcard中找,再找不到才会去请求网络加载资源。

            当然sdcard的缓存 看个人需求是否需要。


注: android 4.0 后 对 SoftReference 的回收机制进行了改变,所以你是可以不用 2级缓存的,直接去掉就好了。

          只要控制好你的 lrucache 或者 linkedhashmap就好了。

          





  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空白的泡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值