高效显示图片(三)

缓存图片

将一个图片加载到用户界面很简单,如果需要将一组图片加载到UI,事件就变得复杂了。在

很多情况下,随着图像在屏幕上快速滚动,需要加载的图像是无穷无尽的。

通过将不在屏幕上的子view进行回收使内存总量保持稳定。假如你不保持对任何图片的长时
间引用,垃圾回收机制将会释放加载的图片。但为了保持一个流畅,快速加载的UI,要避免
每次来回在屏幕上处理这些图像。内存和磁盘缓存通常可以帮助处理这些问题,使得组件能
够快速重新加载处理过的图像。

使用内存缓存

内存缓存以占据宝贵的内存资源为代价提供了对图像的快速访问。LruCache类对于处理缓存
图像的任务是非常好合适的,将最近被引用的对象保存在一个强引用的LinkedHashMap中,
并在内存超过分配给它的大小之前是否最近最少使用的成员对象。

要为LruCache选择一个合适的大小,应该考虑下面一些因素:
1.
2.一次在屏幕上将要显示多少张图片?多少张需要准备好显示到屏幕上?
3.屏幕的大小和密度是怎么样的?一个高分辨率的设备需要准备更多的内存来显示在低分辨率
设备上相同数量的图片。
4.图片的尺寸和配置是怎么样的,每张图片需要占据多大内存空间?
5.图片被访问的频率有多高?是否有一些比其他的访问频率要高?如果是这样,也行你可能需
要总是在内存中保持一定的项目,甚至为不同图片组创建不同的LruCache。
6.你能在质量和数量之间取得平衡吗?有时候可以存储较大数量低质量的图片,在一个后台任
务中加载高质量的图片。

并没有对所有应用程序都适合的方案,应该由你分析自己应用的情况,并拿出一个合理的解决
方案。缓存太小导致额外的开销,缓存太大会导致内存溢出,并给程序的其他部分留下很少的
内存。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private LruCache<String, Bitmap> mMemoryCache;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = ( int ) (Runtime.getRuntime().maxMemory() / 1024 );
 
    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8 ;
 
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
         @Override
         protected int sizeOf(String key, Bitmap bitmap) {
             // The cache size will be measured in kilobytes rather than
             // number of items.
             return bitmap.getByteCount() / 1024 ;
         }
    };
    ...
}
 
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null ) {
         mMemoryCache.put(key, bitmap);
    }
}
 
public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

当把一个图片加载到一个ImageView中时,LruCache将会先进行检查。如果发现一个入口,它
被立即用来更新ImageView,否则一个后台线程会进行图像处理:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
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);
    }
}

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
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的组件会很快占据整个缓存。你的应用程序可能会被另一个任务,例如来电而打
断,而在后台,它将会被杀死,高速缓存也会被摧毁。一旦用户恢复操作,你的应用程序需要
重新处理每个图像。

在这种情况下,可以使用磁盘高速缓存,当图片不再内存中时可以减少加载图像的时间。当然
从磁盘中读取图像比从内存中读取要慢,这个操作应该在一个后台线程中进行,因为磁盘的读
取时间是不可预知的。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true ;
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
    ...
    // Initialize disk cache on background thread
    File cacheDir = getDiskCacheDir( this , DISK_CACHE_SUBDIR);
    new InitDiskCacheTask().execute(cacheDir);
    ...
}
 
class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
    @Override
    protected Void doInBackground(File... params) {
         synchronized (mDiskCacheLock) {
             File cacheDir = params[ 0 ];
             mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
             mDiskCacheStarting = false ; // Finished initialization
             mDiskCacheLock.notifyAll(); // Wake any waiting threads
         }
         return null ;
    }
}
 
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(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
    synchronized (mDiskCacheLock) {
         if (mDiskLruCache != null && mDiskLruCache.get(key) == null ) {
             mDiskLruCache.put(key, bitmap);
         }
    }
}
 
public Bitmap getBitmapFromDiskCache(String key) {
    synchronized (mDiskCacheLock) {
         // Wait while disk cache is started from background thread
         while (mDiskCacheStarting) {
             try {
                mDiskCacheLock.wait();
             } catch (InterruptedException e) {}
         }
         if (mDiskLruCache != null ) {
             return mDiskLruCache.get(key);
         }
    }
    return null ;
}
 
// 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 getDiskCacheDir(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.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                     !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                            context.getCacheDir().getPath();
 
    return new File(cachePath + File.separator + uniqueName);
}

内存缓存在UI线程中进行检查,而磁盘缓存在后台线程中进行检查。磁盘操作不应该在UI线
程上发生。当图片的处理过程结束后,最终的图片被添加到内存和磁盘缓存,以便以后使用。

处理配置变化

运行时的配置变化,例如屏幕方向的变化,会使得Android销毁并重写启动正在运行的activity。
幸运的是,你有一个很好的内存缓存的位图,缓存能够通过使用由调用setRetainInstance保存
的Fragment传递给新的activity。当activity被创建后,这个保留的Fragment被重写连接,你也
获得了访问现有缓存的机会,可以使得图像被快速取出并重写填充到ImageView对象。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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);
         setRetainInstance( true );
    }
}
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值