Fresco浅析

Fresco是facebook出的图片加载库,功能强大,学习成本比较高。支持基本的图片加载、Gif图片加载、JPEG图片的渐进加载(和电脑浏览器一样)、显示图片加载进度。相对于Glide的主要优点是支持图片渐进加载,显示图片加载进度,在Api19以下通过匿名共享内存缓存图片。本文章主要分析Fresco不同的地方。

1.不同点分析

1.1 图片的加载。
1.1.1 NetworkFetchProducer.java

1
2
3
4
5
6
7
8
9
10
11
@Override
public void produceResults(Consumer<EncodedImage> consumer, ProducerContext context) {
...
mNetworkFetcher.fetch(
fetchState, new NetworkFetcher.Callback() {
@Override
public void onResponse(InputStream response, int responseLength) throws IOException {
NetworkFetchProducer.this.onResponse(fetchState, response, responseLength);
}
});
}

这个是网络加载图片开始步骤,获取服务器图片流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private void onResponse(
FetchState fetchState,
InputStream responseData,
int responseContentLength)
throws IOException {
final PooledByteBufferOutputStream pooledOutputStream;
if (responseContentLength > 0) {
pooledOutputStream = mPooledByteBufferFactory.newOutputStream(responseContentLength);
} else {
pooledOutputStream = mPooledByteBufferFactory.newOutputStream();
}
final byte[] ioArray = mByteArrayPool.get(READ_SIZE);
try {
int length;
while ((length = responseData.read(ioArray)) >= 0) {
if (length > 0) {
pooledOutputStream.write(ioArray, 0, length);
maybeHandleIntermediateResult(pooledOutputStream, fetchState);
float progress = calculateProgress(pooledOutputStream.size(), responseContentLength);
fetchState.getConsumer().onProgressUpdate(progress);
}
}
mNetworkFetcher.onFetchCompletion(fetchState, pooledOutputStream.size());
handleFinalResult(pooledOutputStream, fetchState);
} finally {
mByteArrayPool.release(ioArray);
pooledOutputStream.close();
}
}

(1)创建PooledByteBufferOutputStream缓冲流缓存数据。
(2)向PooledByteBufferOutputStream写入数据,回调加载进度,判断是否需要渐进显示图片。
(3)完成以后显示图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private void maybeHandleIntermediateResult(
PooledByteBufferOutputStream pooledOutputStream,
FetchState fetchState) {
final long nowMs = SystemClock.uptimeMillis();
if (shouldPropagateIntermediateResults(fetchState) &&
nowMs - fetchState.getLastIntermediateResultTimeMs() >= TIME_BETWEEN_PARTIAL_RESULTS_MS) {
fetchState.setLastIntermediateResultTimeMs(nowMs);
fetchState.getListener()
.onProducerEvent(fetchState.getId(), PRODUCER_NAME, INTERMEDIATE_RESULT_PRODUCER_EVENT);
notifyConsumer(pooledOutputStream, false, fetchState.getConsumer());
}
}

private void notifyConsumer(
PooledByteBufferOutputStream pooledOutputStream,
boolean isFinal,
Consumer<EncodedImage> consumer) {
CloseableReference<PooledByteBuffer> result =
CloseableReference.of(pooledOutputStream.toByteBuffer());
EncodedImage encodedImage = null;
try {
encodedImage = new EncodedImage(result);
encodedImage.parseMetaData();
consumer.onNewResult(encodedImage, isFinal);
} finally {
EncodedImage.closeSafely(encodedImage);
CloseableReference.closeSafely(result);
}
}

判断是否需要渐进加载显示图片。

1.2 图片的解析
1.2.1 ImagePipelineFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static PlatformDecoder buildPlatformDecoder(
PoolFactory poolFactory,
boolean decodeMemoryFileEnabled,
boolean webpSupportEnabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return new ArtDecoder(
poolFactory.getBitmapPool(),
poolFactory.getFlexByteArrayPoolMaxNumThreads());
} else {
if (decodeMemoryFileEnabled && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return new GingerbreadPurgeableDecoder(webpSupportEnabled);
} else {
return new KitKatPurgeableDecoder(poolFactory.getFlexByteArrayPool());
}
}
}

