关于Android中的三级缓存

摘要:
三级缓存的提出就是为了提升用户体验。对于GC来说,SoftReference的强度明显低于SrongReference。在android里面也是有用到的:FileCleaner.java。这些避免内存溢出的引用方式在Android2.3+的版本上已经不再起太大作用,因为垃圾回收器会频繁回收非强引用的对象,Android官方建议使用LRUCache。这样我们就能很好的理解要三级缓存了。doInBackground()方法中访问网路,这里用到的是Httpurlconnection,通过连接得到输入流,利用位图工厂转换成位图,返回。onPostExecute()方法在doInBackground()方法执行后执行,传入的参数数doInBackground()方法的返回值。

三级缓存的提出就是为了提升用户体验。当我们第一次打开应用获取图片时,先到网络去下载图片,然后依次存入内存缓存,磁盘缓存,当我们再一次需要用到刚才下载的这张图片时,就不需要再重复的到网络上去下载,直接可以从内存缓存和磁盘缓存中找,由于内存缓存速度较快,我们优先到内存缓存中寻找该图片,如果找到则运用,如果没有找到(内存缓存大小有限),那么我们再到磁盘缓存中去找。只要我们合理的去协调这三层缓存运用,便可以提升应用性能。三级缓存指的是:内存缓存、本地缓存、网络缓存。其各自的特点是内存缓存速度快, 优先读取,本地缓存速度其次, 内存没有,读本地,网络缓存速度最慢, 本地也没有,才访问网络。对于网络缓存理解起来较为容易直接从网络中获取资源,本地缓存可以存在SD卡中,内存缓存一般存在数组或集合中。需要在注意的是,数组和集合的生命周期依赖于它存在的activity中,因此当程序退出,一般情况下数组和集合中的资源会被释放。在具体了解三级缓存的工作原理之前有必要先介绍几个概念。

实例和对象:
对象是类的一个实例,创建对象的过程也叫类的实例化。对象是以类为模板来创建的。这样在安卓的底部就会用堆来存储对象的实例,栈来存储类的对象。引用是指某些对象的实例化需要其它的对象实例,比如ImageView的实例化就需要Context对象,就是表示ImageView对于Context持有引用(ImageView holds a reference to Context)。

垃圾回收机制(GC):
对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。更细致来讲就是对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常GC采用有向图的方式记录并管理堆中的所有对象,通过这种方式确定哪些对象时“可达”,哪些对象时“不可达”。当对象不可达的时候,即对象不再被引用的时候,就会被垃圾回收。该机制对虚拟机中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息(Nerver Stop)的保证虚拟机中的内存空间,防止出现内存泄露和溢出问题。

内存泄露:
当不再需要某个实例后,但是这个对象却仍然被引用,这个情况就叫做内存泄露(Memory Leak)。安卓虚拟机为每一个应用分配一定的内存空间,当内存泄露到达一定的程度就会造成内存溢出。

内存的引用:
内存的引用级别包括,强引用、软引用、弱引用、虚引用。强引用是默认的引用方式, 即使内存溢出,也不会回收。软引用(softReference), 内存不够时, 会考虑回收。 弱引用 (WeakReference)内存不够时, 更会考虑回收。虚引用(PhantomReference) 内存不够时, 最优先考虑回收! 一般我们常用到的引用就是强引用,比如引用创建一个成员变量里面的引用。对于GC来说, SoftReference的强度明显低于 SrongReference。SoftReference修饰的引用,其告诉GC:我是一个 软引用,当内存不足的时候,我指向的这个内存是可以给你释放掉的。一般对于这种占用内存资源比较大的,又不是必要的变量;或者一些占用大量内存资源的一些缓存的变量,就需要考虑 SoftReference。对于GC来说, WeakReference 的强度又明显低于 SoftReference 。 WeakReference 修饰的引用,其告诉GC:我是一个弱引用,对于你的要求我没有话说,我指向的这个内存是可以给你释放掉的。虚引用其实和上面讲到的各种引用不是一回事的,他主要是为跟踪一个对象何时被GC回收。在android里面也是有用到的:FileCleaner.java 。这些避免内存溢出的引用方式在Android 2.3+的版本上已经不再起太大作用, 因为垃圾回收器会频繁回收非强引用的对象, Android官方建议使用LRUCache。所以当我们用软引用进行内存缓存时会发现内存中的资源会被系统频繁回收。最终是从本地进行读数据。

这样我们就能很好的理解要三级缓存了。首先,在内存读数据。内存中读数据需要用到最近最少引用算法(lrucache)。Lrucache算法要求为new LruCache<String, Bitmap>()传入一个分配给软件的最大内存,同时重写sizeof()方法,计算每一张图片的大小。这样就可以直接调用LruCache的put()和get()方法。当发现内存中没用数据是时,找到SD卡中的存储文件。通过Bitmap的compress()方法向文件夹中写数据,通过位图工厂BitmapFactory的decodeStream()读取数据,同时可以为decodeStream()方法传入options参数,缩小图片。最后如果,本地仍然没有获取数据,在从网络获取。网络获取数据可以用异步任务来执行(耗时操作不能再主线程中执行)。异步任务需要重写onPostExecute()方法和doInBackground()方法。doInBackground()方法中访问网路,这里用到的是Httpurlconnection,通过连接得到输入流,利用位图工厂转换成位图,返回。onPostExecute()方法在doInBackground()方法执行后执行,传入的参数数doInBackground()方法的返回值。

