天天看點

android之listview緩存圖檔(緩存優化)

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

1、采用線程池

2、記憶體緩存+檔案緩存

3、記憶體緩存中網上很多是采用softreference來防止堆溢出,這兒嚴格限制隻能使用最大jvm記憶體的1/4

4、對下載下傳的圖檔進行按比例縮放,以減少記憶體的消耗

具體的代碼裡面說明。先放上記憶體緩存類的代碼memorycache.java:

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) {  

            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,代碼會簡單很多,但是我推薦上面的方法。

    private map<string, softreference<bitmap>> cache = collections  

            .synchronizedmap(new hashmap<string, softreference<bitmap>>());  

        if (!cache.containskey(id))  

        softreference<bitmap> ref = cache.get(id);  

        return ref.get();  

        cache.put(id, new softreference<bitmap>(bitmap));  

下面是檔案緩存類的代碼filecache.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;  

        file[] files = cachedir.listfiles();  

        if (files == null)  

            return;  

        for (file f : files)  

            f.delete();  

最後最重要的加載圖檔的類,imageloader.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中下載下傳圖檔  

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

    // decode這個圖檔并且按比例縮放以減少記憶體消耗,虛拟機對每張圖檔的緩存大小也是有限制的  

    private bitmap decodefile(file f) {  

            // 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)  

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

            bitmapdisplayer bd = new bitmapdisplayer(bmp, phototoload);  

            // 更新的操作放在ui線程中  

            activity a = (activity) phototoload.imageview.getcontext();  

            a.runonuithread(bd);  

     * 防止圖檔錯位 

     * @param phototoload 

    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;  

        public bitmapdisplayer(bitmap b, phototoload p) {  

            bitmap = b;  

            phototoload = p;  

            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;  

            byte[] bytes = new byte[buffer_size];  

            for (;;) {  

                int count = is.read(bytes, 0, buffer_size);  

                if (count == -1)  

                os.write(bytes, 0, count);  

主要流程是先從記憶體緩存中查找,若沒有再開線程,從檔案緩存中查找都沒有則從指定的url中查找,并對bitmap進行處理,最後通過下面方法對ui進行更新操作。

a.runonuithread(...);  

在你的程式中的基本用法:

imageloader imageloader=new imageloader(context);  

...  

imageloader.displayimage(url, imageview);  

比如你的放在你的listview的adapter的getview()方法中,當然也适用于gridview。ok,先到這。

繼續閱讀