阿里面试官:简历上最好不要写Glide,不是问源码那么简单

// 1、linkHashMap的put方法
previous = map.put(key, value);
if (previous != null) {
//如果有旧的值,会覆盖,所以大小要减掉
size -= safeSizeOf(key, previous);
}
}

trimToSize(maxSize);
return previous;
}
复制代码

LinkedHashMap 结构可以用这种图表示

LinkHashMap 的 put方法和get方法最后会调用trimToSize方法,LruCache 重写trimToSize方法,判断内存如果超过一定大小,则移除最老的数据

LruCache#trimToSize,移除最老的数据

public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {

//大小没有超出,不处理
if (size <= maxSize) {
break;
}

//超出大小,移除最老的数据
Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}

key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
//这个大小的计算,safeSizeOf 默认返回1;
size -= safeSizeOf(key, value);
evictionCount++;
}

entryRemoved(true, key, value, null);
}
}
复制代码

对LinkHashMap 还不是很理解的话可以参考:
图解LinkedHashMap原理

LruCache小结:

  • LinkHashMap 继承HashMap,在 HashMap的基础上,新增了双向链表结构,每次访问数据的时候,会更新被访问的数据的链表指针,具体就是先在链表中删除该节点,然后添加到链表头header之前,这样就保证了链表头header节点之前的数据都是最近访问的(从链表中删除并不是真的删除数据,只是移动链表指针,数据本身在map中的位置是不变的)。
  • LruCache 内部用LinkHashMap存取数据,在双向链表保证数据新旧顺序的前提下,设置一个最大内存,往里面put数据的时候,当数据达到最大内存的时候,将最老的数据移除掉,保证内存不超过设定的最大值。
2.3.2 磁盘缓存 DiskLruCache

依赖:

implementation ‘com.jakewharton:disklrucache:2.0.2’

DiskLruCache 跟 LruCache 实现思路是差不多的,一样是设置一个总大小,每次往硬盘写文件,总大小超过阈值,就会将旧的文件删除。简单看下remove操作:

// DiskLruCache 内部也是用LinkedHashMap
private final LinkedHashMap<String, Entry> lruEntries =
new LinkedHashMap<String, Entry>(0, 0.75f, true);

public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null || entry.currentEditor != null) {
return false;
}

//一个key可能对应多个value,hash冲突的情况
for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
//通过 file.delete() 删除缓存文件,删除失败则抛异常
if (file.exists() && !file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
entry.lengths[i] = 0;
}

return true;
}
复制代码

可以看到 DiskLruCache 同样是利用LinkHashMap的特点,只不过数组里面存的 Entry 有点变化,Editor 用于操作文件。

private final class Entry {
private final String key;

private final long[] lengths;

private boolean readable;

private Editor currentEditor;

private long sequenceNumber;

}
复制代码

2.4 防止OOM

加载图片非常重要的一点是需要防止OOM,上面的LruCache缓存大小设置,可以有效防止OOM,但是当图片需求比较大,可能需要设置一个比较大的缓存,这样的话发生OOM的概率就提高了,那应该探索其它防止OOM的方法。

方法1:软引用

回顾一下Java的四大引用:

  • 强引用: 普通变量都属于强引用,比如 private Context context;
  • 软应用: SoftReference,在发生OOM之前,垃圾回收器会回收SoftReference引用的对象。
  • 弱引用: WeakReference,发生GC的时候,垃圾回收器会回收WeakReference中的对象。
  • 虚引用: 随时会被回收,没有使用场景。

怎么理解强引用:

强引用对象的回收时机依赖垃圾回收算法,我们常说的可达性分析算法,当Activity销毁的时候,Activity会跟GCRoot断开,至于GCRoot是谁?这里可以大胆猜想,Activity对象的创建是在ActivityThread中,ActivityThread要回调Activity的各个生命周期,肯定是持有Activity引用的,那么这个GCRoot可以认为就是ActivityThread,当Activity 执行onDestroy的时候,ActivityThread 就会断开跟这个Activity的联系,Activity到GCRoot不可达,所以会被垃圾回收器标记为可回收对象。

软引用的设计就是应用于会发生OOM的场景,大内存对象如Bitmap,可以通过 SoftReference 修饰,防止大对象造成OOM,看下这段代码

private static LruCache<String, SoftReference> mLruCache = new LruCache<String, SoftReference>(10 * 1024){
@Override
protected int sizeOf(String key, SoftReference value) {
//默认返回1,这里应该返回Bitmap占用的内存大小,单位:K

//Bitmap被回收了,大小是0
if (value.get() == null){
return 0;
}
return value.get().getByteCount() /1024;
}
};

