2011-05-03 55 views
1

我有一個ListView顯示從互聯網下載的圖像。滾動時,我的用戶界面非常不穩定/很慢。不知道爲什麼。爲什麼滾動時來自網絡的圖像會鎖定我的ListView UI?

這裏是我的ImageDownloader.java

/** 
* This helper class download images from the Internet and binds those with the provided ImageView. 
* 
* <p>It requires the INTERNET permission, which should be added to your application's manifest 
* file.</p> 
* 
* A local cache of downloaded images is maintained internally to improve performance. 
*/ 
public class ImageDownloader { 
    private static final String LOG_TAG = "ImageDownloader"; 

    public enum Mode { NO_ASYNC_TASK, NO_DOWNLOADED_DRAWABLE, CORRECT } 
    private Mode mode = Mode.NO_ASYNC_TASK; 

    /** 
    * Download the specified image from the Internet and binds it to the provided ImageView. The 
    * binding is immediate if the image is found in the cache and will be done asynchronously 
    * otherwise. A null bitmap will be associated to the ImageView if an error occurs. 
    * 
    * @param url The URL of the image to download. 
    * @param imageView The ImageView to bind the downloaded image to. 
    */ 
    public void download(String url, ImageView imageView) { 
     resetPurgeTimer(); 
     Bitmap bitmap = getBitmapFromCache(url); 

     if (bitmap == null) { 
      forceDownload(url, imageView); 
     } else { 
      cancelPotentialDownload(url, imageView); 
      imageView.setImageBitmap(bitmap); 
     } 
    } 

    /* 
    * Same as download but the image is always downloaded and the cache is not used. 
    * Kept private at the moment as its interest is not clear. 
     private void forceDownload(String url, ImageView view) { 
      forceDownload(url, view, null); 
     } 
    */ 

