Android引入高速缓存的异步加载全分辨率

Android引进高速缓存的异步加载全分辨率


为什么要缓存

通过图像缩放,我们这样做是对的异步加载优化的大图,但现在的App这不仅是一款高清大图。图。动不动就是图文混排。以图代文,假设这些图片都载入到内存中。必然会OOM。因此,在用户浏览完图像后。应当马上将这些废弃的图像回收,可是。这又带来了另一个问题。也就是当用户在浏览完一次图片后,假设还要返回去再进行又一次浏览,那么这些回收掉的图像又要又一次进行载入,保不准就要那些无聊到蛋疼的人在那一边看你回收GC。一边看你又一次载入。这两件事情,肯定是互相矛盾的。也是影响性能的一个非常重要的原因。

内存缓存

针对这样一个非常须要找到一个彼此平衡点的问题,Google提供了一套内存缓存技术。

内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了高速訪问的方法。当中最核心的类是LruCache 。这个类非常适合用来缓存图片,它的主要算法原理是把近期使用的对象用强引用存储在 LinkedHashMap 中,并且把近期最少使用的对象在缓存值达到预设定值之前从内存中移除。LruCache 是在support-v4中才引入的,在引入LruCache 之前。Google建议的是使用软引用或弱引用 (SoftReference or WeakReference)来进行内存缓存。可是从Android 2.3開始,GC算法改动,软引用与弱引用相同会优先被GC回收。所以这样的方法也就没有太高的使用价值了,如今网上非常多还在继续使用SoftReference 和WeakReference的文章。大多都是过时的文章,建议大家跟上党的步伐,与时俱进。


LruCache使用

内存缓存LruCache所使用的内存缓存大小是由开发人员决定的。开发人员须要依据图像的使用率、分辨率、訪问频率、设备性能等非常多因素进行考虑。

这个平衡点常常须要非常多经验和測试来决定。

使用LruCache非常easy:


private LruCache<String, Bitmap> mMemoryCaches;

// 获取应用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
// 分配cache
int cacheSize = maxMemory / 10;
mMemoryCaches = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };

// 从LruCache获取中获取缓存对象
public Bitmap getBitmapFromMemoryCaches(String url) {
    return mMemoryCaches.get(url);
}

// 添加缓存对象到LruCache
public void addBitmapToMemoryCaches(String url,Bitmap bitmap) {
    if (getBitmapFromMemoryCaches(url) == null) {
        mMemoryCaches.put(url, bitmap);
    }
}

首先,我们须要声明LruCache。接着。通过LruCache的构造方法创建缓存对象,并为其分配cacheSize,这个cacheSize通常我们须要通过Runtime来获取,获取当前系统分给App的可用内存,并将这些内存的一部分用做LruCache缓存。LruCache中必须重写sizeOf方法。通过这种方法,LruCache能够获取每一个缓存对象的大小。子类必须重写。由于默认的LruCache获取的是缓存的个数。

。。尼玛。

最后,我们提供两个方法getBitmapFromMemoryCaches和addBitmapToMemoryCaches分别用来获取和添加内存缓存到LruCache。
等等,我们好像还没写释放内存的方法,对,不用你写了,Lru算法能够保证cacheSize不会OOM,一旦超过这个大小,GC就会回收时间最长的对象,释放空间。

为异步处理添加一级缓存

OK。在了解了关于缓存的基础信息后,我们回到如今这个样例,想想怎么利用缓存来进行异步处理的优化。首先,ListView、GridView这些娇生惯养的玩意儿。碰不得摔不得,更不能在它滚的开心的时候,你还在后面拼命玩载入。所以,第一个重点,滚的时候就让它开心的滚,滚完了再開始载入。

滚完再载入

要实现这一点,我们能够通过给Adapter添加AbsListView.OnScrollListener接口来实现。
当然,另一点须要注意,第一次初始化的时候,一定要手动来载入图片,不然系统推断你没滚,仅仅能调用onScroll方法,不会调用onScrollStateChanged方法。并且我们也须要在onScroll方法中来不断获取可见的Item。特别要注意的是visibleItemCount,仅仅要大于0的时候,才觉得是開始显示图片了。

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (scrollState == SCROLL_STATE_IDLE) {
        mImageLoader.loadImages(mStart, mEnd);
    } else {
        mImageLoader.cancelAllTasks();
    }
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    mStart = firstVisibleItem;
    mEnd = firstVisibleItem + visibleItemCount;
    if (mFirstFlag  && visibleItemCount > 0) {
        mImageLoader.loadImages(mStart, mEnd);
        mFirstFlag = false;
    }
}

载入显示的项目

载入数据的时候,获取第一个能显示的Item和最后一个可见的Item,仅仅载入这一部分。所以我们创建一个方法——loadImages(int start, int end)。这种方法用来载入从start到end之间的Item数据。
载入的时候,先从内存缓存中去取,假设有,那说明近期已经载入过了,那直接载入就好了,假设没有取到,那就开启synctask去下载。

public void loadImages(int start, int end) {
    for (int i = start; i < end; i++) {
        String url = Images.IMAGE_URLS[i];
        Bitmap bitmap = getBitmapFromMemoryCaches(url);
        if (bitmap == null) {
            ASyncDownloadImage task = new ASyncDownloadImage(url);
            mTasks.add(task);
            task.execute(url);
        } else {
            ImageView imageView = (ImageView) mListView.findViewWithTag(url);
            imageView.setImageBitmap(bitmap);
        }
    }
}

这里我们在设置图片的时候,直接通过findViewWithTag。通过url来找到相应的Imageview,这里与之前不同是由于我们这里是依照start到end来进行载入,直接从ListView对象中获取相应的Imageview比較简单。

下载与Asynctask

下载依旧是使用老方法:
private static Bitmap getBitmapFromUrl(String urlString) {
    Bitmap bitmap;
    InputStream is = null;
    try {
        URL url = new URL(urlString);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        is = new BufferedInputStream(conn.getInputStream());
        bitmap = BitmapFactory.decodeStream(is);
        conn.disconnect();
        return bitmap;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (is != null)
                is.close();
        } catch (IOException e) {
        }
    }
    return null;
}

Asynctask也与之前基本相似:

class ASyncDownloadImage extends AsyncTask<String, Void, Bitmap> {

    private String url;

    public ASyncDownloadImage(String url) {
        this.url = url;
    }

    @Override
    protected Bitmap doInBackground(String... params) {
        url = params[0];
        Bitmap bitmap = getBitmapFromUrl(url);
        if (bitmap != null) {
            addBitmapToMemoryCaches(url, bitmap);
        }
        return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
        ImageView imageView = (ImageView) mListView.findViewWithTag(url);
        if (imageView != null && bitmap != null) {
            imageView.setImageBitmap(bitmap);
        }
        mTasks.remove(this);
    }
}

唯一不同的是。我们在下载好图像之后。会将图像载入到Lrucache。

组装

OK,万事具备。准备刷代码。在刷之前,我们先来又一次整理下思路,首先,在Adapter中,一载入ListView,就開始下载显示范围内的Item的图像,这时候缓存中当然没有。所以都去下载了,下完了就显示在Item中,并缓存起来。假设还没下完,你就迫不及待的滚起来了,那么马上取消全部task。让ListView欢快的滚。滚完之后,继续载入。
OK,该讲的都讲了,以下我们開始刷代码了。一切尽在不言中,仅仅有代码最懂你。

package com.imooc.listviewacyncloader;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.LruCache;
import android.widget.ImageView;
import android.widget.ListView;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

public class ImageLoaderWithCaches {

    private Set<ASyncDownloadImage> mTasks;
    private LruCache<String, Bitmap> mMemoryCaches;
    private ListView mListView;

    public ImageLoaderWithCaches(ListView listview) {
        this.mListView = listview;
        mTasks = new HashSet<>();
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 10;
        mMemoryCaches = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
    }

    public void showImage(String url, ImageView imageView) {
        Bitmap bitmap = getBitmapFromMemoryCaches(url);
        if (bitmap == null) {
            imageView.setImageResource(R.drawable.ic_launcher);
        } else {
            imageView.setImageBitmap(bitmap);
        }
    }

    public Bitmap getBitmapFromMemoryCaches(String url) {
        return mMemoryCaches.get(url);
    }

    public void addBitmapToMemoryCaches(String url,Bitmap bitmap) {
        if (getBitmapFromMemoryCaches(url) == null) {
            mMemoryCaches.put(url, bitmap);
        }
    }

    public void loadImages(int start, int end) {
        for (int i = start; i < end; i++) {
            String url = Images.IMAGE_URLS[i];
            Bitmap bitmap = getBitmapFromMemoryCaches(url);
            if (bitmap == null) {
                ASyncDownloadImage task = new ASyncDownloadImage(url);
                mTasks.add(task);
                task.execute(url);
            } else {
                ImageView imageView = (ImageView) mListView.findViewWithTag(url);
                imageView.setImageBitmap(bitmap);
            }
        }
    }

    private static Bitmap getBitmapFromUrl(String urlString) {
        Bitmap bitmap;
        InputStream is = null;
        try {
            URL url = new URL(urlString);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            is = new BufferedInputStream(conn.getInputStream());
            bitmap = BitmapFactory.decodeStream(is);
            conn.disconnect();
            return bitmap;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null)
                    is.close();
            } catch (IOException e) {
            }
        }
        return null;
    }

    public void cancelAllTasks() {
        if (mTasks != null) {
            for (ASyncDownloadImage task : mTasks) {
                task.cancel(false);
            }
        }
    }

    class ASyncDownloadImage extends AsyncTask<String, Void, Bitmap> {

        private String url;

        public ASyncDownloadImage(String url) {
            this.url = url;
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            url = params[0];
            Bitmap bitmap = getBitmapFromUrl(url);
            if (bitmap != null) {
                addBitmapToMemoryCaches(url, bitmap);
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            ImageView imageView = (ImageView) mListView.findViewWithTag(url);
            if (imageView != null && bitmap != null) {
                imageView.setImageBitmap(bitmap);
            }
            mTasks.remove(this);
        }
    }
}

以下是Adapter的代码:

package com.imooc.listviewacyncloader;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;

import java.util.List;

public class MyAdapterUseCaches extends BaseAdapter implements
        AbsListView.OnScrollListener {

    private LayoutInflater mInflater;
    private List<String> mData;
    private ImageLoaderWithCaches mImageLoader;
    private int mStart = 0, mEnd = 0;
    private boolean mFirstFlag;

    public MyAdapterUseCaches(Context context, List<String> data, ListView listView) {
        this.mData = data;
        mInflater = LayoutInflater.from(context);
        mImageLoader = new ImageLoaderWithCaches(listView);
        mImageLoader.loadImages(mStart, mEnd);
        mFirstFlag = true;
        listView.setOnScrollListener(this);
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Object getItem(int position) {
        return mData.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        String url = mData.get(position);
        ViewHolder viewHolder = null;
        if (convertView == null) {
            viewHolder = new ViewHolder();
            convertView = mInflater.inflate(R.layout.listview_item, null);
            viewHolder.imageView =
                    (ImageView) convertView.findViewById(R.id.iv_lv_item);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.imageView.setTag(url);
        viewHolder.imageView.setImageResource(R.drawable.ic_launcher);
        mImageLoader.showImage(url, viewHolder.imageView);
        return convertView;
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == SCROLL_STATE_IDLE) {
            mImageLoader.loadImages(mStart, mEnd);
        } else {
            mImageLoader.cancelAllTasks();
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mStart = firstVisibleItem;
        mEnd = firstVisibleItem + visibleItemCount;
        if (mFirstFlag  && visibleItemCount > 0) {
            mImageLoader.loadImages(mStart, mEnd);
            mFirstFlag = false;
        }
    }

    public class ViewHolder {
        public ImageView imageView;
    }
}

是不是非常easy,如今引入缓存了,下载过的图片会临时保存在内存中。妈妈再也不用操心你OOM啦。
我们下拉试试,下载完的图片再次出现也能够马上载入了,除非滑动太多导致GC。



能够就看见。我们的这次利用缓存进行载入有这样几个特点:
1、初始化的时候载入
2、滑动的时候才载入
3、载入的内容暂存缓存中
4、仅仅载入显示的区域


后面我们将继续优化缓存。未完待续~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我的Github
我的视频 亩类网络


版权声明:本文博主原创文章。博客,未经同意不得转载。

原文地址:https://www.cnblogs.com/bhlsheji/p/4915239.html