复制代码

LruCache里存的是软引用对象,那么当内存不足的时候,Bitmap会被回收,也就是说通过SoftReference修饰的Bitmap就不会导致OOM。

当然,这段代码存在一些问题,Bitmap被回收的时候,LruCache剩余的大小应该重新计算,可以写个方法,当Bitmap取出来是空的时候,LruCache清理一下,重新计算剩余内存;

还有另一个问题,就是内存不足时软引用中的Bitmap被回收的时候,这个LruCache就形同虚设,相当于内存缓存失效了,必然出现效率问题。

方法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 像素存储位置考虑

我们知道,系统为每个进程,也就是每个虚拟机分配的内存是有限的,早期的16M、32M,现在100+M,
虚拟机的内存划分主要有5部分:

  • 虚拟机栈
  • 本地方法栈
  • 程序计数器
  • 方法区

而对象的分配一般都是在堆中,堆是JVM中最大的一块内存,OOM一般都是发生在堆中。

Bitmap 之所以占内存大不是因为对象本身大,而是因为Bitmap的像素数据, Bitmap的像素数据大小 = 宽 * 高 * 1像素占用的内存。

1像素占用的内存是多少?不同格式的Bitmap对应的像素占用内存是不同的,具体是多少呢?
在Fresco中看到如下定义代码

/**

  • Bytes per pixel definitions
    */
    public static final int ALPHA_8_BYTES_PER_PIXEL = 1;
    public static final int ARGB_4444_BYTES_PER_PIXEL = 2;
    public static final int ARGB_8888_BYTES_PER_PIXEL = 4;
    public static final int RGB_565_BYTES_PER_PIXEL = 2;
    public static final int RGBA_F16_BYTES_PER_PIXEL = 8;
    复制代码

如果Bitmap使用 RGB_565 格式,则1像素占用 2 byte,ARGB_8888 格式则占4 byte。
在选择图片加载框架的时候,可以将内存占用这一方面考虑进去,更少的内存占用意味着发生OOM的概率越低。 Glide内存开销是Picasso的一半,就是因为默认Bitmap格式不同。

至于宽高,是指Bitmap的宽高,怎么计算的呢?看BitmapFactory.Options 的 outWidth

/**

  • The resulting width of the bitmap. If {@link #inJustDecodeBounds} is
  • set to false, this will be width of the output bitmap after any
  • scaling is applied. If true, it will be the width of the input image
  • without any accounting for scaling.
  • outWidth will be set to -1 if there is an error trying to decode.

*/
public int outWidth;
复制代码

看注释的意思,如果 BitmapFactory.Options 中指定 inJustDecodeBounds 为true,则为原图宽高,如果是false,则是缩放后的宽高。所以我们一般可以通过压缩来减小Bitmap像素占用内存

扯远了,上面分析了Bitmap像素数据大小的计算,只是说明Bitmap像素数据为什么那么大。那是否可以让像素数据不放在java堆中,而是放在native堆中呢?据说Android 3.0到8.0 之间Bitmap像素数据存在Java堆,而8.0之后像素数据存到native堆中,是不是真的?看下源码就知道了~

8.0 Bitmap

java层创建Bitmap方法

public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
@NonNull Config config, boolean hasAlpha, @NonNull ColorSpace colorSpace) {

Bitmap bm;

if (config != Config.ARGB_8888 || colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)) {
//最终都是通过native方法创建
bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, null, null);
} else {
bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true,
d50.getTransform(), parameters);
}


return bm;
}

复制代码

Bitmap 的创建是通过native方法 nativeCreate

对应源码 8.0.0_r4/xref/frameworks/base/core/jni/android/graphics/Bitmap.cpp

//Bitmap.cpp
static const JNINativeMethod gBitmapMethods[] = {
{ “nativeCreate”, “([IIIIIIZ[FLandroid/graphics/ColorSpace R g b Rgb RgbTransferParameters;)Landroid/graphics/Bitmap;”,
(void*)Bitmap_creator },

复制代码

JNI动态注册,nativeCreate 方法 对应 Bitmap_creator

//Bitmap.cpp
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
jint offset, jint stride, jint width, jint height,
jint configHandle, jboolean isMutable,
jfloatArray xyzD50, jobject transferParameters) {

//1. 申请堆内存,创建native层Bitmap
sk_sp nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap, NULL);
if (!nativeBitmap) {
return NULL;
}


//2.创建java层Bitmap
return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable));
}
复制代码