    /** 
    * Same as download but the image is always downloaded and the cache is not used. 
    * Kept private at the moment as its interest is not clear. 
    */ 
    private void forceDownload(String url, ImageView imageView) { 
     // State sanity: url is guaranteed to never be null in DownloadedDrawable and cache keys. 
     if (url == null) { 
      imageView.setImageDrawable(null); 
      return; 
     } 

     if (cancelPotentialDownload(url, imageView)) { 
      switch (mode) { 
       case NO_ASYNC_TASK: 
        Bitmap bitmap = downloadBitmap(url); 
        addBitmapToCache(url, bitmap); 
        imageView.setImageBitmap(bitmap); 
        break; 

       case NO_DOWNLOADED_DRAWABLE: 
        imageView.setMinimumHeight(156); 
        BitmapDownloaderTask task = new BitmapDownloaderTask(imageView); 
        task.execute(url); 
        break; 

       case CORRECT: 
        task = new BitmapDownloaderTask(imageView); 
        DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task); 
        imageView.setImageDrawable(downloadedDrawable); 
        imageView.setMinimumHeight(156); 
        task.execute(url); 
        break; 
      } 
     } 
    } 

    /** 
    * Returns true if the current download has been canceled or if there was no download in 
    * progress on this image view. 
    * Returns false if the download in progress deals with the same url. The download is not 
    * stopped in that case. 
    */ 
    private static boolean cancelPotentialDownload(String url, ImageView imageView) { 
     BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); 

     if (bitmapDownloaderTask != null) { 
      String bitmapUrl = bitmapDownloaderTask.url; 
      if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) { 
       bitmapDownloaderTask.cancel(true); 
      } else { 
       // The same URL is already being downloaded. 
       return false; 
      } 
     } 
     return true; 
    } 

    /** 
    * @param imageView Any imageView 
    * @return Retrieve the currently active download task (if any) associated with this imageView. 
    * null if there is no such task. 
    */ 
    private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) { 
     if (imageView != null) { 
      Drawable drawable = imageView.getDrawable(); 
      if (drawable instanceof DownloadedDrawable) { 
       DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable; 
       return downloadedDrawable.getBitmapDownloaderTask(); 
      } 
     } 
     return null; 
    } 

    Bitmap downloadBitmap(String url) { 
     //final int IO_BUFFER_SIZE = 4 * 1024; 

     // AndroidHttpClient is not allowed to be used from the main thread 
     final HttpClient client = (mode == Mode.NO_ASYNC_TASK) ? new DefaultHttpClient() : 
      AndroidHttpClient.newInstance("Android"); 
     final HttpGet getRequest = new HttpGet(url); 

     try { 
      HttpResponse response = client.execute(getRequest); 
      final int statusCode = response.getStatusLine().getStatusCode(); 
      if (statusCode != HttpStatus.SC_OK) { 
       Log.w("ImageDownloader", "Error " + statusCode + 
         " while retrieving bitmap from " + url); 
       return null; 
      } 

      final HttpEntity entity = response.getEntity(); 
      if (entity != null) { 
       InputStream inputStream = null; 
       try { 
        inputStream = entity.getContent(); 
        // return BitmapFactory.decodeStream(inputStream); 
        // Bug on slow connections, fixed in future release. 
        return BitmapFactory.decodeStream(new FlushedInputStream(inputStream)); 
       } finally { 
        if (inputStream != null) { 
         inputStream.close(); 
        } 
        entity.consumeContent(); 
       } 
      } 
     } catch (IOException e) { 
      getRequest.abort(); 
      Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e); 
     } catch (IllegalStateException e) { 
      getRequest.abort(); 
      Log.w(LOG_TAG, "Incorrect URL: " + url); 
     } catch (Exception e) { 
      getRequest.abort(); 
      Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e); 
     } finally { 
      if ((client instanceof AndroidHttpClient)) { 
       ((AndroidHttpClient) client).close(); 
      } 
     } 
     return null; 
    } 

    /* 
    * An InputStream that skips the exact number of bytes provided, unless it reaches EOF. 
    */ 
    static class FlushedInputStream extends FilterInputStream { 
     public FlushedInputStream(InputStream inputStream) { 
      super(inputStream); 
     } 

     @Override 
     public long skip(long n) throws IOException { 
      long totalBytesSkipped = 0L; 
      while (totalBytesSkipped < n) { 
       long bytesSkipped = in.skip(n - totalBytesSkipped); 
       if (bytesSkipped == 0L) { 
        int b = read(); 
        if (b < 0) { 
         break; // we reached EOF 
        } else { 
         bytesSkipped = 1; // we read one byte 
        } 
       } 
       totalBytesSkipped += bytesSkipped; 
      } 
      return totalBytesSkipped; 
     } 
    } 

    /** 
    * The actual AsyncTask that will asynchronously download the image. 
    */ 
    class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> { 
     private String url; 
     private final WeakReference<ImageView> imageViewReference; 

     public BitmapDownloaderTask(ImageView imageView) { 
      imageViewReference = new WeakReference<ImageView>(imageView); 
     } 

     /** 
     * Actual download method. 
     */ 
     @Override 
     protected Bitmap doInBackground(String... params) { 
      url = params[0]; 
      return downloadBitmap(url); 
     } 

     /** 
     * Once the image is downloaded, associates it to the imageView 
     */ 
     @Override 
     protected void onPostExecute(Bitmap bitmap) { 
      if (isCancelled()) { 
       bitmap = null; 
      } 

      addBitmapToCache(url, bitmap); 

      if (imageViewReference != null) { 
       ImageView imageView = imageViewReference.get(); 
       BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); 
       // Change bitmap only if this process is still associated with it 
       // Or if we don't use any bitmap to task association (NO_DOWNLOADED_DRAWABLE mode) 
       if ((this == bitmapDownloaderTask) || (mode != Mode.CORRECT)) { 
        imageView.setImageBitmap(bitmap); 
       } 
      } 
     } 
    } 


    /** 
    * A fake Drawable that will be attached to the imageView while the download is in progress. 
    * 
    * <p>Contains a reference to the actual download task, so that a download task can be stopped 
    * if a new binding is required, and makes sure that only the last started download process can 
    * bind its result, independently of the download finish order.</p> 
    */ 
    static class DownloadedDrawable extends ColorDrawable { 
     private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference; 

     public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) { 
      super(Color.BLACK); 
      bitmapDownloaderTaskReference = 
       new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask); 
     } 

     public BitmapDownloaderTask getBitmapDownloaderTask() { 
      return bitmapDownloaderTaskReference.get(); 
     } 
    } 

    public void setMode(Mode mode) { 
     this.mode = mode; 
     clearCache(); 
    } 


    /* 
    * Cache-related fields and methods. 
    * 
    * We use a hard and a soft cache. A soft reference cache is too aggressively cleared by the 
    * Garbage Collector. 
    */ 

    private static final int HARD_CACHE_CAPACITY = 10; 
    private static final int DELAY_BEFORE_PURGE = 10 * 1000; // in milliseconds 

    // Hard cache, with a fixed maximum capacity and a life duration 
    private final HashMap<String, Bitmap> sHardBitmapCache = 
     new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY/2, 0.75f, true) { 
     /** 
      * 
      */ 
      private static final long serialVersionUID = 1L; 

     @Override 
     protected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) { 
      if (size() > HARD_CACHE_CAPACITY) { 
       // Entries push-out of hard reference cache are transferred to soft reference cache 
       sSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue())); 
       return true; 
      } else 
       return false; 
     } 
    }; 

    // Soft cache for bitmaps kicked out of hard cache 
    private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache = 
     new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY/2); 

    private final Handler purgeHandler = new Handler(); 

    private final Runnable purger = new Runnable() { 
     public void run() { 
      clearCache(); 
     } 
    }; 

    /** 
    * Adds this bitmap to the cache. 
    * @param bitmap The newly downloaded bitmap. 
    */ 
    private void addBitmapToCache(String url, Bitmap bitmap) { 
     if (bitmap != null) { 
      synchronized (sHardBitmapCache) { 
       sHardBitmapCache.put(url, bitmap); 
      } 
     } 
    } 

    /** 
    * @param url The URL of the image that will be retrieved from the cache. 
    * @return The cached bitmap or null if it was not found. 
    */ 
    public Bitmap getBitmapFromCache(String url) { 
     // First try the hard reference cache 
     synchronized (sHardBitmapCache) { 
      final Bitmap bitmap = sHardBitmapCache.get(url); 
      if (bitmap != null) { 
       // Bitmap found in hard cache 
       // Move element to first position, so that it is removed last 
       sHardBitmapCache.remove(url); 
       sHardBitmapCache.put(url, bitmap); 
       return bitmap; 
      } 
     } 

     // Then try the soft reference cache 
     SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url); 
     if (bitmapReference != null) { 
      final Bitmap bitmap = bitmapReference.get(); 
      if (bitmap != null) { 
       // Bitmap found in soft cache 
       return bitmap; 
      } else { 
       // Soft reference has been Garbage Collected 
       sSoftBitmapCache.remove(url); 
      } 
     } 

     return null; 
    } 

    /** 
    * Clears the image cache used internally to improve performance. Note that for memory 
    * efficiency reasons, the cache will automatically be cleared after a certain inactivity delay. 
    */ 
    public void clearCache() { 
     sHardBitmapCache.clear(); 
     sSoftBitmapCache.clear(); 
    } 

    /** 
    * Allow a new delay before the automatic cache clear is done. 
    */ 
    private void resetPurgeTimer() { 
     purgeHandler.removeCallbacks(purger); 
     purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE); 
    } 
} 

