使用软引用构建缓存

转载:http://www.devdiv.com/Android-%E4%BD%BF%E7%94%A8%E8%BD%AF%E5%BC%95%E7%94%A8%E6%9E%84%E5%BB%BA%E7%BC%93%E5%AD%98-thread-130476-1-1.html


一、为什么要使用软引用

       在上面关于软引用的介绍中,已经提到了软引用的特性。使用SoftReference引用的对象会有很长的生命周期,只有当系统的内存不足的时候,才会去释放这些软引用对象。所以可以使用软引用来缓存一些比较昂贵的资源,比如获取的网络图片数据。

        当应用从网络中获取网络图片数据时,用户完全有可能做一些重复性的操作去查看相同的图片信息。对于这样的问题,通常会有两种解决方法: 一种是把过去查看过的图片信息保存在内存中,每一个存储了图片信息的 Java 对象的生命周期都贯穿整个应用程序生命周期,另一种是当用户开始查看其他图片信息的时候,把存储了当前的图片信息的 Java 对象结束引用,使得垃圾收集线程可以回收其所占用的内存空间,当用户再次需要浏览该图片信息的时候,重新获取图片信息。

        很显然,第一种实现方法将造成大量的内存浪费,而第二种实现的缺陷在于即使垃圾收集线程还没有进行垃圾收集,包含图片信息的对象仍然完好地保存在内存中,应用程序也要重新构建一个对象。

        像访问磁盘文件、访问网络资源、查询数据库等操作都是影响应用程序执行性能的重要因素,如果能重新获取那些尚未被回收的 Java 对象的引用,必将减少不必要的访问,大大提高程序的运行速度。

        这样看来,使用软引用是非常有必要的一件事情


二、如何使用软引用

        SoftReference 的特点是它的一个实例保存着一个 Java 对象的软引用,该软引用的存在不妨碍垃圾收集器线程对该 Java 对象的回收。也就是说,一旦SoftReference 保存着一个 Java 对象的软引用之后,在垃圾收集器线程对这个 Java 对象回收之前, SoftReference 类所提供的 get() 方法都会返回 这个Java 对象的强引用。另外,一旦垃圾线程回收该 Java 对象之后, get() 方法将返回 null 。

        软引用的使用方法如下面的Java代码所示 :

1 MyObject aRef  =  new   MyObject();//创建一个对象
2 SoftReference aSoftRef = new SoftReference( aRef );//创建对象的软引用

   
        上面的代码执行后,对于MyObject 对象,有两个引用路径,一个是来自 aSoftRef对象的软引用,一个来自变量 aRef 的强引用,所以 MyObject对象是强可及对象。紧跟着,可以使用下面的java的代码结束 aReference 对 MyObject 实例的强引用 :

1 aRef = null ;//断开对象的强引用

        此后, MyObject 对象成为了软可及对象。如果垃圾收集线程进行内存垃圾收集,并不会因为有一个 SoftReference 对该对象的引用而始终保留该对象。 Java 虚拟机的垃圾收集线程对软可及对象和其他一般 Java 对象进行了区别对待 ,软可及对象的清理是由垃圾收集线程根据其特定算法按照内存需求决定的。也就是说,垃圾收集线程会在虚拟机抛出 OutOfMemoryError 之前回收软可及对象,而且虚拟机会尽可能优先回收长时间闲置不用的软可及对象,对那些刚刚构建的或刚刚使用过的“新”软可及对象会被虚拟机尽可能保留。如果想获取软引用中包含的对象,可以使用下面的Java代码:

1 MyObject anotherRef =(MyObject) aSoftRef .get();//通过软引用获取对象
    
        在回收这些对象之前,可以通过上面的代码重新获得对该实例的强引用。而回收之后,当调用软引用的get() 方法时,返回的是 null 


三、如何使用 ReferenceQueue

        作为一个 Java 对象, SoftReference 对象除了具有保存软引用的特殊性之外,也具有 Java 对象的一般性。所以,当软可及对象被回收之后,虽然这个 SoftReference 对象的 get() 方法返回 null, 但这个 SoftReference 对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量 SoftReference 对象带来的内存泄漏。在 java.lang.ref 包里还提供了 ReferenceQueue 。如果在创建 SoftReference 对象的时候,使用了带有一个 ReferenceQueue 对象作为参数的构造方法,如下面的Java代码 :

