安卓網絡請求圖片,對於我們來說並不陌生,因為每個應用都有可能會用到這一技術。通常情況下,我們第一次都是從網絡上請求圖片資源,然后將
圖片資源保存到內存和本地,下一次動態顯示圖片的時候就不需要再從網絡上請求圖片資源了,直接從本地或者內存中獲取就可以了。這就涉及到圖片
的三級緩存技術,分別是內存緩存,本地緩存,網絡緩存。
緩存的流程圖:
首先我們定義一個類叫ClassLoader:
package com.jsako.showprodinfodemo; import java.io.FileOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.support.v4.util.LruCache; import android.widget.ImageView; /** * 圖片加載類 * * @author Administrator * */ public class ImageLoader { private Context context; private int loadingImage; private int errorImage; private LruCache<String, Bitmap> mapCache; public ImageLoader(Context context, int loadingImage, int errorImage) { this.context = context; this.loadingImage = loadingImage; this.errorImage = errorImage; int maxMemory = (int) Runtime.getRuntime().maxMemory(); int mCacheSize = maxMemory / 8; mapCache = new LruCache<String, Bitmap>(mCacheSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } }; } public void getAndSetImage(String imagePath, ImageView iv_image) { //保存當前的ImageView對應的imagepath iv_image.setTag(imagePath); iv_image.setImageResource(loadingImage); /* * 從第一級緩存中找對應imagePath的圖片 如果第一級緩存有對應圖片,顯示! 如果第一級緩存沒有圖片,從第二級緩存中找 */ Bitmap bitmap = getImageByFirstCache(imagePath); if (bitmap != null) { iv_image.setImageBitmap(bitmap); System.out.println("從一級緩存中找到"); return; } /* * 從第二級緩存中找對應的圖片 如果有,則緩存到第一緩存中 如果沒有,則從第三集緩存中找 */ bitmap = getImageBySecondCache(imagePath); if (bitmap != null) { iv_image.setImageBitmap(bitmap); cacheInFirst(imagePath, bitmap); System.out.println("從二級緩存中找到"); return; } /* * 從第三級緩存中找對應的圖片 如果有,則緩存到第一、二緩存中 如果沒有,則顯示錯誤的圖片 */ loadImageByThridCache(imagePath, iv_image); } /** * 將圖片緩存到一級緩存 * * @param imagePath * 圖片的url * @param bitmap */ private void cacheInFirst(String imagePath, Bitmap bitmap) { mapCache.put(imagePath, bitmap); } /** * 從三級緩存中尋找圖片 * * @param imagePath * 圖片的url * @param * @return */ private void loadImageByThridCache(final String imagePath, final ImageView iv_image) { new AsyncTask<String, Void, Bitmap>() { /** * 開啟異步任務前調用 */ @Override protected void onPreExecute() { } /** * 異步任務完成后調用 */ @Override protected void onPostExecute(Bitmap result) { String nowImagePath=(String) iv_image.getTag(); if(!nowImagePath.equals(imagePath)){ //如果當前請求的圖片路徑和需要顯示的圖片路徑不一致的話,就不顯示圖片 System.out.println("不顯示圖片了"); return; } if (result != null) { iv_image.setImageBitmap(result); } else { iv_image.setImageResource(errorImage); } } /** * 后台進行異步任務 */ @Override protected Bitmap doInBackground(String... params) { String nowImagePath=(String) iv_image.getTag(); if(!nowImagePath.equals(params[0])){ //如果當前請求的圖片路徑和需要顯示的圖片路徑不一致的話,就不進行網絡請求了 System.out.println("不進行網絡請求了"); return null; } String url = params[0]; HttpURLConnection conn = null; try { URL mUrl = new URL(url); conn = (HttpURLConnection) mUrl.openConnection(); conn.setRequestMethod("GET"); conn.setReadTimeout(6000); conn.setConnectTimeout(6000); conn.setDoInput(true); conn.connect(); int code = conn.getResponseCode(); if (code == 200) { InputStream in = conn.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(in); // 在分線程中緩存圖片到一級和二級緩存 cacheInFirst(url, bitmap); String imageName = url .substring(url.lastIndexOf("/") + 1); String fileName = context.getExternalFilesDir(null) .getAbsolutePath() + "/" + imageName; bitmap.compress(CompressFormat.JPEG, 50, new FileOutputStream(fileName)); return bitmap; } else { return null; } } catch (Exception e) { e.printStackTrace(); return null; } finally { if (conn != null) { conn.disconnect(); } } } }.execute(imagePath); } /** * 從二級緩存中尋找圖片 * * @param imagePath * 圖片的url * @return */ private Bitmap getImageBySecondCache(String imagePath) { String imageName = imagePath.substring(imagePath.lastIndexOf("/") + 1); String fileName = context.getExternalFilesDir(null).getAbsolutePath() + "/" + imageName; return BitmapFactory.decodeFile(fileName); } /** * 從一級緩存中尋找圖片 * * @param imagePath * 圖片的url * @return */ private Bitmap getImageByFirstCache(String imagePath) { return mapCache.get(imagePath); } }
可以看到這個類的構造方法有三個,第一個是上下文,第二個是加載的圖片的時候所顯示的圖片對應的資源id,第三個是圖片加載失敗后所顯示的圖片對應的資源id。
這里有幾個關鍵點:
第一個是在內存緩存中,我們應該使用LruCache來緩存圖片,這個類會根據當前所存儲的圖片空間是否大過設定值,如果比設定值大就會自動釋放內存,
這樣就能防止內存溢出的問題。
第二個是在使用ListView異步加載圖片的時候,並且重用了convertView的時候會導致圖片錯位的現象。
導致圖片錯位的根本原因就是重用了convertView。
解決方案如下:
a. 每次getView()都將圖片的url保存到ImageView上: imageView.setTag(imagePath)
b. 在分線程准備請求服務器加載圖片之前, 比較准備加載圖片的url與ImageView中保存的最新圖片的url是同一個,
如果不是同一個, 當前加載圖片的任務不應該再執行
如果相同, 繼續執行加載遠程圖片
c. 在主線程准備顯示圖片之前, 比較加載到圖片的url與ImageView中保存的最新圖片的url是同一個
如果不是同一個, 不需要顯示此圖片
如果相同, 顯示圖片