Android 获取远程图片与本地图片缓存

1.意义:加快读取速度,减少流量的消耗,减少崩溃的次数

2.Android应用中的UI现成5秒没有相应的话就会强制抛出异常,俗称ANR(Appliction Not Responce),对于获取远程的资源,这里特指的是从服务器获取的数据譬如图片等等,这种异常将会更加容易被抛出来,所以在Android 4.0 里面将限制了网络的访问,不允许将网络的访问放在主线程,低于4.0的版本就不会收到限制,这个是在测试的时候发现的,Android中提供两个方法来做这件事情:

启动一个心的现成来获取资源,完成后通过handler机制发送消息,同时在handler的接收端更新主线程,从而达到异步线程获取图片,接收端定义handler变量,同事复写handlMessage(Message msg)方法

本地缓存

  对图片来说,你不可能让应用每次获取的时候都重新到远程服务器去下载,特别是显示ListView中的图片的时候,滑动的速度变得很快,这样将会造成ANR,即使图片比较小,但是图片还没来得及释放的话,累计的图片将会占用比较大的内存,但是又不能将所有的图片资源在获取之后放在内存中,使用弱引用保存对象的方法保存,因为图片的资源往往很占内存也比较容易造成ANR,那么如果下载下来的图片保存的SdCard中,下次直接从SDcard上去获取的话,是比较靠谱的缓存方法,采用LRU等一些算法可以保证sdcard被占用的空间的一小部分,这样即保证了图片的加载,节省了从远程获取的图片流量,又使Sdcard的空间只占用了一笑部分,另外一中方法是设置LRU规则跟过期的时间

代码的流程如下:

下载图片--->判断Sdcard上的空间--->判断开辟的10Mde空间--->保存图片--->过期策略

2.1在Sdcard上开辟一定的空间,需要先判断Sdcard上剩余的空间是否足够,如果足够的话,就可以开辟空间,例如开辟10M的内存空间用来保存图片

2.2当需要获取图片的时候,就先从Sdcard上的目录中去找,如果找的到的话,使用该图片,并且更新图片最后被使用的时间,如果找不到,通过URL去DownLoad

2.3去服务器下载图片,如果图片下载成功了,放入SDcard,并使用,如果失败了,应该有重试的机制重新下载,譬如三次

2.4下载成功后保存到Sdcard上需要判断10M的空间是否已经用完,如果没用完就保存,如果已经用完空间,就根据LRU规则删除一些最近没有被用户用到的资源

保存图片到SD的代码:

private void saveBmpToSd(Bitmap bm, String url) {
		if (bm == null) {
			Log.w(TAG, " trying to savenull bitmap");
			return;
		}
		// 判断sdcard上的空间
		if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
			Log.w(TAG, "Low free space onsd, do not cache");
			return;
		}
		String filename = convertUrlToFileName(url);
		String dir = getDirectory(filename);
		File file = new File(dir + "/" + filename);
		try {
			file.createNewFile();
			OutputStream outStream = new FileOutputStream(file);
			bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
			outStream.flush();
			outStream.close();
			Log.i(TAG, "Image saved tosd");
		} catch (FileNotFoundException e) {
			Log.w(TAG, "FileNotFoundException");
		} catch (IOException e) {
			Log.w(TAG, "IOException");
		}
	}

计算Sdcard上的空间:

/**
	 * 计算sdcard上的剩余空间
	 * 
	 * @return
	 */
	private int freeSpaceOnSd() {
		// TODO Auto-generated method stub
		StatFs stat = new StatFs(Environment.getExternalStorageDirectory()
				.getPath());
		double sdFreeMB = ((double) stat.getAvailableBlocks() * (double) stat
				.getBlockSize()); // MB
		return (int) sdFreeMB;
	}


修改文件的最后修改时间:

/**
	 * 修改文件的最后修改时间
	 * 
	 * @param dir
	 * @param fileName
	 */
	private void updateFileTime(String dir, String fileName) {
		File file = new File(dir, fileName);
		long newModifiedTime = System.currentTimeMillis();
		file.setLastModified(newModifiedTime);
	}


本地缓存优化:

/**
	 * 计算储存目录下的文件大小,当文件的总大小超过规定的CACHE_SIZE,
	 * 或者是Sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE 的规定 ,那么删除40%最近没有使用的图片
	 * 
	 * @param dirPath
	 */
	private void removeCache(String dirPath) {
		File file = new File(dirPath);
		File[] files = file.listFiles();
		if (files == null) {
			return;
		}
		int dirSize = 0;
		for (int i = 0; i < files.length; i++) {
			if (files[i].getName().contains(WHOLESALE_CONV)) {
				dirSize += files[i].length();
			}
		}
		if (dirSize > CACHE_SIZE * MB
				|| FREE_SD_SPACE_NEEED_TO_CACHE > freeSpaceOnSd()) {
			int removeFactor = (int) ((0.4 * files.length) + 10);
			Arrays.sort(files, new FileLastModifSort());
			Log.i(TAG, "Clear some expiredcache files");
			for (int i = 0; i < removeFactor; i++) {
				if (files[i].getName().contains(WHOLESALE_CONV)) {
					files[i].delete();
				}
			}
		}
	}