1 ReferenceQueue queue = new ReferenceQueue();//创建引用队列
2 SoftReference  ref = new SoftReference( aMyObject, queue );// 把引用加入到引用队列

        当这个 SoftReference 所软引用的 aMyOhject 被垃圾收集器回收的同时,ref 所强引用的 SoftReference 对象被列入 ReferenceQueue 。也就是说, ReferenceQueue 中保存的对象是 Reference 对象,而且是已经失去了它所软引用的对象的 Reference 对象。另外从 ReferenceQueue 这个名字也可以看出,它是一个队列,当调用它的 poll() 方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个 Reference 对象。

        在任何时候,都可以调用 ReferenceQueue 的 poll() 方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个 null, 否则该方法返回队列中最前面一个 Reference 对象。利用这个方法,可以检查哪个 SoftReference 所软引用的对象已经被回收。可以把这些失去所软引用的对象的 SoftReference 对象清除掉,如下面的Java代码所示。:

1 SoftReference ref = null ;
2 while ((ref = (EmployeeRef) q .poll()) != null ) {
3     // 清除 ref
4 }


四、实例分析

        理解了 Java中的引用机制之后就可以在Android中构造缓存器(cache)了,在Android中应用比较多的控件是ListView,通常会使用ListView显示网络数据列表,同时会包含图片缩略图,当数据量很大的时候,为了让用户能更流畅地流量信息,可以使用异步加载和缓存机制处理网络图片。
通过以上对于Java软引用类型的了解,可以知道使用软引用来构建缓存是比较合适的。虽然软引用能够延长数据对象的生命周期,但是对于移动设备来说,内存资源相对来说比较紧缺,仅使用软引用未必能达到最佳的缓存效果。通常会使用一些组合方式来进行数据缓存,最常用的是强引用、软引用加本地缓存的方式。

        Android提供了一个AsyncTask类,它封装了基本的异步操作模型,只需要实现几个最基本的方法就可以很容易的实现异步加载图片,主要的方法是doInBackground方法和onPostExecute方法。AsyncTask类会启动一个新的线程执行doInBackground方法,所以我们所有的网络操作都应该在这个方法中实现,当doInBackground方法执行完成后,AsyncTask类会使用内置的Handler发送消息在主线程中执行onPostExecute方法,所以关于对UI的操作都应该放在onPostExecute方法中实现。

        对于缓存的处理,主要思路是:在开始时,创建两个缓存区域:强引用缓存区域和软引用缓存区域。在强引用缓存区中保存有限的图片对象,根据LRU策略把一些最不常用的图片对象移到软引用缓存区,当缓存区域中都没有图片对象时从网络加载图片。完成后把图片数据保存到SDCard中,并根据LRU策略进行管理SDCard中保存的图片文件。

        下图为ListView异步加载远程图片的流程图:


jhkghjkghk.png


图14-2  ListView异步加载远程图片的流程图


        下面通过一个ListView的使用实例来说明如何在Android应用程序中使用异步加载图片,并且在内存和本地缓存它们。

        第一步,首先建立一个Android工程,名称为AsyncListImage,由于应用需要访问网络所以需要修改AndroidManifest.xml文件,添加网络连接的权限,代码如下:

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

        第二步,修改main.xml文件添加listview控件,并设置listview的一些基本属性信息,如下面的xml代码:

01 <?xml version="1.0" encoding="utf-8"?>
02 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
03         android:orientation="vertical"
04 android:layout_width="fill_parent"
05         android:layout_height="fill_parent">
06         <ListView android:id="@+id/list"
07      android:layout_width="fill_parent"
08                 android:layout_height="fill_parent"
09                 android:background="#ffffffff"
10                 android:cacheColorHint="#00000000"/>
11 </LinearLayout>

        第三步,修改AsyncListImage Activity类并覆盖oncreate方法,初始化listview,并创建listview控件使用的Adapter。在AsyncListImage中定义了两种缓存区域A和B,A代表强引用缓存区域,B代表软引用缓存区域,由于使用强引用缓存区域保存数据只能保存一定的数量,而不能一直往里面存放,需要设置数据的过期时间、LRU等算法。这里有一个方法是把常用的数据放到缓存A中,不常用的放到另外一个缓存B中。当要获取数据时先从A中去获取,如果A中不存在那么再去B中获取。B中的数据主要是A中经过LRU生成的数据,这里的内存回收主要针对B内存,从而保持A中的数据可以有效的被命中。

        下面是完整的Java代码:

