【Android - 进阶】之图片三级缓存的原理及实现
在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、实现
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; } }
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; } }
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; } }
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、调用
<?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>
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); } }
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); } }
<?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>
posted on 2016-12-22 14:03 ITGungnir 阅读(15001) 评论(3) 编辑 收藏 举报