2012-07-02 23 views
2

我正在構建一個相對基礎的新聞閱讀器應用程序,它涉及在自定義列表視圖中顯示新聞(圖像+標題+每個列表元素的簡短說明)。如何在Android中有效地存儲位圖?

我的問題是如何存儲從服務器下載的圖像,然後將它們附加到列表視圖中?圖像的尺寸相對較小,通常爲200 x 200,格式爲.jpeg。

這不僅僅是「如何有效地做到這一點」的問題,因爲當我使用默認的「ic_launcher」圖標而不是位圖時,我已經注意到低端手機的滯後。

它會以更快的速度將它們存儲爲文件或者到新聞數據庫與其他新聞數據一起時,應用程序啓動並同步了消息或緩存他們...?

我應該怎麼辦?

+0

究竟你 「滯後」 是什麼意思?在ListView中停頓或長時間加載直到顯示圖片? –

+2

您可以使用一些技巧來提高性能。請參閱[本文檔中的文檔](http://developer.android.com/training/displaying-bitmaps/index.html) – FoamyGuy

+0

當使用ic_launcher圖像而不是實際位圖時在列表視圖中出現口吃。問題是如何在應用之前高效地存儲位圖? – Eugen

回答

2

更好的是,你可以通過ImageManager類使用SoftReference。

在你ListAdpater的getView()方法中調用ImageManager的displayImage()方法。

的ImageManager編碼例:

public class ImageManagerExemple { 

private static final String LOG_TAG = "ImageManager"; 

private static ImageManagerExemple instance = null; 

public static ImageManagerExemple getInstance(Context context) { 
    if (instance == null) { 
     instance = new ImageManagerExemple(context); 
    } 
    return instance;   
} 

private HashMap<String, SoftReference<Bitmap>> imageMap = new HashMap<String, SoftReference<Bitmap>>(); 

private Context context; 
private File cacheDir; 

private ImageManagerExemple(Context context) { 
    this.context = context; 
    // Find the dir to save cached images 
    String sdState = android.os.Environment.getExternalStorageState(); 
    if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) { 
     File sdDir = android.os.Environment.getExternalStorageDirectory();  
     cacheDir = new File(sdDir,"data/yourappname"); 
    } else { 
     cacheDir = context.getCacheDir(); 
    } 
    if(!cacheDir.exists()) { 
     cacheDir.mkdirs(); 
    } 
} 


/** 
* Display web Image loading thread 
* @param imageUrl picture web url 
* @param imageView target 
* @param imageWaitRef picture during loading 
*/ 
public void displayImage(String imageUrl, ImageView imageView, Integer imageWaitRef) { 
    String imageKey = imageUrl;  
    imageView.setTag(imageKey); 
    if(imageMap.containsKey(imageKey) && imageMap.get(imageKey).get() != null) { 
     Bitmap bmp = imageMap.get(imageKey).get(); 
     imageView.setImageBitmap(bmp); 
    } else { 
     queueImage(imageUrl, imageView); 
     if(imageWaitRef != null) 
      imageView.setImageResource(imageWaitRef); 
    } 
} 

private void queueImage(String url, ImageView imageView) { 
    ImageRef imgRef=new ImageRef(url, imageView); 
    // Start thread 
    Thread imageLoaderThread = new Thread(new ImageQueueManager(imgRef)); 
    // Make background thread low priority, to avoid affecting UI performance 
    imageLoaderThread.setPriority(Thread.NORM_PRIORITY-1); 
    imageLoaderThread.start(); 
} 

private Bitmap getBitmap(String url) { 
    String filename = String.valueOf(url.hashCode()); 
    File f = new File(cacheDir, filename); 
    try { 
     // Is the bitmap in our cache? 
     Bitmap bitmap = BitmapFactory.decodeFile(f.getPath()); 
     if(bitmap != null) return bitmap; 
     // Nope, have to download it 
     bitmap = ImageServerUtils.pictureUrlToBitmap(url); 
     // save bitmap to cache for later 
     writeFile(bitmap, f); 
     return bitmap; 
    } catch (Exception ex) { 
     ex.printStackTrace(); 
     Log.e(LOG_TAG, ""+ex.getLocalizedMessage()); 
     return null; 
    } catch (OutOfMemoryError e) { 
     Log.e(LOG_TAG, "OutOfMemoryError : "+e.getLocalizedMessage()); 
     e.printStackTrace(); 
     return null; 
    } 
} 

private void writeFile(Bitmap bmp, File f) { 
    if (bmp != null && f != null) { 
     FileOutputStream out = null; 

     try { 
      out = new FileOutputStream(f); 
      //bmp.compress(Bitmap.CompressFormat.PNG, 80, out); 
      bmp.compress(Bitmap.CompressFormat.JPEG, 80, out); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
     finally { 
      try { if (out != null) out.close(); } 
      catch(Exception ex) {} 
     } 
    } 
} 


private class ImageRef { 
    public String imageUrl; 
    public ImageView imageView; 

    public ImageRef(String imageUrl, ImageView i) { 
     this.imageUrl=imageUrl; 
     this.imageView=i; 
    } 
} 

private class ImageQueueManager implements Runnable { 
    private ImageRef imageRef; 
    public ImageQueueManager(ImageRef imageRef) { 
     super(); 
     this.imageRef = imageRef; 
    } 
    @Override 
    public void run() { 
     ImageRef imageToLoad = this.imageRef; 
     if (imageToLoad != null) { 
      Bitmap bmp = getBitmap(imageToLoad.imageUrl); 
      String imageKey = imageToLoad.imageUrl; 
      imageMap.put(imageKey, new SoftReference<Bitmap>(bmp)); 
      Object tag = imageToLoad.imageView.getTag(); 

      // Make sure we have the right view - thread safety defender 
      if (tag != null && ((String)tag).equals(imageKey)) { 
       BitmapDisplayer bmpDisplayer = new BitmapDisplayer(bmp, imageToLoad.imageView);       
       Activity a = (Activity)imageToLoad.imageView.getContext();       
       a.runOnUiThread(bmpDisplayer); 
      } 
     } 
    } 
} 

//Used to display bitmap in the UI thread 
private class BitmapDisplayer implements Runnable { 
    Bitmap bitmap; 
    ImageView imageView; 

    public BitmapDisplayer(Bitmap b, ImageView i) { 
     bitmap=b; 
     imageView=i; 
    } 
    @Override 
    public void run() { 
     if(bitmap != null) { 
      imageView.setImageBitmap(bitmap); 
     } 
    } 
} 
1

訣竅得到平滑的ListView滾動而不口吃是當用戶正在滾動以任何方式,形狀或形式,以對其進行更新。 Afaik,這實際上是iOS如何設法使其ListViews平滑:它在用戶的手指上不允許對它進行任何更改(以及一般的UI)。

只是註釋掉改變你的ListView的同時使所有的位圖加載代碼完好的任何代碼,你會看到,位圖中背景中的實際負載並沒有真正影響性能的。問題在於UI線程無法跟上視圖更新並同時滾動。

您可以通過使用一個OnScrollListener阻止所有更新到ListView當用戶滾動它實現同樣的事情。一旦用戶停下來,你可以潛入所有未決的更新。 爲了提高性能,請儘量不要使用notifyDataSetChanged,而是迭代ListView的視圖並僅更新實際更改的視圖。

+0

感謝您的回答。真的很有意思,你說的,我不知道的。問題是我沒有使用notifyDataSetChanged()或其他列表修飾符。在啓動listview活動之前加載圖像,而不是在後臺加載。 – Eugen

+0

我還想知道的是如何通過存儲圖像來縮短加載時間。我想象緩存它們是最快的方法......? – Eugen

+1

我通過在本機代碼存儲器空間中創建自己的圖像緩存,在我的圖像應用程序中解決了此問題。我的縮略圖比較小,每個圖像允許8K的RAM,因此存儲2000張圖像並不是什麼大不了的事情。 – BitBank

相關問題