Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 中加載網絡資源時的優化 緩存和異步機制

Android 中加載網絡資源時的優化 緩存和異步機制

編輯:關於Android編程

網上關於這個方面的文章也不少,基本的思路是線程+緩存來解決。下面提出一些優化:

1、采用線程池

2、內存緩存+文件緩存

3、內存緩存中網上很多是采用SoftReference來防止堆溢出,這兒嚴格限制只能使用最大JVM內存的1/4

4、對下載的圖片進行按比例縮放,以減少內存的消耗


具體的代碼裡面說明。先放上內存緩存類的代碼MemoryCache.java:


[java]
<SPAN style="FONT-SIZE: 18px"><STRONG>public class MemoryCache { 
 
    private static final String TAG = "MemoryCache"; 
    // 放入緩存時是個同步操作  
    // LinkedHashMap構造方法的最後一個參數true代表這個map裡的元素將按照最近使用次數由少到多排列,即LRU  
    // 這樣的好處是如果要將緩存中的元素替換,則先遍歷出最近最少使用的元素來替換以提高效率  
    private Map<String, Bitmap> cache = Collections 
            .synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true)); 
    // 緩存中圖片所占用的字節,初始0,將通過此變量嚴格控制緩存所占用的堆內存  
    private long size = 0;// current allocated size  
    // 緩存只能占用的最大堆內存  
    private long limit = 1000000;// max memory in bytes  
 
    public MemoryCache() { 
        // use 25% of available heap size  
        setLimit(Runtime.getRuntime().maxMemory() / 4); 
    } 
 
    public void setLimit(long new_limit) {  
        limit = new_limit; 
        Log.i(TAG, "MemoryCache will use up to " + limit / 1024. / 1024. + "MB"); 
    } 
 
    public Bitmap get(String id) { 
        try { 
            if (!cache.containsKey(id)) 
                return null; 
            return cache.get(id); 
        } catch (NullPointerException ex) { 
            return null; 
        } 
    } 
 
    public void put(String id, Bitmap bitmap) { 
        try { 
            if (cache.containsKey(id)) 
                size -= getSizeInBytes(cache.get(id)); 
            cache.put(id, bitmap); 
            size += getSizeInBytes(bitmap); 
            checkSize(); 
        } catch (Throwable th) { 
            th.printStackTrace(); 
        } 
    } 
 
    /**
     * 嚴格控制堆內存,如果超過將首先替換最近最少使用的那個圖片緩存
     * 
     */ 
    private void checkSize() { 
        Log.i(TAG, "cache size=" + size + " length=" + cache.size()); 
        if (size > limit) { 
            // 先遍歷最近最少使用的元素  
            Iterator<Entry<String, Bitmap>> iter = cache.entrySet().iterator(); 
            while (iter.hasNext()) { 
                Entry<String, Bitmap> entry = iter.next(); 
                size -= getSizeInBytes(entry.getValue()); 
                iter.remove(); 
                if (size <= limit) 
                    break; 
            } 
            Log.i(TAG, "Clean cache. New size " + cache.size()); 
        } 
    } 
 
    public void clear() { 
        cache.clear(); 
    } 
 
    /**
     * 圖片占用的內存
     * 
     * @param bitmap
     * @return
     */ 
    long getSizeInBytes(Bitmap bitmap) { 
        if (bitmap == null) 
            return 0; 
        return bitmap.getRowBytes() * bitmap.getHeight(); 
    } 
}</STRONG></SPAN> 

public class MemoryCache {

