关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android Bitmap缓存池使用详解

    Android Bitmap缓存池使用详解

    2015-01-02 00:00:00 出处:博客园
    分享

    该篇讲述了如何使用缓存来提高UI的载入输入和滑动的流畅性。使用内存缓存、使用磁盘缓存、处理配置改变事件等方法将会有效的解决这个问题。

    在您的UI中显示单个图片是非常简单的,假如您需要一次显示很多图片就有点复杂了。在很多情况下(例如使用 Listview, GridView 或者 ViewPager控件),显示在屏幕上的图片以及即将显示在屏幕上的图片数量是非常大的(例如在图库中浏览大量图片)。

    在这些控件中,当一个子控件不显示的时候,系统会重用该控件来循环显示 以便减少对内存的消耗。同时垃圾回收机制还会释放那些已经载入内存中的Bitmap资源(假设您没有强引用这些Bitmap)。一般来说这样都是不错的,但是在用户来回滑动屏幕的时候,为了保证UI的流畅性和载入图片的效率,您需要避免重复的处理这些需要显示的图片。 使用内存缓存和磁盘缓存可以解决这个问题,使用缓存可以让控件快速的加载已经处理过的图片。

    本文介绍如何使用缓存来提高UI的载入输入和滑动的流畅性。

    使用内存缓存

    内存缓存提高了访问图片的速度,但是要占用不少内存。 LruCache
    类(在API 4之前可以使用Support Library 中的类 )特别适合缓存Bitmap, 把最近使用到的
    Bitmap对象用强引用保存起来(保存到LinkedHashMap中),当缓存数量达到预定的值的时候,把
    不经常使用的对象删除。

    注意: 过去,实现内存缓存的常用做法是使用
    SoftReference 或者
    WeakReference bitmap 缓存,
    但是不推荐使用这种方式。从Android 2.3 (API Level 9) 开始,垃圾回收开始强制的回收掉 soft/weak 引用 从而导致这些缓存没有任何效率的提升。
    另外,在 Android 3.0 (API Level 11)之前,这些缓存的Bitmap数据保存在底层内存(native memory)中,并且达到预定条件后也不会释放这些对象,从而可能导致
    程序超过内存限制并崩溃。

    在使用 LruCache 的时候,需要考虑如下一些因素来选择一个合适的缓存数量参数:

    程序中还有多少内存可用 同时在屏幕上显示多少图片?要先缓存多少图片用来显示到即将看到的屏幕上? 设备的屏幕尺寸和屏幕密度是多少?超高的屏幕密度(xhdpi 例如 Galaxy Nexus)
    设备显示同样的图片要比低屏幕密度(hdpi 例如 Nexus S)设备需要更多的内存。 图片的尺寸和格式决定了每个图片需要占用多少内存 图片访问的频率如何?一些图片的访问频率要比其他图片高很多?假如是这样的话,您可能需要把这些经常访问的图片放到内存中。 在质量和数量上如何平衡?有些情况下保存大量的低质量的图片是非常有用的,当需要的情况下使用后台线程来加入一个高质量版本的图片。

    这里没有万能配方可以适合所有的程序,您需要分析您的使用情况并在指定自己的缓存策略。使用太小的缓存并不能起到应有的效果,而使用太大的缓存会消耗更多
    的内存从而有可能导致 java.lang.OutOfMemory 异常或者留下很少的内存供您的程序其他功能使用。

    下面是一个使用 LruCache 缓存的示例:

    private LruCache<string, bitmap=""> mMemoryCache; 
    
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        ... 
        // Get memory class of this device, exceeding this amount will throw an 
        // OutOfMemory exception. 
        final int memClass = ((ActivityManager) context.getSystemService( 
                Context.ACTIVITY_SERVICE)).getMemoryClass(); 
    
        // Use 1/8th of the available memory for this memory cache. 
        final int cacheSize = 1024 * 1024 * memClass / 8; 
    
        mMemoryCache = new LruCache<string, bitmap="">(cacheSize) { 
            @Override 
            protected int sizeOf(String key, Bitmap bitmap) { 
                // The cache size will be measured in bytes rather than number of items. 
                return bitmap.getByteCount(); 
            } 
        }; 
        ... 
    }                                                               
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
        if (getBitmapFromMemCache(key) == null) { 
            mMemoryCache.put(key, bitmap); 
        } 
    }                                                               
    public Bitmap getBitmapFromMemCache(String key) { 
        return mMemoryCache.get(key); 
    }

    注意: 在这个示例中,该程序的1/8内存都用来做缓存用了。在一个normal/hdpi设备中,这至少有4MB(32/8)内存。
    在一个分辨率为 800×480的设备中,满屏的GridView全部填充上图片将会使用差不多1.5MB(800*480*4 bytes)
    的内存,所以这样差不多在内存中缓存了2.5页的图片。

    当在 ImageView 中显示图片的时候,
    先检查LruCache 中是否存在。假如存在就使用缓存后的图片,假如不存在就启动后台线程去载入图片并缓存:

    public void loadBitmap(int resId, ImageView imageView) { 
        final String imageKey = String.valueOf(resId); 
        final Bitmap bitmap = getBitmapFromMemCache(imageKey); 
        if (bitmap != null) { 
            mImageView.setImageBitmap(bitmap); 
        } else { 
            mImageView.setImageResource(R.drawable.image_placeholder); 
            BitmapWorkerTask task = new BitmapWorkerTask(mImageView); 
            task.execute(resId); 
        } 
    }

    BitmapWorkerTask 需要把新的图片添加到缓存中:

    class BitmapWorkerTask extends AsyncTask<integer, void,="" bitmap=""> { 
        ... 
        // Decode image in background. 
        @Override 
        protected Bitmap doInBackground(Integer... params) { 
            final Bitmap bitmap = decodeSampledBitmapFromResource( 
                    getResources(), params[0], 100, 100)); 
            addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); 
            return bitmap; 
        } 
        ... 
    }

    下页将为您介绍其它两种方法使用磁盘缓存和处理配置改变事件

     

    使用磁盘缓存

    在访问最近使用过的图片中,内存缓存速度很快,但是您无法确定图片是否在缓存中存在。像
    GridView 这种控件可能具有很多图片需要显示,很快图片数据就填满了缓存容量。
    同时您的程序还可能被其他任务打断,比如打进的电话 — 当您的程序位于后台的时候,系统可能会清楚到这些图片缓存。一旦用户恢复使用您的程序,您还需要重新处理这些图片。

    在这种情况下,可以使用磁盘缓存来保存这些已经处理过的图片,当这些图片在内存缓存中不可用的时候,可以从磁盘缓存中加载从而省略了图片处理过程。
    当然, 从磁盘载入图片要比从内存读取慢很多,并且应该在非UI线程中载入磁盘图片。

    注意: 假如缓存的图片经常被使用的话,可以考虑使用
    ContentProvider ,例如在图库程序中就是这样干滴。

    在示例代码中有个简单的 DiskLruCache 实现。然后,在Android 4.0中包含了一个更加可靠和推荐使用的DiskLruCache(libcore/luni/src/main/java/libcore/io/DiskLruCache.java)
    。您可以很容易的把这个实现移植到4.0之前的版本中使用(来 href=”http://www.google.com/search q=disklrucache”>Google一下 看看其他人是否已经这样干了!)。

    这里是一个更新版本的 DiskLruCache :

    private DiskLruCache mDiskCache; 
    private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB 
    private static final String DISK_CACHE_SUBDIR = "thumbnails"; 
    
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        ... 
        // Initialize memory cache 
        ... 
        File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR); 
        mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE); 
        ... 
    }                                
    class BitmapWorkerTask extends AsyncTask<integer, void,="" bitmap=""> { 
        ... 
        // Decode image in background. 
        @Override 
        protected Bitmap doInBackground(Integer... params) { 
            final String imageKey = String.valueOf(params[0]); 
    
            // Check disk cache in background thread 
            Bitmap bitmap = getBitmapFromDiskCache(imageKey); 
    
            if (bitmap == null) { // Not found in disk cache 
                // Process as normal 
                final Bitmap bitmap = decodeSampledBitmapFromResource( 
                        getResources(), params[0], 100, 100)); 
            }                               
            // Add final bitmap to caches 
            addBitmapToCache(String.valueOf(imageKey, bitmap); 
    
            return bitmap; 
        } 
        ... 
    }                                
    public void addBitmapToCache(String key, Bitmap bitmap) { 
        // Add to memory cache as before 
        if (getBitmapFromMemCache(key) == null) { 
            mMemoryCache.put(key, bitmap); 
        }                                
        // Also add to disk cache 
        if (!mDiskCache.containsKey(key)) { 
            mDiskCache.put(key, bitmap); 
        } 
    }                                
    public Bitmap getBitmapFromDiskCache(String key) { 
        return mDiskCache.get(key); 
    }                                
    // Creates a unique subdirectory of the designated app cache directory. Tries to use external 
    // but if not mounted, falls back on internal storage. 
    public static File getCacheDir(Context context, String uniqueName) { 
        // Check if media is mounted or storage is built-in, if so, try and use external cache dir 
        // otherwise use internal cache dir 
        final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED 
                || !Environment.isExternalStorageRemovable()   
                        context.getExternalCacheDir().getPath() : context.getCacheDir().getPath(); 
        return new File(cachePath + File.separator + uniqueName); 
    }

    在UI线程中检测内存缓存,在后台线程中检测磁盘缓存。磁盘操作从来不应该在UI线程中实现。当图片处理完毕后,最终的结果会同时添加到
    内存缓存和磁盘缓存中以便将来使用。

    处理配置改变事件

    运行时的配置变更 — 例如 屏幕方向改变 — 导致Android摧毁正在运行的Activity,然后使用
    新的配置从新启动该Activity (详情,参考这里 Handling Runtime Changes)。
    您需要注意避免在配置改变的时候导致重新处理所有的图片,从而提高用户体验。

    幸运的是,您在 使用内存缓存 部分已经有一个很好的图片缓存了。该缓存可以通过
    Fragment (Fragment会通过setRetainInstance(true)函数保存起来)来传递给新的Activity
    当Activity重新启动 后,Fragment 被重新附加到Activity中,您可以通过该Fragment来获取缓存对象。

    下面是一个在 Fragment中保存缓存的示例:

    private LruCache<string, bitmap=""> mMemoryCache;                  
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        ... 
        RetainFragment mRetainFragment =            RetainFragment.findOrCreateRetainFragment(getFragmentManager()); 
        mMemoryCache = RetainFragment.mRetainedCache; 
        if (mMemoryCache == null) { 
            mMemoryCache = new LruCache<string, bitmap="">(cacheSize) { 
                ... // Initialize cache here as usual 
            } 
            mRetainFragment.mRetainedCache = mMemoryCache; 
        } 
        ... 
    }                  
    class RetainFragment extends Fragment { 
        private static final String TAG = "RetainFragment"; 
        public LruCache<string, bitmap=""> mRetainedCache; 
    
        public RetainFragment() {}                  
        public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { 
            RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); 
            if (fragment == null) { 
                fragment = new RetainFragment(); 
            } 
            return fragment; 
        }                  
        @Override 
        public void onCreate(Bundle savedInstanceState) { 
            super.onCreate(savedInstanceState); 
            <strong>setRetainInstance(true);</strong> 
        } 
    }

    此外您可以尝试分别使用和不使用Fragment来旋转设备的屏幕方向来查看具体的图片载入情况。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    SQL汉字转换为拼音的函数

    windows 7系统无法运行Photoshop CS3的解决方法

    巧用MySQL加密函数对Web网站敏感数据进行保护

    MySQL基础知识简介

    Windows7和WinXP下如何实现不输密码自动登录系统的设置方法介绍

    windows 7系统ip地址冲突怎么办?windows 7系统IP地址冲突问题的

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。