主要两个步骤:

  1. 申请内存,创建native层Bitmap,看下allocateHeapBitmap方法
    8.0.0_r4/xref/frameworks/base/libs/hwui/hwui/Bitmap.cpp

//
static sk_sp allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes,
SkColorTable* ctable) {
// calloc 是c++ 的申请内存函数
void* addr = calloc(size, 1);
if (!addr) {
return nullptr;
}
return sk_sp(new Bitmap(addr, size, info, rowBytes, ctable));
}
复制代码

可以看到通过c++的 calloc 函数申请了一块内存空间,然后创建native层Bitmap对象,把内存地址传过去,也就是native层的Bitmap数据(像素数据)是存在native堆中。

  1. 创建java 层Bitmap

//Bitmap.cpp
jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
int density) {

BitmapWrapper* bitmapWrapper = new BitmapWrapper(bitmap);
//通过JNI回调Java层,调用java层的Bitmap构造方法
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
reinterpret_cast(bitmapWrapper), bitmap->width(), bitmap->height(), density,
isMutable, isPremultiplied, ninePatchChunk, ninePatchInsets);


return obj;
}

复制代码

env->NewObject,通过JNI创建Java层Bitmap对象,gBitmap_class,gBitmap_constructorMethodID这些变量是什么意思,看下面这个方法,对应java层的Bitmap的类名和构造方法。

//Bitmap.cpp
int register_android_graphics_Bitmap(JNIEnv* env)
{
gBitmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, “android/graphics/Bitmap”));
gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, “mNativePtr”, “J”);
gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, “”, “(JIIIZZ[BLandroid/graphics/NinePatch$InsetStruct;)V”);
gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, “reinit”, “(IIZ)V”);
gBitmap_getAllocationByteCountMethodID = GetMethodIDOrDie(env, gBitmap_class, “getAllocationByteCount”, “()I”);
return android::RegisterMethodsOrDie(env, “android/graphics/Bitmap”, gBitmapMethods,
NELEM(gBitmapMethods));
}
复制代码

8.0 的Bitmap创建就两个点:

  1. 创建native层Bitmap,在native堆申请内存。
  2. 通过JNI创建java层Bitmap对象,这个对象在java堆中分配内存。

像素数据是存在native层Bitmap,也就是证明8.0的Bitmap像素数据存在native堆中。

7.0 Bitmap

直接看native层的方法,

/7.0.0_r31/xref/frameworks/base/core/jni/android/graphics/Bitmap.cpp

//JNI动态注册
static const JNINativeMethod gBitmapMethods[] = {
{ “nativeCreate”, “([IIIIIIZ)Landroid/graphics/Bitmap;”,
(void*)Bitmap_creator },

static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
jint offset, jint stride, jint width, jint height,
jint configHandle, jboolean isMutable) {

//1.通过这个方法来创建native层Bitmap
Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);

return GraphicsJNI::createBitmap(env, nativeBitmap,
getPremulBitmapCreateFlags(isMutable));
}

复制代码

native层Bitmap 创建是通过GraphicsJNI::allocateJavaPixelRef,看看里面是怎么分配的, GraphicsJNI 的实现类是Graphics.cpp

android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
SkColorTable* ctable) {
const SkImageInfo& info = bitmap->info();

size_t size;
//计算需要的空间大小
if (!computeAllocationSize(*bitmap, &size)) {
return NULL;
}

// we must respect the rowBytes value already set on the bitmap instead of
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();
// 1. 创建一个数组,通过JNI在java层创建的
jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime,
gVMRuntime_newNonMovableArray,
gByte_class, size);

// 2. 获取创建的数组的地址
jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj);

//3. 创建Bitmap,传这个地址
android::Bitmap* wrapper = new android::Bitmap(env, arrayObj, (void*) addr,
info, rowBytes, ctable);
wrapper->getSkBitmap(bitmap);
// since we’re already allocated, we lockPixels right away
// HeapAllocator behaves this way too
bitmap->lockPixels();

return wrapper;
}
复制代码

可以看到,7.0 像素内存的分配是这样的:

  1. 通过JNI调用java层创建一个数组
  2. 然后创建native层Bitmap,把数组的地址传进去。

由此说明,7.0 的Bitmap像素数据是放在java堆的。