 private static final String TAG = "MemoryCache";
 // 放入緩存時是個同步操作
 // LinkedHashMap構造方法的最後一個參數true代表這個map裡的元素將按照最近使用次數由少到多排列,即LRU
 // 這樣的好處是如果要將緩存中的元素替換,則先遍歷出最近最少使用的元素來替換以提高效率
 private Map<String, Bitmap> cache = Collections
   .synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true));
 // 緩存中圖片所占用的字節,初始0,將通過此變量嚴格控制緩存所占用的堆內存
 private long size = 0;// current allocated size
 // 緩存只能占用的最大堆內存
 private long limit = 1000000;// max memory in bytes

 public MemoryCache() {
  // use 25% of available heap size
  setLimit(Runtime.getRuntime().maxMemory() / 4);
 }

 public void setLimit(long new_limit) {
  limit = new_limit;
  Log.i(TAG, "MemoryCache will use up to " + limit / 1024. / 1024. + "MB");
 }

 public Bitmap get(String id) {
  try {
   if (!cache.containsKey(id))
    return null;
   return cache.get(id);
  } catch (NullPointerException ex) {
   return null;
  }
 }

 public void put(String id, Bitmap bitmap) {
  try {
   if (cache.containsKey(id))
    size -= getSizeInBytes(cache.get(id));
   cache.put(id, bitmap);
   size += getSizeInBytes(bitmap);
   checkSize();
  } catch (Throwable th) {
   th.printStackTrace();
  }
 }

 /**
  * 嚴格控制堆內存,如果超過將首先替換最近最少使用的那個圖片緩存
  *
  */
 private void checkSize() {
  Log.i(TAG, "cache size=" + size + " length=" + cache.size());
  if (size > limit) {
   // 先遍歷最近最少使用的元素
   Iterator<Entry<String, Bitmap>> iter = cache.entrySet().iterator();
   while (iter.hasNext()) {
    Entry<String, Bitmap> entry = iter.next();
    size -= getSizeInBytes(entry.getValue());
    iter.remove();
    if (size <= limit)
     break;
   }
   Log.i(TAG, "Clean cache. New size " + cache.size());
  }
 }

 public void clear() {
  cache.clear();
 }

 /**
  * 圖片占用的內存
  *
  * @param bitmap
  * @return
  */
 long getSizeInBytes(Bitmap bitmap) {
  if (bitmap == null)
   return 0;
  return bitmap.getRowBytes() * bitmap.getHeight();
 }
}
也可以使用SoftReference,代碼會簡單很多,但是我推薦上面的方法。

 

[java]
public class MemoryCache { 
     
    private Map<String, SoftReference<Bitmap>> cache = Collections 
            .synchronizedMap(new HashMap<String, SoftReference<Bitmap>>()); 
 
    public Bitmap get(String id) { 
        if (!cache.containsKey(id)) 
            return null; 
        SoftReference<Bitmap> ref = cache.get(id); 
        return ref.get(); 
    } 
 
    public void put(String id, Bitmap bitmap) { 
        cache.put(id, new SoftReference<Bitmap>(bitmap)); 
    } 
 
    public void clear() { 
        cache.clear(); 
    } 
 

public class MemoryCache {
 
 private Map<String, SoftReference<Bitmap>> cache = Collections
   .synchronizedMap(new HashMap<String, SoftReference<Bitmap>>());

 public Bitmap get(String id) {
  if (!cache.containsKey(id))
   return null;
  SoftReference<Bitmap> ref = cache.get(id);
  return ref.get();
 }

 public void put(String id, Bitmap bitmap) {
  cache.put(id, new SoftReference<Bitmap>(bitmap));
 }

 public void clear() {
  cache.clear();
 }

}
下面是文件緩存類的代碼FileCache.java:

 

[java]
public class FileCache { 
 
    private File cacheDir; 
 
    public FileCache(Context context) { 
        // 如果有SD卡則在SD卡中建一個LazyList的目錄存放緩存的圖片  
        // 沒有SD卡就放在系統的緩存目錄中  
        if (android.os.Environment.getExternalStorageState().equals( 
                android.os.Environment.MEDIA_MOUNTED)) 
            cacheDir = new File( 
                    android.os.Environment.getExternalStorageDirectory(), 
                    "LazyList"); 
        else 
            cacheDir = context.getCacheDir(); 
        if (!cacheDir.exists()) 
            cacheDir.mkdirs(); 
    } 
 
    public File getFile(String url) { 
        // 將url的hashCode作為緩存的文件名  
        String filename = String.valueOf(url.hashCode()); 
        // Another possible solution  
        // String filename = URLEncoder.encode(url);  
        File f = new File(cacheDir, filename); 
        return f; 
 
    } 
 
    public void clear() { 
        File[] files = cacheDir.listFiles(); 
        if (files == null) 
            return; 
        for (File f : files) 
            f.delete(); 
    } 
 

public class FileCache {

 private File cacheDir;

