Android Image下载程序/缓存管理器

时间:2013-09-10 06:29:10

标签: android caching

Masters,我正在开发一个Google plus类似于android的应用程序。这将从服务器呈现提要。我正在使用自定义列表视图并填充它。之间,我有要在每个列表项中显示的图像。

我使用以下代码下载图片并输入视图。

我是从here得到的。还有很多其他地方我见过相同的代码。

这里的问题是

  1. 缓存没有发生。我记录了这些电话,发现每次都会重新下载图片。
  2. 我需要一个解决方案,我需要存储一些图像,当请求网址出现时,它应检查现有的缓存并提供图像。无需再次从网上下载。
  3. 任何人都可以建议我做任何好的库,或者对下面的代码进行任何简单的修复。

    请注意,我不是专家:)

    /*
     * Copyright (C) 2010 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    
    /**
     * 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(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;
        }
    
        /**
         * A patched InputSteam that tries harder to fully read the input stream.
         */
        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) break;
                    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) {
            @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.
         */
        private 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);
        }
    }
    

2 个答案:

答案 0 :(得分:8)

GitHub上有很多项目 Picasso
Universal Image Loader
Glide
Android Image Manager
Android Image Worker(这个是我自己的“自行车”,更多的是为了生产而不是为了生产)

<强> UPD

Facebook最近发布了自己的解决方案 - Fresco。没试过,但看起来很有希望。

答案 1 :(得分:5)

嗨,我希望这会有所帮助。这对我有用。

public class ImageLoader {

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

private static ImageLoader instance = null;

private ImageLoader() {

}

private ImageLoader(Context context) {

    fileCache = new FileCache(context);

    executorService = Executors.newFixedThreadPool(5);
}

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

public static void setInstance(ImageLoader instance) {
    ImageLoader.instance = instance;
}

final int stub_id = R.drawable.no_img;

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));
}

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

    // from SD cache
    Bitmap b = decodeFile(f);
    if (b != null)
        return b;

    // from web
    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 = new BufferedInputStream(conn.getInputStream());
        OutputStream os = new FileOutputStream(f);
        Utils.CopyStream(is, os);
        os.close();
        bitmap = decodeFile(f);
        return bitmap;
    } catch (Exception ex) {
        return null;
    }
}

// decodes image and scales it to reduce memory consumption
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;
    }

    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);
        Activity a = (Activity) photoToLoad.imageView.getContext();
        a.runOnUiThread(bd);
    }
}

boolean imageViewReused(PhotoToLoad photoToLoad) {
    String tag = imageViews.get(photoToLoad.imageView);
    if (tag == null || !tag.equals(photoToLoad.url))
        return true;
    return false;
}

// Used to display bitmap in the UI thread
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();
}}

MemoryCache.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();
}}

的Utils,JAVA

public class Utils {
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){}
}}

现在在Activity或Adapter中实现像这样的Imageloader。

 private ImageLoader imageLoader = null;
imageLoader = ImageLoader.getInstance(context);
ImageView image = (ImageView) findViewById(R.id.img);
imageLoader.DisplayImage(details.url_1, image);