图片框架优缺点整理(Fresco和Glide) - Tori Zhang
性能优化专题三--内存优化(图片三级缓存)_沙漠一只雕得儿得儿的博客-CSDN博客
对于一般App来说,Glide完全够用,而对于图片需求比较大的App,为了防止加载大量图片导致OOM,Fresco 会更合适一些。并不是说用Glide会导致OOM,Glide默认用的内存缓存是LruCache,内存不会一直往上涨。
Glide
图片加载流程存在一个LinkedHashMap存放数据,并且实现了LRU(最少使用算法)缓存策略。
Map<T,Y> cache = new LinkedHashMap<>(100,0.75f, true):
Glide缓存机制_橙子19911016的博客-CSDN博客_glide缓存机制
下面就是我们的三级缓存机制:内存缓存、复用池、磁盘缓存
首先,梳理一下必要的图片加载框架的需求:
线程池,多少个?
由于网络会阻塞,所以读内存和硬盘可以放在一个线程池,网络需要另外一个线程池,网络也可以采用Okhttp内置的线程池。
读硬盘和读网络需要放在不同的线程池中处理,所以用两个线程池比较合适。
Glide 必然也需要多个线程池,看下源码是不是这样
- public final class GlideBuilder {
- ...
- private GlideExecutor sourceExecutor; //加载源文件的线程池,包括网络加载
- private GlideExecutor diskCacheExecutor; //加载硬盘缓存的线程池
- ...
- private GlideExecutor animationExecutor; //动画线程池
Glide使用了三个线程池,不考虑动画的话就是两个。
图片异步加载成功,需要在主线程去更新ImageView,
无论是RxJava、EventBus,还是Glide,只要是想从子线程切换到Android主线程,都离不开Handler。
性能优化专题三--内存优化(图片三级缓存)_沙漠一只雕得儿得儿的博客-CSDN博客
我们常说的图片三级缓存:内存缓存、复用池、磁盘缓存
内存缓存:可以使用官方已经帮我们实现好的LurCache;
复用池:用来装载内存缓存LRU中被抛弃掉的,但是可能又会马上使用到的那些图片,我们在复用池中缓存下来,防止立马被GC回收掉;
硬盘缓存:有JakeWharton大神实现的DiskLruCache,下载地址:
为什么要设置复用池呢?
因为在官方8.0Android系统中,将图片放在native层中进行了处理,例如回收机制,在java层我们无法进行干预,为了能够不立即将图片交给native层处理,我们利用复用池中的弱引用暂时保存在java层中,以方便我们需要使用刚刚被内存缓存LRU放弃掉的图片,而不是重复decode图片再放入到LRU队列中,降低性能消耗。
下面就是我们的三级缓存机制:内存缓存、复用池、磁盘缓存
LruCache 采用最近最少使用算法,设定一个缓存大小,当缓存达到这个大小之后,会将最老的数据移除,避免图片占用内存过大导致OOM。
加载图片非常重要的一点是需要防止OOM,上面的LruCache缓存大小设置,可以有效防止OOM,但是当图片需求比较大,可能需要设置一个比较大的缓存,这样的话发生OOM的概率就提高了,那应该探索其它防止OOM的方法。
方法1:软引用
软引用的设计就是应用于会发生OOM的场景,大内存对象如Bitmap,可以通过 SoftReference 修饰,防止大对象造成OOM
LruCache里存的是软引用对象,那么当内存不足的时候,Bitmap会被回收,也就是说通过SoftReference修饰的Bitmap就不会导致OOM。
方法2:onLowMemory
当内存不足的时候,Activity、Fragment会调用onLowMemory
方法,可以在这个方法里去清除缓存,Glide使用的就是这一种方式来防止OOM。
- //Glide
- public void onLowMemory() {
- clearMemory();
- }
-
- public void clearMemory() {
- // Engine asserts this anyway when removing resources, fail faster and consistently
- Util.assertMainThread();
- // memory cache needs to be cleared before bitmap pool to clear re-pooled Bitmaps too. See #687.
- memoryCache.clearMemory();
- bitmapPool.clearMemory();
- arrayPool.clearMemory();
- }
方法3:从Bitmap 像素存储位置考虑
象的分配一般都是在堆中,堆是JVM中最大的一块内存,OOM一般都是发生在堆中。
Bitmap 之所以占内存大不是因为对象本身大,而是因为Bitmap的像素数据, Bitmap的像素数据大小 = 宽 * 高 * 1像素占用的内存。
如果Bitmap使用 RGB_565
格式,则1像素占用 2 byte,ARGB_8888
格式则占4 byte。
在选择图片加载框架的时候,可以将内存占用这一方面考虑进去,更少的内存占用意味着发生OOM的概率越低。 Glide内存开销是Picasso的一半,就是因为默认Bitmap格式不同。
那是否可以让像素数据不放在java堆中,而是放在native堆中呢?据说Android 3.0到8.0 之间Bitmap像素数据存在Java堆,而8.0之后像素数据存到native堆中
将ImageView用WeakReference修饰就完事了。事实上,这种方式虽然解决了内存泄露问题,但是并不完美,例如在界面退出的时候,我们除了希望ImageView被回收,同时希望加载图片的任务可以取消,队未执行的任务可以移除。
Glide的做法是监听生命周期回调,看 RequestManager
这个类
- public void onDestroy() {
- targetTracker.onDestroy();
- for (Target> target : targetTracker.getAll()) {
- //清理任务
- clear(target);
- }
- targetTracker.clear();
- requestTracker.clearRequests();
- lifecycle.removeListener(this);
- lifecycle.removeListener(connectivityMonitor);
- mainHandler.removeCallbacks(addSelfToLifecycle);
- glide.unregisterRequestManager(this);
- }
在Activity/fragment 销毁的时候,取消图片加载任务