 public FileCache(Context context) {
  // 如果有SD卡則在SD卡中建一個LazyList的目錄存放緩存的圖片
  // 沒有SD卡就放在系統的緩存目錄中
  if (android.os.Environment.getExternalStorageState().equals(
    android.os.Environment.MEDIA_MOUNTED))
   cacheDir = new File(
     android.os.Environment.getExternalStorageDirectory(),
     "LazyList");
  else
   cacheDir = context.getCacheDir();
  if (!cacheDir.exists())
   cacheDir.mkdirs();
 }

 public File getFile(String url) {
  // 將url的hashCode作為緩存的文件名
  String filename = String.valueOf(url.hashCode());
  // Another possible solution
  // String filename = URLEncoder.encode(url);
  File f = new File(cacheDir, filename);
  return f;

 }

 public void clear() {
  File[] files = cacheDir.listFiles();
  if (files == null)
   return;
  for (File f : files)
   f.delete();
 }

}
最後最重要的加載圖片的類,ImageLoader.java:

 

[java]
public class ImageLoader { 
 
    MemoryCache memoryCache = new MemoryCache(); 
    FileCache fileCache; 
    private Map<ImageView, String> imageViews = Collections 
            .synchronizedMap(new WeakHashMap<ImageView, String>()); 
    // 線程池  
    ExecutorService executorService; 
 
    public ImageLoader(Context context) { 
        fileCache = new FileCache(context); 
        executorService = Executors.newFixedThreadPool(5); 
    } 
 
    // 當進入listview時默認的圖片,可換成你自己的默認圖片  
    final int stub_id = R.drawable.stub; 
 
    // 最主要的方法  
    public void DisplayImage(String url, ImageView imageView) { 
        imageViews.put(imageView, url); 
        // 先從內存緩存中查找  
 
        Bitmap bitmap = memoryCache.get(url); 
        if (bitmap != null) 
            imageView.setImageBitmap(bitmap); 
        else { 
            // 若沒有的話則開啟新線程加載圖片  
            queuePhoto(url, imageView); 
            imageView.setImageResource(stub_id); 
        } 
    } 
 
    private void queuePhoto(String url, ImageView imageView) { 
        PhotoToLoad p = new PhotoToLoad(url, imageView); 
        executorService.submit(new PhotosLoader(p)); 
    } 
 
    private Bitmap getBitmap(String url) { 
        File f = fileCache.getFile(url); 
 
        // 先從文件緩存中查找是否有  
        Bitmap b = decodeFile(f); 
        if (b != null) 
            return b; 
 
        // 最後從指定的url中下載圖片  
        try { 
            Bitmap bitmap = null; 
            URL imageUrl = new URL(url); 
            HttpURLConnection conn = (HttpURLConnection) imageUrl 
                    .openConnection(); 
            conn.setConnectTimeout(30000); 
            conn.setReadTimeout(30000); 
            conn.setInstanceFollowRedirects(true); 
            InputStream is = conn.getInputStream(); 
            OutputStream os = new FileOutputStream(f); 
            CopyStream(is, os); 
            os.close(); 
            bitmap = decodeFile(f); 
            return bitmap; 
        } catch (Exception ex) { 
            ex.printStackTrace(); 
            return null; 
        } 
    } 
 
    // decode這個圖片並且按比例縮放以減少內存消耗,虛擬機對每張圖片的緩存大小也是有限制的  
    private Bitmap decodeFile(File f) { 
        try { 
            // decode image size  
            BitmapFactory.Options o = new BitmapFactory.Options(); 
            o.inJustDecodeBounds = true; 
            BitmapFactory.decodeStream(new FileInputStream(f), null, o); 
 
            // Find the correct scale value. It should be the power of 2.  
            final int REQUIRED_SIZE = 70; 
            int width_tmp = o.outWidth, height_tmp = o.outHeight; 
            int scale = 1; 
            while (true) { 
                if (width_tmp / 2 < REQUIRED_SIZE 
                        || height_tmp / 2 < REQUIRED_SIZE) 
                    break; 
                width_tmp /= 2; 
                height_tmp /= 2; 
                scale *= 2; 
            } 
 
            // decode with inSampleSize  
            BitmapFactory.Options o2 = new BitmapFactory.Options(); 
            o2.inSampleSize = scale; 
            return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); 
        } catch (FileNotFoundException e) { 
        } 
        return null; 
    } 
 