Fresco针对系统的版本做了不同的图片解析方案。

1.2.1 PlatformDecoder.java

1
2
3
4
5
6
7
8
CloseableReference<Bitmap> decodeFromEncodedImage(
final EncodedImage encodedImage,
Bitmap.Config bitmapConfig);

CloseableReference<Bitmap> decodeJPEGFromEncodedImage(
EncodedImage encodedImage,
Bitmap.Config bitmapConfig,
int length);

decodeFromEncodedImage是对完整图片的解析,decodeJPEGFromEncodedImage是解析JPEG渐进中的图片。

1.2.3 GingerbreadPurgeableDecoder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected Bitmap decodeFileDescriptorAsPurgeable(
CloseableReference<PooledByteBuffer> bytesRef,
int inputLength,
byte[] suffix,
BitmapFactory.Options options) {
MemoryFile memoryFile = null;
try {
memoryFile = copyToMemoryFile(bytesRef, inputLength, suffix);
FileDescriptor fd = getMemoryFileDescriptor(memoryFile);
Bitmap bitmap;
if (mWebpSupportEnabled) {
bitmap = sWebpBitmapFactory.decodeFileDescriptor(fd, null, options);
} else {
bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
}
return Preconditions.checkNotNull(bitmap, "BitmapFactory returned null");
} catch (IOException e) {
throw Throwables.propagate(e);
} finally {
if (memoryFile != null) {
memoryFile.close();
}
}
}

针对API 19以下使用了匿名共享内存,减少Java Heap内存。匿名共享内存可查看Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划

1.2.4 KitKatPurgeableDecoder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected Bitmap decodeByteArrayAsPurgeable(
CloseableReference<PooledByteBuffer> bytesRef,
BitmapFactory.Options options) {
final PooledByteBuffer pooledByteBuffer = bytesRef.get();
final int length = pooledByteBuffer.size();
final CloseableReference<byte[]> encodedBytesArrayRef = mFlexByteArrayPool.get(length);
try {
final byte[] encodedBytesArray = encodedBytesArrayRef.get();
pooledByteBuffer.read(0, encodedBytesArray, 0, length);
Bitmap bitmap = BitmapFactory.decodeByteArray(
encodedBytesArray,
0,
length,
options);
return Preconditions.checkNotNull(bitmap, "BitmapFactory returned null");
} finally {
CloseableReference.closeSafely(encodedBytesArrayRef);
}
}

API19 这个版本不支持匿名共享内存,Bitmap将在java memory中。这个是注释:

The MemoryFile trick used in GingerbreadPurgeableDecoder does not work in KitKat. Here, weinstead use Java memory to store the encoded images, but make use of a pool to minimizeallocations. We cannot decode from a stream, as that does not support purgeable decodes.

1.2.5 ArtDecoder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public CloseableReference<Bitmap> decodeFromEncodedImage(
EncodedImage encodedImage,
Bitmap.Config bitmapConfig) {
final BitmapFactory.Options options = getDecodeOptionsForStream(encodedImage, bitmapConfig);
boolean retryOnFail=options.inPreferredConfig != Bitmap.Config.ARGB_8888;
try {
return decodeStaticImageFromStream(encodedImage.getInputStream(), options);
} catch (RuntimeException re) {
if (retryOnFail) {
return decodeFromEncodedImage(encodedImage, Bitmap.Config.ARGB_8888);
}
throw re;
}
}

API21及以上,新增了Options inBitmap属性,这个属性支持Bitmap创建时复用之前无用的Bitmap。

2.缺点分析

1.1 图片处理问题
(1)不支持根据ImageView宽高处理图片
(2)图片压缩不够细腻。对应大图和长图的压缩有问题;没有Transformation的过程,意味不会根据ScaleType处理图片,只有更具simple size 压缩图片。所有的ScaleType都是通过定义Drawable和通过矩阵来实现最终效果,这种方式感觉浪费系统资源。
(3)扩展性不够好,代码复杂,重构和上手成本高。