• 【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、实现

    (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>
    最终运行结果如下图所示:
  • 相关阅读:
    Delphi 日期函数列表
    Delphi Copy 函数 和 Pos函数
    delphi xe10 手机程序事件服务操作、退出键操作
    delphi xe10 安卓设备信息
    delphi xe10 获取屏幕截图
    Battery electric vehicles (BEVs) 快充技术
    短波红外(SWIR)相机camera
    多核片上系统(SoC)架构的嵌入式DSP软件设计
    工业4.0是个白日梦吗?
    电子设计搜索引擎引入分析和见解
  • 原文地址:https://www.cnblogs.com/itgungnir/p/6211002.html
Copyright © 2020-2023  润新知