    // Task for the queue  
    private class PhotoToLoad { 
        public String url; 
        public ImageView imageView; 
 
        public PhotoToLoad(String u, ImageView i) { 
            url = u; 
            imageView = i; 
        } 
    } 
 
    class PhotosLoader implements Runnable { 
        PhotoToLoad photoToLoad; 
 
        PhotosLoader(PhotoToLoad photoToLoad) { 
            this.photoToLoad = photoToLoad; 
        } 
 
        @Override 
        public void run() { 
            if (imageViewReused(photoToLoad)) 
                return; 
            Bitmap bmp = getBitmap(photoToLoad.url); 
            memoryCache.put(photoToLoad.url, bmp); 
            if (imageViewReused(photoToLoad)) 
                return; 
            BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad); 
            // 更新的操作放在UI線程中  
            Activity a = (Activity) photoToLoad.imageView.getContext(); 
            a.runOnUiThread(bd); 
        } 
    } 
 
    /**
     * 防止圖片錯位
     * 
     * @param photoToLoad
     * @return
     */ 
    boolean imageViewReused(PhotoToLoad photoToLoad) { 
        String tag = imageViews.get(photoToLoad.imageView); 
        if (tag == null || !tag.equals(photoToLoad.url)) 
            return true; 
        return false; 
    } 
 
    // 用於在UI線程中更新界面  
    class BitmapDisplayer implements Runnable { 
        Bitmap bitmap; 
        PhotoToLoad photoToLoad; 
 
        public BitmapDisplayer(Bitmap b, PhotoToLoad p) { 
            bitmap = b; 
            photoToLoad = p; 
        } 
 
        public void run() { 
            if (imageViewReused(photoToLoad)) 
                return; 
            if (bitmap != null) 
                photoToLoad.imageView.setImageBitmap(bitmap); 
            else 
                photoToLoad.imageView.setImageResource(stub_id); 
        } 
    } 
 
    public void clearCache() { 
        memoryCache.clear(); 
        fileCache.clear(); 
    } 
 
    public static void CopyStream(InputStream is, OutputStream os) { 
        final int buffer_size = 1024; 
        try { 
            byte[] bytes = new byte[buffer_size]; 
            for (;;) { 
                int count = is.read(bytes, 0, buffer_size); 
                if (count == -1) 
                    break; 
                os.write(bytes, 0, count); 
            } 
        } catch (Exception ex) { 
        } 
    } 

public class ImageLoader {

 MemoryCache memoryCache = new MemoryCache();
 FileCache fileCache;
 private Map<ImageView, String> imageViews = Collections
   .synchronizedMap(new WeakHashMap<ImageView, String>());
 // 線程池
 ExecutorService executorService;

 public ImageLoader(Context context) {
  fileCache = new FileCache(context);
  executorService = Executors.newFixedThreadPool(5);
 }

 // 當進入listview時默認的圖片,可換成你自己的默認圖片
 final int stub_id = R.drawable.stub;

 // 最主要的方法
 public void DisplayImage(String url, ImageView imageView) {
  imageViews.put(imageView, url);
  // 先從內存緩存中查找

  Bitmap bitmap = memoryCache.get(url);
  if (bitmap != null)
   imageView.setImageBitmap(bitmap);
  else {
   // 若沒有的話則開啟新線程加載圖片
   queuePhoto(url, imageView);
   imageView.setImageResource(stub_id);
  }
 }

 private void queuePhoto(String url, ImageView imageView) {
  PhotoToLoad p = new PhotoToLoad(url, imageView);
  executorService.submit(new PhotosLoader(p));
 }

 private Bitmap getBitmap(String url) {
  File f = fileCache.getFile(url);

  // 先從文件緩存中查找是否有
  Bitmap b = decodeFile(f);
  if (b != null)
   return b;

  // 最後從指定的url中下載圖片
  try {
   Bitmap bitmap = null;
   URL imageUrl = new URL(url);
   HttpURLConnection conn = (HttpURLConnection) imageUrl
     .openConnection();
   conn.setConnectTimeout(30000);
   conn.setReadTimeout(30000);
   conn.setInstanceFollowRedirects(true);
   InputStream is = conn.getInputStream();
   OutputStream os = new FileOutputStream(f);
   CopyStream(is, os);
   os.close();
   bitmap = decodeFile(f);
   return bitmap;
  } catch (Exception ex) {
   ex.printStackTrace();
   return null;
  }
 }