接下来是代码实,这是我调用工具类的写法:

importandroid.os.Bundle;
importandroid.support.v7.app.AppCompatActivity;
importandroid.widget.ImageView;

importcom.example.huang.demo.utils.CacheUtils;

public class MainActivity extendsAppCompatActivity {
    private static final String TAG = "MainActivity";

    privateImageView iv;

    @Override
    protected voidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getBitmap();
    }

    private voidgetBitmap() {
        iv =(ImageView) findViewById(R.id.iv);
        CacheUtils utils = newCacheUtils();
        utils.diaplay(iv,"http://192.168.23.48:8080/test.jpg");    
    }
}

布局文件只要一个imageview就不单独写出来,直接来看缓存工具类:

importandroid.graphics.Bitmap;
importandroid.util.Log;
importandroid.widget.ImageView;

/*** Created by huang on 2016/12/3.
 */

public classCacheUtils {
    private static final String TAG = "CacheUtils";
    privateMemoryCacheUtils mMemoryCacheUtils;
    privateLocalCacheUtils mLocalCacheUtils;
    privateNetCacheUtils mNetCacheUtils;

    publicCacheUtils() {
        mMemoryCacheUtils = newMemoryCacheUtils();
        mLocalCacheUtils = newLocalCacheUtils();
        mNetCacheUtils = newNetCacheUtils(mMemoryCacheUtils, mLocalCacheUtils);
    }

    public voiddiaplay(ImageView imageView, String url) {

        //内存缓存   生命周期同调用者
        Bitmap bitmap =mMemoryCacheUtils.getBitmapToMemory(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            Log.i(TAG, "diaplay: 221111111111");
            return;
        }


        //本地缓存
        bitmap =LocalCacheUtils.getBitmapToLoacl(url);
        if (bitmap != null) {
            Log.i(TAG, "diaplay: 1111111");
            imageView.setImageBitmap(bitmap);
            mMemoryCacheUtils.putBitmapToMemory(bitmap, url);
            return;
        }

        //网络缓存
mNetCacheUtils.getBitmapFromNet(imageView, url);
    }
}

内存缓存的工具类

importandroid.graphics.Bitmap;
importandroid.util.Log;
importandroid.util.LruCache;

import staticandroid.content.ContentValues.TAG;

/*** Created by huang on 2016/12/3.
 */

public classMemoryCacheUtils {

    private LruCache<String, Bitmap>mMemoryCache;

    publicMemoryCacheUtils() {
        int maxmemory = (int) Runtime.getRuntime().maxMemory();
        Log.i(TAG, "MemoryCacheUtils: " +maxmemory);
        mMemoryCache = new LruCache<String, Bitmap>(maxmemory / 8) {
            @Override
            protected intsizeOf(String key, Bitmap value) {
                return value.getRowBytes() *value.getHeight();
            }
        };
    }

    public voidputBitmapToMemory(Bitmap bitmap, String url) {
        Log.i(TAG, "putBitmapToMemory: ");
        mMemoryCache.put(url, bitmap);
    }

    publicBitmap getBitmapToMemory(String url) {
        Log.i(TAG, "getBitmapToMemory: ");
        Bitmap bitmap =mMemoryCache.get(url);
        returnbitmap;
    }
}

本地缓存(SD卡)的工具类:

importandroid.graphics.Bitmap;
importandroid.graphics.BitmapFactory;
importandroid.os.Environment;
importandroid.util.Log;

importjava.io.File;
importjava.io.FileInputStream;
importjava.io.FileNotFoundException;
importjava.io.FileOutputStream;

import staticandroid.content.ContentValues.TAG;

/*** Created by huang on 2016/12/3.
 */

public classLocalCacheUtils {
    public static voidputBitmapToLoacl(Bitmap bitmap, String url) {
        String encode =Md5Utils.encode(url);
        File file = newFile(Environment.getExternalStorageDirectory(), encode);
        Log.i(TAG, "putBitmapToLoacl: " +file.toString());
        File parent =file.getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        try{
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, newFileOutputStream(file));
        } catch(FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    public staticBitmap getBitmapToLoacl(String url) {
        String encode =Md5Utils.encode(url);
        File file = newFile(Environment.getExternalStorageDirectory(), encode);
        if(file.exists()) {
            BitmapFactory.Options opts = newBitmapFactory.Options();
            opts.inSampleSize = 3;
            try{
                Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, opts);
                returnbitmap;
            } catch(FileNotFoundException e) {
                e.printStackTrace();
                return null;
            }
        }
        return null;
    }
}

网络缓存的工具类:

importandroid.graphics.Bitmap;
importandroid.graphics.BitmapFactory;
importandroid.os.AsyncTask;
importandroid.widget.ImageView;