当然,3.0 以下Bitmap像素内存据说也是放在native堆的,但是需要手动释放native层的Bitmap,也就是需要手动调用recycle方法,native层内存才会被回收。这个大家可以自己去看源码验证。

native层Bitmap 回收问题

Java层的Bitmap对象由垃圾回收器自动回收,而native层Bitmap印象中我们是不需要手动回收的,源码中如何处理的呢?

记得有个面试题是这样的:

说说final、finally、finalize 的关系

三者除了长得像,其实没有半毛钱关系,final、finally大家都用的比较多,而 finalize 用的少,或者没用过,finalize 是 Object 类的一个方法,注释是这样的:

/**

  • Called by the garbage collector on an object when garbage collection
  • determines that there are no more references to the object.
  • A subclass overrides the {@code finalize} method to dispose of
  • system resources or to perform other cleanup.

…**/
protected void finalize() throws Throwable { }
复制代码

意思是说,垃圾回收器确认这个对象没有其它地方引用到它的时候,会调用这个对象的finalize方法,子类可以重写这个方法,做一些释放资源的操作。

在6.0以前,Bitmap 就是通过这个finalize 方法来释放native层对象的。 6.0 Bitmap.java

Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {

mNativePtr = nativeBitmap;
//1.创建 BitmapFinalizer
mFinalizer = new BitmapFinalizer(nativeBitmap);
int nativeAllocationByteCount = (buffer == null ? getByteCount() : 0);
mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount);
}

private static class BitmapFinalizer {
private long mNativeBitmap;

// Native memory allocated for the duration of the Bitmap,
// if pixel data allocated into native memory, instead of java byte[]
private int mNativeAllocationByteCount;

BitmapFinalizer(long nativeBitmap) {
mNativeBitmap = nativeBitmap;
}

public void setNativeAllocationByteCount(int nativeByteCount) {
if (mNativeAllocationByteCount != 0) {
VMRuntime.getRuntime().registerNativeFree(mNativeAllocationByteCount);
}
mNativeAllocationByteCount = nativeByteCount;
if (mNativeAllocationByteCount != 0) {
VMRuntime.getRuntime().registerNativeAllocation(mNativeAllocationByteCount);
}
}

@Override
public void finalize() {
try {
super.finalize();
} catch (Throwable t) {
// Ignore
} finally {
//2.就是这里了,
setNativeAllocationByteCount(0);
nativeDestructor(mNativeBitmap);
mNativeBitmap = 0;
}
}
}

复制代码

在Bitmap构造方法创建了一个 BitmapFinalizer类,重写finalize 方法,在java层Bitmap被回收的时候,BitmapFinalizer 对象也会被回收,finalize 方法肯定会被调用,在里面释放native层Bitmap对象。

6.0 之后做了一些变化,BitmapFinalizer 没有了,被NativeAllocationRegistry取代。

例如 8.0 Bitmap构造方法