/**
	 * 删除过期文件
	 * @param dirPath
	 * @param fileName
	 */
	private void removeExpiredCache(String dirPath, String fileName) {
		File file = new File(dirPath, fileName);
		if (System.currentTimeMillis() - file.lastModified() > M_TIME_DIFF) {
			Log.i(TAG, "Clear some expiredcache files");
			file.delete();
		}
	}


文件使用时间排序:

/**
	 * 根据文件的最后修改时间进行排序
	 * @author huanglong
	 *
	 */
	class FileLastModifSort implements Comparator<File> {
		public int compare(File arg0, File arg1) {
			if (arg0.lastModified() > arg1.lastModified()) {
				return 1;
			} else if (arg0.lastModified() == arg1.lastModified()) {
				return 0;
			} else {
				return -1;
			}
		}
	}

内存保存:

在内存中保存的话,只能保存一定的量,而不能一直往里面放,需要设置数据的过期时间,LRU等算法,这里有一个方法是把常用的数据放到一个缓存中(A),不常用的放在另外一个缓存中(B),当要获取数据时候先从A中去获取,如果A中存在在去B中获取,B中的数据主要是A中LUR出来的数据,这里的内存回收主要是针对B,从而保持A中的数据可以有效的被命中。

定义A缓存:

// 定义A缓存
	private final HashMap<String, Bitmap> mHardBItmapCache = new LinkedHashMap<String, Bitmap>(
			HARD_CACHE_CAPACITY / 2, 0.75f, true) {
		@Override
		protected boolean removeEldestEntry(
				java.util.Map.Entry<String, Bitmap> eldest) {
			// TODO Auto-generated method stub
			if (size() > HARD_CACHE_CAPACITY) {
				// 保证map的size大于30时候,把最不常用的key放到mSoftBitmapCache中,从而保证mHardBitmapCache的效率
				mSoftBitmapCache.put(eldest.getKey(),
						new SoftReference<Bitmap>(eldest.getValue()));
				return true;
			} else {
				return false;
			}
		}
	};

定义B缓存:

// 定义B缓存
	// 当mHardBitmapCache的key大于30的时候,会根据LRU算法把最近没有使用的Key放入到缓存中
	// Bitmap 使用了SoftReference 当内存不足的时候,此时cache中的bitmap会被垃圾回收掉
	private final static ConcurrentHashMap<String, SoftReference<Bitmap>> mSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(
			HARD_CACHE_CAPACITY / 2);

从缓存中获取数据:

/**
	 * 从缓存中获得数据
	 * 
	 * @param url
	 * @return
	 */
	private Bitmap getBitmapFromeCache(String url) {
		// 先从mHardBitmapCache缓存中获取
		synchronized (mHardBItmapCache) {
			final Bitmap bitmap = mHardBItmapCache.get(url);
			if (bitmap != null) {
				// 如果找到的话,把元素移动到linkedHashMap的最前面,从而保证LRU算法中是最后被删除的
				mHardBItmapCache.remove(url);
				mHardBItmapCache.put(url, bitmap);
				return bitmap;
			}
		}
		// 如果mHardBitmapCache中找不到,到mSoftBitmapCache中找
		SoftReference<Bitmap> bitmapReference = mSoftBitmapCache.get(url);
		if (bitmapReference != null) {
			final Bitmap bitmap = bitmapReference.get();
			if (bitmap != null) {
				return bitmap;
			} else {
				mSoftBitmapCache.remove(url);
			}
		}
		return null;
	}

如果缓存不存在,那么就只能去服务器下载:

// 如果缓存中不存在那么就只能去服务器下载
	class ImageDownloaderTask extends AsyncTask<String, Void, Bitmap> {
		private static final int IO_BUFFER_SIZE = 4 * 1024;
		private String url;
		private final WeakReference<ImageView> imageViewReferrReference;

		private ImageDownloaderTask() {
			imageViewReferrReference = new WeakReference<ImageView>(imageView);

		}

		@Override
		protected Bitmap doInBackground(String... params) {
			// TODO Auto-generated method stub
			final AndroidHttpClient client = AndroidHttpClient
					.newInstance("Android");
			url = params[0];
			final HttpGet getRequest = new HttpGet(url);
			try {
				HttpResponse response = client.execute(getRequest);
				final int statusCode = response.getStatusLine().getStatusCode();
				if (statusCode != HttpStatus.SC_OK) {
					Log.v(TAG, "从" + url + "中下载图片是出错!  错误码:" + statusCode);
					return null;
				}
				final HttpEntity entity = response.getEntity();
				if (entity != null) {
					InputStream inputStream = null;
					OutputStream outputStream = null;
					try {
						inputStream = entity.getContent();
						final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
						outputStream = new BufferedOutputStream(dataStream,
								IO_BUFFER_SIZE);
						copy(inputStream, outputStream);
						outputStream.flush();
						final byte[] data = dataStream.toByteArray();
						final Bitmap bitma = BitmapFactory.decodeByteArray(
								data, 0, data.length);
					} finally {
						if (inputStream != null) {
							inputStream.close();
						}
						if (outputStream != null) {
							outputStream.close();
						}
						entity.consumeContent();
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				getRequest.abort();
				Log.w(TAG, "Incorrect URL :" + url);
				e.printStackTrace();
			} finally {
				if (client != null) {
					client.close();
				}
			}

			return null;
		}
	}

这是两种做法,还有一些应用在下载的时候使用了线程池和消息队列MQ,对于图片的下载效果会更好一些


http://mobile.51cto.com/android-288600.htm





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值