importjava.io.IOException;
importjava.io.InputStream;
importjava.net.HttpURLConnection;
importjava.net.URL;

/*** Created by huang on 2016/12/3.
 */

public classNetCacheUtils {
    privateMemoryCacheUtils mMemoryCacheUtils;
    privateLocalCacheUtils mLocalCacheUtils;

    publicNetCacheUtils(MemoryCacheUtils mMemoryCacheUtils, LocalCacheUtils mLocalCacheUtils) {
        this.mLocalCacheUtils =mLocalCacheUtils;
        this.mMemoryCacheUtils =mMemoryCacheUtils;
    }

    public voidgetBitmapFromNet(ImageView imageView, String url) {
        imageView.setTag(url);
        Bitmaptask task = newBitmaptask();
        task.execute(imageView, url);
    }

    class Bitmaptask extends AsyncTask<Object, Void, Bitmap>{

        privateHttpURLConnection urlConnection;
        privateImageView imageView;
        privateString url;

        @Override
        protected voidonPostExecute(Bitmap bitmap) {
            if (bitmap != null) {
                if(url.equals(imageView.getTag())) {
                    imageView.setImageBitmap(bitmap);
                    System.out.print("onPostExecute");
                    mLocalCacheUtils.putBitmapToLoacl(bitmap, url);
                    mMemoryCacheUtils.putBitmapToMemory(bitmap, url);
                }
            }
        }

        @Override
        protectedBitmap doInBackground(Object[] params) {
            imageView = (ImageView) params[0];
            url = (String) params[1];
            Bitmap bitmap =downloadFromNet(url);
            returnbitmap;
        }

        privateBitmap downloadFromNet(String url) {
            try{
                urlConnection = (HttpURLConnection) newURL(url).openConnection();
                urlConnection.setConnectTimeout(5000);
                urlConnection.setRequestMethod("GET");
                int responseCode =urlConnection.getResponseCode();
                if (responseCode == 200) {
                    InputStream inputStream =urlConnection.getInputStream();
                    BitmapFactory.Options opts = newBitmapFactory.Options();
                    opts.inSampleSize = 2;
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, opts);
                    returnbitmap;
                }
            } catch(IOException e) {
                e.printStackTrace();
                return null;
            } finally{
                urlConnection.disconnect();
            }
            return null;
        }
    }
}

本地缓存中用到的MD5Utils工具类:

importjava.security.MessageDigest;

/*** Created by huang on 2016/12/3.
 */

public classMd5Utils {
    public staticString encode(String pwd) {
        try{
            MessageDigest digest = MessageDigest.getInstance("md5");
            byte[] bs =digest.digest(pwd.getBytes());
            StringBuilder sb = newStringBuilder();
            for (byteb : bs) {
                int number = b & 0xff;
                String str =Integer.toHexString(number);
                if (str.length() == 1) {
                    sb.append("0");
                }
                sb.append(number);
            }
            returnsb.toString();
        } catch(Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

最会别忘了添加权限:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

免责声明:文章转载自《关于Android中的三级缓存》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇自定义消息中如果需要定义WPARAM和LPARAM,该怎么使用和分配?linux 修改open files 参数下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

ssm+mybatis无法给带有下划线属性赋值问题,无法获取数据库带下划线的字段值

1、配置问题 <!-- 是否开启自动驼峰命名规则(camel case)映射, --> <setting name="mapUnderscoreToCamelCase" value="true"/> 或者 //开启驼峰映射 bean.getObject().getConfiguration().setMapUnderscore...

【C#写日志两个简单方法】

方法一:以日期为日志文件名. public void WriteLog(stringmsg) { string filePath = AppDomain.CurrentDomain.BaseDirectory + "Log"; if (!Directory.Exists(filePath)) {...

高仿微信新消息提示音功能

近期公司在做一个项目。有一个切换消息提示音的功能,能够切换本应用收到消息的提示音,而不影响系统提示音。我就依照微信的那个样式进行了编程,终于得到想要的效果。 转载请注明出处。谢谢:http://blog.csdn.net/harryweasley/article/details/46408037 怕有些人不知道怎么进入微信的新消息提示音功能,我这里说...

Spring学习(四)Spring IOC

Spring 学习目录 Spring学习(一)Spring初识 Spring学习(二)Spring框架结构 Spring学习(三)第一个Spring程序 Spring学习(四)Spring IOC Spring学习(五)Spring 基于注解装配Bean Spring学习(六)Spring AOP Spring学习(七)Spring JdbcTempl...

RabbitMQ(二):Java 操作队列

1. 简单模式 模型: P:消息的生产者 队列:rabbitmq C:消息的消费者 获取 MQ 连接 public static Connection getConnection() throws IOException, TimeoutException { // 定义一个连接工厂 ConnectionFactory...

c++11の关联容器

一、关联容器 C++的容器类型可以分为顺序容器和关联容器两大类。对于关联容器,主要有map和set,对于这两种,根据不同的维度,衍生出了8种容器 map                                      //值对 set                                         //仅有值 multimap ...