Bitmap(long nativeBitmap, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {


mNativePtr = nativeBitmap;
long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
// 创建NativeAllocationRegistry这个类,调用registerNativeAllocation 方法
NativeAllocationRegistry registry = new NativeAllocationRegistry(
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
registry.registerNativeAllocation(this, nativeBitmap);
}
复制代码

NativeAllocationRegistry 就不分析了, 不管是BitmapFinalizer 还是NativeAllocationRegistry,目的都是在java层Bitmap被回收的时候,将native层Bitmap对象也回收掉。 一般情况下我们无需手动调用recycle方法,由GC去盘它即可。

上面分析了Bitmap像素存储位置,我们知道,Android 8.0 之后Bitmap像素内存放在native堆,Bitmap导致OOM的问题基本不会在8.0以上设备出现了(没有内存泄漏的情况下),那8.0 以下设备怎么办?赶紧升级或换手机吧~

[图片上传中…(image-2f4224-1591452178023-0)]

我们换手机当然没问题,但是并不是所有人都能跟上Android系统更新的步伐,所以,问题还是要解决~

Fresco 之所以能跟Glide 正面交锋,必然有其独特之处,文中开头列出 Fresco 的优点是:“在5.0以下(最低2.3)系统,Fresco将图片放到一个特别的内存区域(Ashmem区)” 这个Ashmem区是一块匿名共享内存,Fresco 将Bitmap像素放到共享内存去了,共享内存是属于native堆内存。

Fresco 关键源码在 PlatformDecoderFactory 这个类

public class PlatformDecoderFactory {

/**

  • Provide the implementation of the PlatformDecoder for the current platform using the provided
  • PoolFactory
  • @param poolFactory The PoolFactory
  • @return The PlatformDecoder implementation
    */
    public static PlatformDecoder buildPlatformDecoder(
    PoolFactory poolFactory, boolean gingerbreadDecoderEnabled) {
    //8.0 以上用 OreoDecoder 这个解码器
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    int maxNumThreads = poolFactory.getFlexByteArrayPoolMaxNumThreads();
    return new OreoDecoder(
    poolFactory.getBitmapPool(), maxNumThreads, new Pools.SynchronizedPool<>(maxNumThreads));
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    //大于5.0小于8.0用 ArtDecoder 解码器
    int maxNumThreads = poolFactory.getFlexByteArrayPoolMaxNumThreads();
    return new ArtDecoder(
    poolFactory.getBitmapPool(), maxNumThreads, new Pools.SynchronizedPool<>(maxNumThreads));
    } else {
    if (gingerbreadDecoderEnabled && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
    //小于4.4 用 GingerbreadPurgeableDecoder 解码器
    return new GingerbreadPurgeableDecoder();
    } else {
    //这个就是4.4到5.0 用的解码器了
    return new KitKatPurgeableDecoder(poolFactory.getFlexByteArrayPool());
    }
    }
    }
    }

复制代码

8.0 先不看了,看一下 4.4 以下是怎么得到Bitmap的,看下GingerbreadPurgeableDecoder这个类有个获取Bitmap的方法

//GingerbreadPurgeableDecoder
private Bitmap decodeFileDescriptorAsPurgeable(
CloseableReference bytesRef,
int inputLength,
byte[] suffix,
BitmapFactory.Options options) {
// MemoryFile :匿名共享内存
MemoryFile memoryFile = null;
try {
//将图片数据拷贝到匿名共享内存
memoryFile = copyToMemoryFile(bytesRef, inputLength, suffix);
FileDescriptor fd = getMemoryFileDescriptor(memoryFile);
if (mWebpBitmapFactory != null) {
// 创建Bitmap,Fresco自己写了一套创建Bitmap方法
Bitmap bitmap = mWebpBitmapFactory.decodeFileDescriptor(fd, null, options);
return Preconditions.checkNotNull(bitmap, “BitmapFactory returned null”);
} else {
throw new IllegalStateException(“WebpBitmapFactory is null”);
}
}
}

复制代码

捋一捋,4.4以下,Fresco 使用匿名共享内存来保存Bitmap数据,首先将图片数据拷贝到匿名共享内存中,然后使用Fresco自己写的加载Bitmap的方法。

Fresco对不同Android版本使用不同的方式去加载Bitmap,至于4.4-5.0,5.0-8.0,8.0 以上,对应另外三个解码器,大家可以从PlatformDecoderFactory 这个类入手,自己去分析,思考为什么不同平台要分这么多个解码器,8.0 以下都用匿名共享内存不好吗?期待你在评论区跟大家分享~

2.5 ImageView 内存泄露

曾经在Vivo驻场开发,带有头像功能的页面被测出内存泄漏,原因是SDK中有个加载网络头像的方法,持有ImageView引用导致的。

当然,修改也比较简单粗暴,将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 销毁的时候,取消图片加载任务,细节大家可以自己去看源码。

2.6 列表加载问题

图片错乱

由于RecyclerView或者LIstView的复用机制,网络加载图片开始的时候ImageView是第一个item的,加载成功之后ImageView由于复用可能跑到第10个item去了,在第10个item显示第一个item的图片肯定是错的。

常规的做法是给ImageView设置tag,tag一般是图片地址,更新ImageView之前判断tag是否跟url一致。

当然,可以在item从列表消失的时候,取消对应的图片加载任务。要考虑放在图片加载框架做还是放在UI做比较合适。

线程池任务过多

列表滑动,会有很多图片请求,如果是第一次进入,没有缓存,那么队列会有很多任务在等待。所以在请求网络图片之前,需要判断队列中是否已经存在该任务,存在则不加到队列去。

总结

本文通过Glide开题,分析一个图片加载框架必要的需求,以及各个需求涉及到哪些技术和原理。

最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)

除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
存,那么队列会有很多任务在等待。所以在请求网络图片之前,需要判断队列中是否已经存在该任务,存在则不加到队列去。

总结

本文通过Glide开题,分析一个图片加载框架必要的需求,以及各个需求涉及到哪些技术和原理。

最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)
[外链图片转存中…(img-wsWOs7dS-1715424669517)]

除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值