现在的位置: 首页 > 综合 > 正文

Android之软引用 (SoftReference)

2018年03月31日 ⁄ 综合 ⁄ 共 5818字 ⁄ 字号 评论关闭

前言:

这方面的文章,其实网上挺多的,但是都太乱,需要自己整理,或者不太适合,这篇文章,我主要是讲软引用,用在图片的下载,加载,本地缓存。

由于手机的内存有限,不能将所有图标都加载到内存中,而软引用的好处在于:

        1. 在内存足够时,所有的图片都在内存中;

        2. 内存不足时,将释放掉当前页面不需要用到的图片内存,而只加载需要用到的,这样,就防止内存爆掉,导致手机Crash。

 

流程图:

 

实现:

AsyncImageLoader.java 实现图片的内存管理,本地缓存管理,下载管理,并通知需要显示图片的activity来显示:

	private static HashMap<String, SoftReference<Bitmap>> imageCache = 
		new HashMap<String, SoftReference<Bitmap>>();
	private static final int nThreadPoolSize = 5;
	private static ExecutorService mExecutorService = Executors.newFixedThreadPool(nThreadPoolSize);
	private OnImageLoadListener mImageLoadListener;
	private Handler mHandler;

         先定义几个变量,imageCache即是一个HashMap的对图片的软引用, mExecutorService是一个线程池,避免过多的图片同时去下载显示,导致手机负载过重,影响性能,mImageLoadListener是一个回调函数接口,即谁注册,通知谁,谁来用。

	public AsyncImageLoader(OnImageLoadListener l){
		mImageLoadListener = l;
		
		mHandler = new Handler(){
			@Override
			public void handleMessage(Message msg) {
				switch(msg.what){
				case 0:
					String imageUrl = msg.getData().getString("imageUrl");
					int position = msg.getData().getInt("position");
					mImageLoadListener.ImageLoadFinished((Bitmap)msg.obj, imageUrl, position);
					break;
					
				default:
					break;
				}
			}
		};
	}

         初始化类,参数是回调接口,内部实现一个消息处理的handler。

	public void loadBitmap(String imageUrl, final int position){
		final String imgUrl = imageUrl;

		mExecutorService.execute(new Runnable(){
			@Override
			public void run() {
				Bitmap bitmap = loadBitmapFromCache(imgUrl);
				if(bitmap == null){
					bitmap = loadBitmapFromLocal(imgUrl);
					if(bitmap == null){
						bitmap = loadBitmapFromUrl(imgUrl);
						if(bitmap == null){
							imageCache.put(imgUrl, null);
							return;
						}
					}
				}
				
				Message msg = mHandler.obtainMessage(0, bitmap);
				Bundle bundle = new Bundle();
				bundle.putString("imageUrl", imgUrl);
				bundle.putInt("position", position);
				msg.setData(bundle);
				mHandler.sendMessage(msg);
				return;
			}
		});
	}

         启动线程,完成:从内存、从本地缓存、从网络下载三步骤,并通知显示。

         咱们来看看,这三个步骤是如何实现完成的

        step1, 查找内存:

	public Bitmap loadBitmapFromCache(String imageUrl){
		Bitmap bm = null;
		if(imageCache.containsKey(imageUrl)){
			SoftReference<Bitmap> softReference = imageCache.get(imageUrl);
			if(softReference != null){
				bm = softReference.get();
				if(bm == null){
					imageCache.remove(imageUrl);
				}
			}
		}

		return bm;
	}

          不在软引用,即内存中,返回NULL,如果在,看位图数据不在,去除URL,然后返回NULL;

         step2, 查找本地缓存:

	private Bitmap loadBitmapFromLocal(String imageUrl){

		String filename = getFilenameFromUrl(imageUrl);
		File dir = new File(Environment.getExternalStorageDirectory(), "imifun");
		if(dir.exists()){
			File filepath = new File(dir, filename);
			Bitmap bm = BitmapFactory.decodeFile(filepath.getAbsolutePath());
			if(bm != null){
				if(!imageCache.containsKey(imageUrl)){
					imageCache.put(imageUrl, new SoftReference<Bitmap>(bm));
				}
			}
		}
		return loadBitmapFromCache(imageUrl);
	}

         从SD卡指定的imifun目录下,找查以imageUrl为名字的图片,如果存在,加载至软引用,并由软引用返回通知UI,当然不存在,也交给软引用,反正是找不到的嘛;
        step3, 网络下载:

	private String getFilenameFromUrl(String imageUrl){
		String [] strs = imageUrl.split("/");
		String filename = strs[strs.length - 1];
		if(filename.contains(".")){
			filename = filename.substring(0, filename.lastIndexOf("."));
		}
		return filename;
	}

 

	private Bitmap loadBitmapFromUrl(String imageUrl){
		URL m;
		InputStream i = null;
		try {
			m = new URL(imageUrl);
			HttpURLConnection connection = (HttpURLConnection) m.openConnection();
			connection.setDoInput(true);
			connection.connect();
			i =  connection.getInputStream();
		
			 ByteArrayOutputStream baos = new ByteArrayOutputStream();
		          byte[] b = new byte[1024];
		          int len = 0;
		          while ((len = i.read(b, 0, 1024)) != -1){
			     baos.write(b, 0, len);
			     baos.flush();
		          }
		          byte[] bytes = baos.toByteArray();
		          Bitmap bm=BitmapFactory.decodeByteArray(bytes,0,bytes.length);
			
			if(bm != null){
				saveBitmap(bm, getFilenameFromUrl(imageUrl));
				if(!imageCache.containsKey(imageUrl)){
					imageCache.put(imageUrl, new SoftReference<Bitmap>(bm));
				}
			}
		} catch (MalformedURLException e1) {
			e1.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
		return loadBitmapFromCache(imageUrl);
	}

          这里采用HTTP方式下载,没什么好说的,大家都会,下载好了后,保存至本地缓存,但这里要说下,就是先将图片采用流的方式保存至数组,然后在写文件。为啥要这么麻烦?哎,直接保存,有时候保存的图片不正确,无法打开,因为文件没有EOF结束符,但是,采用流的方式,可以保证有EOF。

         最后,给出保存的函数吧:

	private void saveBitmap(Bitmap bitmap, String filename){
		if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
			File dir = new File(Environment.getExternalStorageDirectory(), "imifun");
			if(!dir.exists()){
				dir.mkdirs();
			}
			
			File file = new File(dir, filename);
			FileOutputStream fos;
			if(!file.exists()){
				try {
					file.createNewFile();
					fos = new FileOutputStream(file);
					if(bitmap.compress(Bitmap.CompressFormat.PNG, 70, fos)){
						fos.flush();
                    }
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

          保存,与读取本地缓存相反,在SD卡上,创建以imageUrl为名字的文件,将数据流写进去,即OK。
          最后,给出interface定义:

	public interface OnImageLoadListener{
		public void ImageLoadFinished(Bitmap bitmap, String imageUrl, int position);
	}

          在需要使用软引用的地方,实现这个接口中的方法即可。

例子:

          1. 初始化异步图片加载类:

	private void initAsycImageLoader(){
		mImageUrls = new ArrayList<String>();
		mImageLoad = new AsyncImageLoader(new OnImageLoadListener(){
			@Override
			public void ImageLoadFinished(Bitmap bitmap, String imageUrl, int position) {
				if(mImageUrls.contains(imageUrl)){
					ImageView iv = (ImageView) rootView.findViewWithTag(position);
					if(iv != null && bitmap != null && bitmap.isRecycled() == false){
						iv.setImageBitmap(bitmap);
						iv.setScaleType(ScaleType.FIT_XY);
					}
				}
			}
		});
	}

          2. 调用

	private void setImageViewBg(ImageView iv, String imgUrl, int position, int defaultResImg){
		Bitmap bitmap = mImageLoad.loadBitmapFromCache(imgUrl);
		iv.setTag(position);
		mImageUrls.add(imgUrl);
		if(bitmap != null && bitmap.isRecycled() == false){
			iv.setImageBitmap(bitmap);
			iv.setScaleType(ScaleType.FIT_XY);
		}else{
			iv.setImageResource(defaultResImg);
			mImageLoad.loadBitmap(imgUrl, position);
		}
	}

        position,因为是在listview中显示,所以,将posotion设置为imageview的tag,方便回调回来时,查找。

总结:
          AsyncImageLoader实现就这么多,不过,这个类还可以改进,实现更高的效率:

         1. 通常,我们请求服务器获取一堆数据,比如类似现在的新闻,10条json数据,那么,我们可以一次性进图片放进ArrayList中,并传给异步加载类;

         2. 异步加载类,就开始一个接一个的去下载,完成后返回数据,通知在该显示的地方显示;

         这里,还有个小问题,不知道大家看出来了没有?
         Android在图片加载到内存,是有个限制的,一次性加载到内存的图片大小不能超过8MB,否则就会Crash。当然,我们的后台一般都不会这么做,但是,如果某个带相册图片浏览的应用,全屏浏览单张图片,而这张图片是由于后台的编辑,不小心传了张大于8MB的,呃,可想而之的后果。。

         网上有解决办法,大家可以找找,我这给个提示:先读取图片的宽,高属性,判断是否超过8MB,如果超过,我们可以取这张图的缩略图,然后在显示,或者显示部分,当然啦,这得需求,或用户来决定哪种方法好,或者UI体验性好。

抱歉!评论已关闭.