001 package com.devdiv.android.asynimagelist;
002  
003 import java.io.File;
004 import java.lang.ref.SoftReference;
005 import java.util.HashMap;
006 import java.util.LinkedHashMap;
007 import java.util.concurrent.ConcurrentHashMap;
008  
009 import android.app.Activity;
010 import android.graphics.Bitmap;
011 import android.os.Bundle;
012 import android.util.Log;
013 import android.view.View;
014 import android.view.ViewGroup;
015 import android.widget.BaseAdapter;
016 import android.widget.ImageView;
017 import android.widget.ListView;
018 import android.widget.ImageView.ScaleType;
019  
020 @SuppressWarnings("serial")
021 public class AsyncListImage extends Activity implements RemoteImageCallback {
022         private ListView list;
023         private static final String TAG = AsyncListImage.class.getSimpleName();
024         private static final int HARD_CACHE_CAPACITY = 10;
025  
026         private final HashMap<String, Bitmap> mHardBitmapCache = newLinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 20.75f, true) {
027  
028                 @Override
029         protected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) {
030             if (size() > HARD_CACHE_CAPACITY) {
031                //当map的size大于10时,把最近不常用的key放到mSoftBitmapCache中,从而保证mHardBitmapCache的效率
032                mSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
033                 return true;
034             else
035                 return false;
036         }
037     };
038      
039         /**
040          *当mHardBitmapCache的key大于10的时候,会根据LRU算法把最近没有被使用的key放入到这个缓存中。
041          * Bitmap使用了SoftReference,当内存空间不足时,此cache中的bitmap会被垃圾回收掉
042          */
043         private final static ConcurrentHashMap<String, SoftReference<Bitmap>> mSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(
044                         HARD_CACHE_CAPACITY / 2);
045  
046         @Override
047         public void onCreate(Bundle savedInstanceState) {
048                 super.onCreate(savedInstanceState);
049                 setContentView(R.layout.main);
050                 list = (ListView) findViewById(R.id.list);
051                  
052                 initCacheDir();
053                  
054                 MyListAdapter adapter = new MyListAdapter();
055                 list.setAdapter(adapter);
056         }
057          
058         private void initCacheDir() {
059                 String cacheDir ="/data/data/com.devdiv.android.asynimagelist/files/caches";
060                 File f = new File(cacheDir);
061                 if (!f.exists()) {
062                         f.mkdirs();
063                 }
064         }
065          
066         private class MyListAdapter extends BaseAdapter {
067                 private String[] urls = new String[] {
080                 };
081                  
082                 @Override
083                 public int getCount() {
084                         return urls.length;
085                 }
086  
087                 @Override
088                 public String getItem(int position) {
089                         return urls[position];
090                 }
091  
092                 @Override
093                 public long getItemId(int position) {
094                         return position;
095                 }
096  
097                 @Override
098                 public View getView(int position, View convertView, ViewGroup parent) {
099                         if (convertView == null) {
100                                 convertView = new ImageView(AsyncListImage.this);
101                         }
102                         ImageView iv = (ImageView)convertView;
103                         iv.setScaleType(ScaleType.FIT_START);
104                         Bitmap bitmap = getBitmapFromCache(getItem(position));
105                         if (bitmap == null) {
106                                 iv.setImageResource(R.drawable.default_image);
107                                 iv.setTag(getItem(position));
108                                 new ImageDownloaderTask(AsyncListImage.this).execute(newString[]{getItem(position)});
109                         else {
110                                 iv.setImageBitmap(bitmap);
111                         }
112                         iv = null;
113                         return convertView;
114                 }
115                  
116         }
117          
118         /**
119      * 从缓存中获取图片
120      */
121     private Bitmap getBitmapFromCache(String url) {
122         // 先从mHardBitmapCache缓存中获取
123         synchronized (mHardBitmapCache) {
124             final Bitmap bitmap = mHardBitmapCache.get(url);
125             if (bitmap != null) {
126                 //如果找到的话,把元素移到linkedhashmap的最前面,从而保证在LRU算法中是最后被删除
127                 mHardBitmapCache.remove(url);
128                 Log.d(TAG, "move bitmap to the head of linkedhashmap:" + url);
129                 mHardBitmapCache.put(url,bitmap);
130                 return bitmap;
131             }
132         }
133         //如果mHardBitmapCache中找不到,到mSoftBitmapCache中找
134         SoftReference<Bitmap> bitmapReference = mSoftBitmapCache.get(url);
135         if (bitmapReference != null) {
136             final Bitmap bitmap = bitmapReference.get();
137             if (bitmap != null) {
138                     Log.d(TAG, "get bitmap from mSoftBitmapCache with key:" + url);
139                 return bitmap;
140             else {
141                 mSoftBitmapCache.remove(url);
142                 Log.d(TAG, "remove bitmap with key:" + url);
143             }
144         }
145         return null;
146     }
147  
148         @Override
149         public void onComplete(String url, Bitmap bitmap) {
150                 Log.d(TAG, "onComplete after got bitmap from remote with key:" + url);
151                 ImageView iv = (ImageView)list.findViewWithTag(url);
152                 if (iv != null) {
153                         iv.setImageBitmap(bitmap);
154                         mHardBitmapCache.put(url, bitmap);
155                 }
156         }
157          
158 }

        第四步,定义AsyncTask 类的子类ImageDownloaderTask类并覆盖doInBackground 方法和onPostExecute方法。在doInBackground方法中进行网络操作和文件操作,在onPostExecute方法中执行回调函数,把获取的bitmap数据发送到UI线程与ListView中的imageView进行关联,Java代码如下。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值