這裏是ListAdapter

private class StreamAdapter extends ArrayAdapter<JSONObject> { 

     private ArrayList<JSONObject> messages; 
     private final ImageDownloader imageDownloader = new ImageDownloader(); 

     public StreamAdapter(Context context, int textViewResourceId, ArrayList<JSONObject> messages) { 
      super(context, textViewResourceId, messages); 
      this.messages = messages; 
     } 

     @Override 
     public View getView(int position, View convertView, ViewGroup parent) { 
      View v = convertView; 
      if (v == null) { 
       LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
       v = vi.inflate(R.layout.message_row, null); 
      } 

      JSONObject aMessage = messages.get(position); 

      if (aMessage != null) { 
       TextView usernameTextView = (TextView) v.findViewById(R.id.usernameTextView); 
       TextView bodyTextView = (TextView) v.findViewById(R.id.bodyTextView); 
       TextView dateTextView = (TextView) v.findViewById(R.id.dateTextView); 
       ImageView avatarImageView = (ImageView)v.findViewById(R.id.avatarImageView); 

       if (usernameTextView != null) { 
        try { 
         usernameTextView.setText(Html.fromHtml(aMessage.getString("user_login"))); 
        } catch (JSONException e) { 
         // TODO Auto-generated catch block 
         e.printStackTrace(); 
        } 
       } 
       if (bodyTextView != null) { 
        try { 
         bodyTextView.setText((Html.fromHtml(aMessage.getString("body")))); 
        } catch (JSONException e) { 
         // TODO Auto-generated catch block 
         e.printStackTrace(); 
        } 
       } 
       if (dateTextView != null) { 
        try { 
         dateTextView.setText(aMessage.getString("updated_at")); 
        } catch (JSONException e) { 
         // TODO Auto-generated catch block 
         e.printStackTrace(); 
        } 
       } 

       if (avatarImageView != null) { 
        try { 
         imageDownloader.download(aMessage.getString("avatar_url"), avatarImageView); 
        } catch (JSONException e) { 
         // TODO Auto-generated catch block 
         e.printStackTrace(); 
        } 
       } 
      } 

      return v; 
     } 
    } 
+0

圖像加載到緩存後,列表滾動仍然不穩定嗎? – dmon 2011-05-03 02:26:15

+0

不,不是。但是加載到緩存中需要一段時間,大概需要15-20秒。這是用戶滾動並經歷一段時間的足夠時間。任何更好的方法來做到這一點? – 2011-05-03 02:31:49

+1

好吧,我在你的代碼中看到你有不同的「模式」,但是你已經硬編碼了「NO_ASYNC_TASK」,這意味着你正在UI線程中下載圖像,這會阻止它。你爲什麼設置這種模式? AsyncTasks是否給你帶來麻煩? – dmon 2011-05-03 02:40:46

回答

1

的問題是,你檢索圖像在UI線程,因爲你有你「模式」變量設置爲NO_ASYNC_TASK。

相關問題