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





發佈了8 篇原創文章 · 獲贊 17 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章