 // decode這個圖片並且按比例縮放以減少內存消耗,虛擬機對每張圖片的緩存大小也是有限制的
 private Bitmap decodeFile(File f) {
  try {
   // decode image size
   BitmapFactory.Options o = new BitmapFactory.Options();
   o.inJustDecodeBounds = true;
   BitmapFactory.decodeStream(new FileInputStream(f), null, o);

   // Find the correct scale value. It should be the power of 2.
   final int REQUIRED_SIZE = 70;
   int width_tmp = o.outWidth, height_tmp = o.outHeight;
   int scale = 1;
   while (true) {
    if (width_tmp / 2 < REQUIRED_SIZE
      || height_tmp / 2 < REQUIRED_SIZE)
     break;
    width_tmp /= 2;
    height_tmp /= 2;
    scale *= 2;
   }

   // decode with inSampleSize
   BitmapFactory.Options o2 = new BitmapFactory.Options();
   o2.inSampleSize = scale;
   return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
  } catch (FileNotFoundException e) {
  }
  return null;
 }

 // Task for the queue
 private class PhotoToLoad {
  public String url;
  public ImageView imageView;

  public PhotoToLoad(String u, ImageView i) {
   url = u;
   imageView = i;
  }
 }

 class PhotosLoader implements Runnable {
  PhotoToLoad photoToLoad;

  PhotosLoader(PhotoToLoad photoToLoad) {
   this.photoToLoad = photoToLoad;
  }

  @Override
  public void run() {
   if (imageViewReused(photoToLoad))
    return;
   Bitmap bmp = getBitmap(photoToLoad.url);
   memoryCache.put(photoToLoad.url, bmp);
   if (imageViewReused(photoToLoad))
    return;
   BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
   // 更新的操作放在UI線程中
   Activity a = (Activity) photoToLoad.imageView.getContext();
   a.runOnUiThread(bd);
  }
 }

 /**
  * 防止圖片錯位
  *
  * @param photoToLoad
  * @return
  */
 boolean imageViewReused(PhotoToLoad photoToLoad) {
  String tag = imageViews.get(photoToLoad.imageView);
  if (tag == null || !tag.equals(photoToLoad.url))
   return true;
  return false;
 }

 // 用於在UI線程中更新界面
 class BitmapDisplayer implements Runnable {
  Bitmap bitmap;
  PhotoToLoad photoToLoad;

  public BitmapDisplayer(Bitmap b, PhotoToLoad p) {
   bitmap = b;
   photoToLoad = p;
  }

  public void run() {
   if (imageViewReused(photoToLoad))
    return;
   if (bitmap != null)
    photoToLoad.imageView.setImageBitmap(bitmap);
   else
    photoToLoad.imageView.setImageResource(stub_id);
  }
 }

 public void clearCache() {
  memoryCache.clear();
  fileCache.clear();
 }

 public static void CopyStream(InputStream is, OutputStream os) {
  final int buffer_size = 1024;
  try {
   byte[] bytes = new byte[buffer_size];
   for (;;) {
    int count = is.read(bytes, 0, buffer_size);
    if (count == -1)
     break;
    os.write(bytes, 0, count);
   }
  } catch (Exception ex) {
  }
 }
}
主要流程是先從內存緩存中查找,若沒有再開線程,從文件緩存中查找都沒有則從指定的url中查找,並對bitmap進行處理,最後通過下面方法對UI進行更新操作。

 

[java]
a.runOnUiThread(...); 

a.runOnUiThread(...);
在你的程序中的基本用法:

 

[java]
<SPAN style="FONT-SIZE: 18px"><STRONG>ImageLoader imageLoader=new ImageLoader(context); 
... 
imageLoader.DisplayImage(url, imageView);</STRONG></SPAN> 

ImageLoader imageLoader=new ImageLoader(context);
...
imageLoader.DisplayImage(url, imageView);
比如你的放在你的ListView的adapter的getView()方法中,當然也適用於GridView。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved