【Android - 进阶】之图片三级缓存的原理及实现

来源:互联网 发布:linux 输出文件夹大小 编辑:程序博客网 时间:2024/06/07 09:58

        在Android开发中,如果图片过多,而我们又没有对图片进行有效的缓存,就很容易导致OOM(Out Of Memory)错误。因此,图片的缓存是非常重要的,尤其是对图片非常多的应用。现在很多框架都做了很好的图片缓存处理,如【Fresco】、【Glide】等。

        本帖主要介绍以下Android中图片的三级缓存机制的原理及其应用。本帖中的代码都是使用Android原生的代码编写的。

1、原理

        Android图片三级缓存的原理如下图所示:


        可见,Android中图片的三级缓存主要是强引用、软银用和文件系统。

        Android原生为我们提供了一个LruCache,其中维护着一个LinkedHashMap。LruCache可以用来存储各种类型的数据,但最常见的是存储图片(Bitmap)。LruCache创建LruCache时,我们需要设置它的大小,一般是系统最大存储空间的八分之一。LruCache的机制是存储最近、最后使用的图片,如果LruCache中的图片大小超过了其默认大小,则会将最老、最远使用的图片移除出去。

        当图片被LruCache移除的时候,我们需要手动将这张图片添加到软引用(SoftReference)中。我们需要在项目中维护一个由SoftReference组成的集合,其中存储被LruCache移除出来的图片。软引用的一个好处是当系统空间紧张的时候,软引用可以随时销毁,因此软引用是不会影响系统运行的,换句话说,如果系统因为某个原因OOM了,那么这个原因肯定不是软引用引起的。


        下面叙述一下三级缓存的流程

        当我们的APP中想要加载某张图片时,先去LruCache中寻找图片,如果LruCache中有,则直接取出来使用,如果LruCache中没有,则去SoftReference中寻找,如果SoftReference中有,则从SoftReference中取出图片使用,同时将图片重新放回到LruCache中,如果SoftReference中也没有图片,则去文件系统中寻找,如果有则取出来使用,同时将图片添加到LruCache中,如果没有,则连接网络从网上下载图片。图片下载完成后,将图片保存到文件系统中,然后放到LruCache中。


2、实现

(1)网络访问工具类HttpUtil:
import java.io.ByteArrayOutputStream;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.URL;/** * 访问Http的工具类 */public class HttpUtil {    private static HttpUtil instance;    private HttpUtil() {    }    public static HttpUtil getInstance() {        if (instance == null) {            synchronized (HttpUtil.class) {                if (instance == null) {                    instance = new HttpUtil();                }            }        }        return instance;    }    /**     * 通过path(URL)访问网络获取返回的字节数组     */    public byte[] getByteArrayFromWeb(String path) {        byte[] b = null;        InputStream is = null;        ByteArrayOutputStream baos = null;        try {            URL url = new URL(path);            HttpURLConnection connection = (HttpURLConnection) url.openConnection();            connection.setRequestMethod("GET");            connection.setDoInput(true);            connection.setConnectTimeout(5000);            connection.connect();            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {                baos = new ByteArrayOutputStream();                is = connection.getInputStream();                byte[] tmp = new byte[1024];                int length = 0;                while ((length = is.read(tmp)) != -1) {                    baos.write(tmp, 0, length);                }            }            b = baos.toByteArray();        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                if (is != null) {                    is.close();                }                if (baos != null) {                    baos.close();                }            } catch (Exception e) {                e.printStackTrace();            }        }        return b;    }}
(2)操作文件系统的工具类FileUtil:
import android.content.Context;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;/** * 操作内存文件的工具类 */public class FileUtil {    private static FileUtil instance;    private Context context;    private FileUtil(Context context) {        this.context = context;    }    public static FileUtil getInstance(Context context) {        if (instance == null) {            synchronized (FileUtil.class) {                if (instance == null) {                    instance = new FileUtil(context);                }            }        }        return instance;    }    /**     * 将文件存储到内存中     */    public void writeFileToStorage(String fileName, byte[] b) {        FileOutputStream fos = null;        try {            File file = new File(context.getFilesDir(), fileName);            fos = new FileOutputStream(file);            fos.write(b, 0, b.length);        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                if (fos != null) {                    fos.close();                }            } catch (Exception e) {                e.printStackTrace();            }        }    }    /**     * 从内存中读取文件的字节码     */    public byte[] readBytesFromStorage(String fileName) {        byte[] b = null;        FileInputStream fis = null;        ByteArrayOutputStream baos = null;        try {            fis = context.openFileInput(fileName);            baos = new ByteArrayOutputStream();            byte[] tmp = new byte[1024];            int len = 0;            while ((len = fis.read(tmp)) != -1) {                baos.write(tmp, 0, len);            }            b = baos.toByteArray();        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                if (fis != null) {                    fis.close();                }                if (baos != null) {                    baos.close();                }            } catch (Exception e) {                e.printStackTrace();            }        }        return b;    }}
(3)LruCache的子类ImageCache:
import android.graphics.Bitmap;import android.os.Build;import android.support.annotation.RequiresApi;import android.util.LruCache;import java.lang.ref.SoftReference;import java.util.Map;/** * 图片缓存 */@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR1)public class ImageCache extends LruCache<String, Bitmap> {    private Map<String, SoftReference<Bitmap>> cacheMap;    public ImageCache(Map<String, SoftReference<Bitmap>> cacheMap) {        super((int) (Runtime.getRuntime().maxMemory() / 8));        this.cacheMap = cacheMap;    }    @Override // 获取图片大小    protected int sizeOf(String key, Bitmap value) {        return value.getRowBytes() * value.getHeight();    }    @Override // 当有图片从LruCache中移除时,将其放进软引用集合中    protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {        if (oldValue != null) {            SoftReference<Bitmap> softReference = new SoftReference<Bitmap>(oldValue);            cacheMap.put(key, softReference);        }    }    public Map<String, SoftReference<Bitmap>> getCacheMap() {        return cacheMap;    }}
(4)三级缓存的工具类CacheUtil:
import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Build;import android.widget.ImageView;import java.io.File;import java.lang.ref.SoftReference;import java.util.HashMap;import java.util.Map;/** * 缓存工具类 */public class CacheUtil {    private static CacheUtil instance;    private Context context;    private ImageCache imageCache;    private CacheUtil(Context context) {        this.context = context;        Map<String, SoftReference<Bitmap>> cacheMap = new HashMap<>();        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { // SDK版本判断            this.imageCache = new ImageCache(cacheMap);        }    }    public static CacheUtil getInstance(Context context) {        if (instance == null) {            synchronized (CacheUtil.class) {                if (instance == null) {                    instance = new CacheUtil(context);                }            }        }        return instance;    }    /**     * 将图片添加到缓存中     */    private void putBitmapIntoCache(String fileName, byte[] data) {        // 将图片的字节数组写入到内存中        FileUtil.getInstance(context).writeFileToStorage(fileName, data);        // 将图片存入强引用(LruCache)        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {            imageCache.put(fileName, BitmapFactory.decodeByteArray(data, 0, data.length));        }    }    /**     * 从缓存中取出图片     */    private Bitmap getBitmapFromCache(String fileName) {        // 从强引用(LruCache)中取出图片        Bitmap bm = null;        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) { // SDK版本判断            bm = imageCache.get(fileName);            if (bm == null) {                // 如果图片不存在强引用中,则去软引用(SoftReference)中查找                Map<String, SoftReference<Bitmap>> cacheMap = imageCache.getCacheMap();                SoftReference<Bitmap> softReference = cacheMap.get(fileName);                if (softReference != null) {                    bm = softReference.get();                    imageCache.put(fileName, bm);                } else {                    // 如果图片不存在软引用中,则去内存中找                    byte[] data = FileUtil.getInstance(context).readBytesFromStorage(fileName);                    if (data != null && data.length > 0) {                        bm = BitmapFactory.decodeByteArray(data, 0, data.length);                        imageCache.put(fileName, bm);                    }                }            }        }        return bm;    }    /**     * 使用三级缓存为ImageView设置图片     */    public void setImageToView(final String path, final ImageView view) {        final String fileName = path.substring(path.lastIndexOf(File.separator) + 1);        Bitmap bm = getBitmapFromCache(fileName);        if (bm != null) {            view.setImageBitmap(bm);        } else {            // 从网络获取图片            new Thread(new Runnable() {                @Override                public void run() {                    byte[] b = HttpUtil.getInstance().getByteArrayFromWeb(path);                    if (b != null && b.length > 0) {                        // 将图片字节数组写入到缓存中                        putBitmapIntoCache(fileName, b);                        final Bitmap bm = BitmapFactory.decodeByteArray(b, 0, b.length);                        // 将从网络获取到的图片设置给ImageView                        view.post(new Runnable() {                            @Override                            public void run() {                                view.setImageBitmap(bm);                            }                        });                    }                }            }).start();        }    }}

3、调用

(1)MainActivity的布局文件activity_main.xml中的代码:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent">    <ListView        android:id="@+id/id_main_lv_lv"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:divider="#DDDDDD"        android:dividerHeight="1.0dip" /></RelativeLayout>
(2)MainActivity中的代码:
import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.widget.ListView;import com.example.itgungnir.testimagecache.R;import com.example.itgungnir.testimagecache.SharedData;import com.example.itgungnir.testimagecache.adapter.ImageAdapter;import java.util.Arrays;import java.util.List;public class MainActivity extends AppCompatActivity {    private ListView lv;    private List<String> urlList;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        lv = (ListView) findViewById(R.id.id_main_lv_lv);        initData();    }    // 初始化数据    private void initData() {        // 初始化图片URL列表        urlList = Arrays.asList(SharedData.IMAGE_URLS);    }    @Override    protected void onResume() {        super.onResume();        initView();    }    // 初始化视图    private void initView() {        // 为ListView适配数据        ImageAdapter adapter = new ImageAdapter(MainActivity.this, urlList);        lv.setAdapter(adapter);    }}
(3)ListView的适配器类ImageAdapter中的代码:
import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.widget.ListView;import com.example.itgungnir.testimagecache.R;import com.example.itgungnir.testimagecache.SharedData;import com.example.itgungnir.testimagecache.adapter.ImageAdapter;import java.util.Arrays;import java.util.List;public class MainActivity extends AppCompatActivity {    private ListView lv;    private List<String> urlList;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        lv = (ListView) findViewById(R.id.id_main_lv_lv);        initData();    }    // 初始化数据    private void initData() {        // 初始化图片URL列表        urlList = Arrays.asList(SharedData.IMAGE_URLS);    }    @Override    protected void onResume() {        super.onResume();        initView();    }    // 初始化视图    private void initView() {        // 为ListView适配数据        ImageAdapter adapter = new ImageAdapter(MainActivity.this, urlList);        lv.setAdapter(adapter);    }}
(4)ListView的Item的布局文件listitem_image.xml中的代码:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:orientation="vertical"    android:padding="10.0dip">    <ImageView        android:id="@+id/id_imageitem_image"        android:layout_width="100.0dip"        android:layout_height="100.0dip"        android:layout_gravity="center_horizontal"        android:contentDescription="@string/app_name"        android:scaleType="fitXY" /></LinearLayout>
最终运行结果如下图所示:


1 0
原创粉丝点击