Glide 源码阅览笔记(六)
在第一篇文章中我简略介绍了Glide
实例的创立进程,要点介绍了Glide
的内存缓存完结和磁盘的缓存完结:Glide 源码阅览笔记(一)
在第二篇文章中介绍了Glide
对生命周期的管理:Glide 源码阅览笔记(二)
在第三篇文章中介绍了 Request
和 RequestCoordinator
,还简略介绍了 Registry
中的中心组件:Glide 源码阅览笔记(三)
第四篇文章中介绍了 Glide
怎样加载内存缓存和磁盘缓存的资源。其间内存缓存又分为存活的内存缓存资源和被缓存的内存缓存资源;磁盘缓存分为 RESOURCE_CACHE
类型(有尺度等参数作为 key
)缓存与 DATA_CACHE
类型(从网络中加载的原始缓存数据,无尺度等等信息):Glide 源码阅览笔记(四)
第五篇文章中介绍了Glide
怎样加载网络恳求的数据和怎样写入磁盘缓存(包含ResourceCache
和DataCache
);还介绍了RequestManager
详细怎样处理不同的Android
生命周期:Glide 源码阅览笔记(五)
前面几篇文章中基本跑通了整个 Glide
,可是还有一部分十分中心的逻辑没有讲,那便是 Registry
中注册的中心组件,其间包含 ModelLoader
, Decoder
和 Transcoder
。本篇文章中主要介绍一下其间有代表性的 ModelLoader
和 Decoder
。那么废话少说,开端今天的内容吧。
ModelLoader
在聊 ModelLoader
之前,咱们需求知道 Glide
中 model
的概念,在前面的文章中其实都现已讲过了,这儿再说一次,Glide.with(context).load(url)
其间 load()
办法传入的目标便是 model
,咱们不仅能够传入 String
类型作为 model
,也能够传入 File
作为 model
。ModelLoader
就需求讲对应类型的 model
转换成 Decoder
能够承受的输入类型,一般 Decoder
能够承受的输入类型便是 InputStream
。
假设是 http
的协议的 url
,就需求树立 http
的网络链接,然后获取到对应的 ResponseBody
的 InputStream
,然后传递给 Decoder
;假设是 File
作为 model
输入,那就愈加简略了,直接打开文件流的 FileInputStream
就好了。
在开端之前咱们先看看 ModelLoader
接口的界说:
public interface ModelLoader<Model, Data> {
class LoadData<Data> {
public final Key sourceKey;
public final List<Key> alternateKeys;
public final DataFetcher<Data> fetcher;
public LoadData(@NonNull Key sourceKey, @NonNull DataFetcher<Data> fetcher) {
this(sourceKey, Collections.<Key>emptyList(), fetcher);
}
public LoadData(
@NonNull Key sourceKey,
@NonNull List<Key> alternateKeys,
@NonNull DataFetcher<Data> fetcher) {
this.sourceKey = Preconditions.checkNotNull(sourceKey);
this.alternateKeys = Preconditions.checkNotNull(alternateKeys);
this.fetcher = Preconditions.checkNotNull(fetcher);
}
}
@Nullable
LoadData<Data> buildLoadData(
@NonNull Model model, int width, int height, @NonNull Options options);
boolean handles(@NonNull Model model);
}
其间 handles()
办法的回来值就表示当时的 ModelLoader
是否能够处理这个 model
;buildLoadData()
办法会把对 model
的处理办法封装在 LoadData
目标中;在 LoadData
中包含了 model
对应的 key
,这对缓存的 key
的核算十分重要,其间最重要的是 DataFetcher
,他是真实处理 model
转换的地方。
咱们再简略看看 DataFetcher
的接口:
public interface DataFetcher<T> {
interface DataCallback<T> {
void onDataReady(@Nullable T data);
void onLoadFailed(@NonNull Exception e);
}
void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback);
void cleanup();
void cancel();
@NonNull
Class<T> getDataClass();
@NonNull
DataSource getDataSource();
}
其间 loadData()
办法便是履行异步加载;getDataClass()
获取终究输出的目标的 Class
;getDataSource()
获取对应的数据来历,比如内存缓存,磁盘缓存和网络恳求等等。
在了解了 ModelLoader
的相关接口后,咱们来看看一些重要的 ModelLoader
是怎样完结的。
StringLoader
首先这儿要了解 StringLoader
是将 String
类型的 model
转换成 InputStream
。
咱们先看看 StreamFactory
的完结:
public static class StreamFactory implements ModelLoaderFactory<String, InputStream> {
@NonNull
@Override
public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class));
}
@Override
public void teardown() {
// Do nothing.
}
}
咱们看到 build
办法中创立了一个 StringLoader
实例,而还经过 MultiFactory
创立了一个其他的 ModelLoader
作为 StringLoader
的结构函数的参数。这个 ModelLoader
输入的 model
类型是 Uri
,输出的类型是 InputStream
。
咱们再看看 StringLoader
的源码:
public class StringLoader<Data> implements ModelLoader<String, Data> {
private final ModelLoader<Uri, Data> uriLoader;
// Public API.
@SuppressWarnings("WeakerAccess")
public StringLoader(ModelLoader<Uri, Data> uriLoader) {
this.uriLoader = uriLoader;
}
@Override
public LoadData<Data> buildLoadData(
@NonNull String model, int width, int height, @NonNull Options options) {
Uri uri = parseUri(model);
if (uri == null || !uriLoader.handles(uri)) {
return null;
}
return uriLoader.buildLoadData(uri, width, height, options);
}
@Override
public boolean handles(@NonNull String model) {
// Avoid parsing the Uri twice and simply return null from buildLoadData if we don't handle this
// particular Uri type.
return true;
}
@Nullable
private static Uri parseUri(String model) {
Uri uri;
if (TextUtils.isEmpty(model)) {
return null;
// See https://pmd.github.io/pmd-6.0.0/pmd_rules_java_performance.html#simplifystartswith
} else if (model.charAt(0) == '/') {
uri = toFileUri(model);
} else {
uri = Uri.parse(model);
String scheme = uri.getScheme();
if (scheme == null) {
uri = toFileUri(model);
}
}
return uri;
}
private static Uri toFileUri(String path) {
return Uri.fromFile(new File(path));
}
}
上面的代码也十分简略,将输入的 model
转换成 Uri
目标,假设 Uri
没有对应的 scheme
,那就当文件处理,然后将转换后的 Uri
目标交由结构函数传入的 ModelLoader
处理,前面咱们知道这个 ModelLoader
承受 Uri
的输入,最终回来 InputStream
数据。
UrlUriLoader
它是支撑 Uri
输入,InputStream
输出的 ModleLoader
。
咱们持续看看它的 StreamFactory
目标是怎样完结的:
public static class StreamFactory implements ModelLoaderFactory<Uri, InputStream> {
@NonNull
@Override
public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new UrlUriLoader<>(multiFactory.build(GlideUrl.class, InputStream.class));
}
@Override
public void teardown() {
// Do nothing.
}
}
咱们看到它的处理办法和 StringLoader
中类似,构建 UrlUriLoader
实例时也会先创立一个 ModelLoader
实例,作为它的结构函数的参数,这个 ModelLoader
承受的输入是 GlideUrl
,输出是 InputStream
。
咱们再来看看 UrlUriLoader
中的源码完结:
public class UrlUriLoader<Data> implements ModelLoader<Uri, Data> {
private static final Set<String> SCHEMES =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList("http", "https")));
private final ModelLoader<GlideUrl, Data> urlLoader;
// Public API.
@SuppressWarnings("WeakerAccess")
public UrlUriLoader(ModelLoader<GlideUrl, Data> urlLoader) {
this.urlLoader = urlLoader;
}
@Override
public LoadData<Data> buildLoadData(
@NonNull Uri uri, int width, int height, @NonNull Options options) {
GlideUrl glideUrl = new GlideUrl(uri.toString());
return urlLoader.buildLoadData(glideUrl, width, height, options);
}
@Override
public boolean handles(@NonNull Uri uri) {
return SCHEMES.contains(uri.getScheme());
}
}
咱们注意看它的 handles()
办法完结,它只支撑协议为 http
和 https
的 uri
。在 buildLoadData()
办法中会以 uri
作为参数,然后构建一个 GlideUrl
目标,然后传给结构函数传过来的 ModelLoader
。
HttpGlideUrlLoader
他便是真实的处理 http
恳求的 ModelLoader
,它以 GlideUrl
作为输入目标,输出一个 InputStream
。他是默许的网络恳求的 ModelLoader
,所以假设咱们要替换默许的网络恳求 ModelLoader
,代码就要像以下这样写:
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
super.registerComponents(context, glide, registry)
registry.replace(
GlideUrl::class.java,
InputStream::class.java,
OkHttpUrlLoader.Factory(appOkHttpClient)
)
}
咱们言归正传,看看 HttpGlideUrlLoader
的 Factory
是怎样完结的:
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private final ModelCache<GlideUrl, GlideUrl> modelCache = new ModelCache<>(500);
@NonNull
@Override
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new HttpGlideUrlLoader(modelCache);
}
@Override
public void teardown() {
// Do nothing.
}
}
构建 HttpGlideUrlLoader
实例时,还创立了一个 ModelCache
用来记载 model
的内存缓存,缓存的巨细是 500。
持续看看 HttpGlideUrlLoader
的代码完结:
public class HttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
public static final Option<Integer> TIMEOUT =
Option.memory("com.bumptech.glide.load.model.stream.HttpGlideUrlLoader.Timeout", 2500);
@Nullable private final ModelCache<GlideUrl, GlideUrl> modelCache;
public HttpGlideUrlLoader() {
this(null);
}
public HttpGlideUrlLoader(@Nullable ModelCache<GlideUrl, GlideUrl> modelCache) {
this.modelCache = modelCache;
}
@Override
public LoadData<InputStream> buildLoadData(
@NonNull GlideUrl model, int width, int height, @NonNull Options options) {
GlideUrl url = model;
if (modelCache != null) {
url = modelCache.get(model, 0, 0);
if (url == null) {
modelCache.put(model, 0, 0, model);
url = model;
}
}
int timeout = options.get(TIMEOUT);
return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
}
@Override
public boolean handles(@NonNull GlideUrl model) {
return true;
}
}
上面代码十分简略,仅仅假设 model
没有在 ModelCache
中,会增加到 ModelCache
中,然后构建一个 HttpUrlFetcher
目标,并存入 LoadData
中,HttpUrlFetcher
完结了真实的网络恳求。
咱们看看 HttpUrlFetcher#loadData()
办法是怎样构建网络恳求的:
@Override
public void loadData(
@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
callback.onDataReady(result);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
} finally {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
}
}
}
经过 loadDataWithRedirects()
办法履行 http
恳求,假设成功回调 Callback#onDataReady()
,假设失利回调 Callback#onLoadFailed()
。
咱们看看 loadDataWithRedirects()
办法的完结:
private InputStream loadDataWithRedirects(
URL url, int redirects, URL lastUrl, Map<String, String> headers) throws HttpException {
// 判别是否达到最大的重定向次数,最大次数 5 次。
if (redirects >= MAXIMUM_REDIRECTS) {
throw new HttpException(
"Too many (> " + MAXIMUM_REDIRECTS + ") redirects!", INVALID_STATUS_CODE);
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new HttpException("In re-direct loop", INVALID_STATUS_CODE);
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
// 构建 http 的 Connection
urlConnection = buildAndConfigureConnection(url, headers);
try {
// Connect explicitly to avoid errors in decoders if connection fails.
// 履行 http 恳求
urlConnection.connect();
// Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
// 获取对应的 stream,用于释放链接
stream = urlConnection.getInputStream();
} catch (IOException e) {
throw new HttpException(
"Failed to connect or obtain data", getHttpStatusCodeOrInvalid(urlConnection), e);
}
if (isCancelled) {
return null;
}
// 获取 http 状况码
final int statusCode = getHttpStatusCodeOrInvalid(urlConnection);
if (isHttpOk(statusCode)) {
// 恳求成功,直接获取对应的 ResponseBody 的 InputStream。
return getStreamForSuccessfulRequest(urlConnection);
} else if (isHttpRedirect(statusCode)) {
// 假设是重定向,解析新的 Url 然后递归调用 loadDataWithRedirects() 办法。
String redirectUrlString = urlConnection.getHeaderField(REDIRECT_HEADER_FIELD);
if (TextUtils.isEmpty(redirectUrlString)) {
throw new HttpException("Received empty or null redirect url", statusCode);
}
URL redirectUrl;
try {
redirectUrl = new URL(url, redirectUrlString);
} catch (MalformedURLException e) {
throw new HttpException("Bad redirect url: " + redirectUrlString, statusCode, e);
}
// Closing the stream specifically is required to avoid leaking ResponseBodys in addition
// to disconnecting the url connection below. See #2352.
cleanup();
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else if (statusCode == INVALID_STATUS_CODE) {
throw new HttpException(statusCode);
} else {
try {
throw new HttpException(urlConnection.getResponseMessage(), statusCode);
} catch (IOException e) {
throw new HttpException("Failed to get a response message", statusCode, e);
}
}
}
我相信许多年轻的朋友都没有用过 HttpUrlConnection
,我是在刚学 Android
的时候用过,然后作业后有用过 Volly
,再到然后就一直是 OkHttp
了。说了一点废话,咱们简略看看 HttpUrlConnection
是怎样用的吧(横竖现在作业中不行能用这个)。
- 判别是否达到最大的重定向次数 5 次。
- 经过
buildAndConfigureConnection()
办法构建一个HttpUrlConnection
。 - 调用
HttpUrlConnection#connect()
办法恳求http
。 - 经过
getHttpStatusCodeOrInvalid()
办法获取http
的状况码。
- 恳求成功:经过
getStreamForSuccessfulRequest()
办法获取ResponseBody
的InputStream
。 - 重定向:重新解析获取重定向的
url
,然后递归调用loadDataWithRedirects()
办法。 - 其他情况:抛出反常。
咱们看看 buildAndConfigureConnection()
办法怎样构建 HttpUrlConnection
的:
private HttpURLConnection buildAndConfigureConnection(URL url, Map<String, String> headers)
throws HttpException {
HttpURLConnection urlConnection;
try {
urlConnection = connectionFactory.build(url);
} catch (IOException e) {
throw new HttpException("URL.openConnection threw", /* statusCode= */ 0, e);
}
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
// Stop the urlConnection instance of HttpUrlConnection from following redirects so that
// redirects will be handled by recursive calls to this method, loadDataWithRedirects.
urlConnection.setInstanceFollowRedirects(false);
return urlConnection;
}
private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory {
@Synthetic
DefaultHttpUrlConnectionFactory() {}
@Override
public HttpURLConnection build(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
}
朴实无华的代码,增加了 RequestHeader
,设置了超时,关闭了默许的重定向处理(由代码自己处理)。
持续看看 getStreamForSuccessfulRequest()
办法是怎样获取 RequestBody
的 InputStream
的:
private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
throws HttpException {
try {
if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
int contentLength = urlConnection.getContentLength();
stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());
}
stream = urlConnection.getInputStream();
}
} catch (IOException e) {
throw new HttpException(
"Failed to obtain InputStream", getHttpStatusCodeOrInvalid(urlConnection), e);
}
return stream;
}
上面代码中会尝试去读 Content_Length
,假设读取成功,就会将原来的 InputStream
用 ContentLengthInputStream
封装一下,反之就直接用 HttpUrlConnection#getInputStream()
办法回来的结果。
有一说一 HttpUrlConnection
的代码可读性和运用的便利性与 OkHttp
比较差远了,所以咱们再来看看用 OkHttp
的 ModelLoader
是怎样完结的。
OkHttpUrlLoader
同样先看看它的 Factory
怎样写的:
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private static volatile Call.Factory internalClient;
private final Call.Factory client;
private static Call.Factory getInternalClient() {
if (internalClient == null) {
synchronized (Factory.class) {
if (internalClient == null) {
internalClient = new OkHttpClient();
}
}
}
return internalClient;
}
public Factory() {
this(getInternalClient());
}
public Factory(@NonNull Call.Factory client) {
this.client = client;
}
@NonNull
@Override
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new OkHttpUrlLoader(client);
}
@Override
public void teardown() {
// Do nothing, this instance doesn't own the client.
}
}
假设没有默许的 OkHttpClient
就会创立一个,然后以 OkHttpClient
为结构函数的参数创立一个 OkHttpUrlLoader
实例。
再看看 OkHttpUrlLoader#buildLoadData()
的完结:
@Override
public LoadData<InputStream> buildLoadData(
@NonNull GlideUrl model, int width, int height, @NonNull Options options) {
return new LoadData<>(model, new OkHttpStreamFetcher(client, model));
}
网络恳求封装在 OkHttpStreamFetcher
中,咱们看看 OkHttpStreamFetcher#loadData()
办法是怎样完结的:
@Override
public void loadData(
@NonNull Priority priority, @NonNull final DataCallback<? super InputStream> callback) {
Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
Request request = requestBuilder.build();
this.callback = callback;
call = client.newCall(request);
call.enqueue(this);
}
上面的代码相信咱们都很熟悉了,先构建了一个 RequestBuilder
,把 url
和 Http Header
都增加进去后,然后构建一个 Request
,最终运用的是 OkHttp
异步调用的代码。
咱们看看 onResponse()
成功的回调中怎样处理的:
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
responseBody = response.body();
if (response.isSuccessful()) {
long contentLength = Preconditions.checkNotNull(responseBody).contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
callback.onDataReady(stream);
} else {
callback.onLoadFailed(new HttpException(response.message(), response.code()));
}
}
同样运用 ContentLengthInputStream
来封装 ResponseBody
的 InputStream
,这儿我要说明下,OkHttp
中自带的重定向处理次数是 20 次,并且无法关闭。
再看看 onFailure()
反常处理的代码是怎样样的:
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "OkHttp failed to obtain result", e);
}
callback.onLoadFailed(e);
}
朴实无华的代码,没啥好解说的。
Decoder
咱们从网络或许本地缓存中获取到的图片文件一般是紧缩过的格局,常见的有 PNG
,JPEG
和 GIF
等等。这些紧缩格局文件的图片 Android
是无法直接烘托的,咱们需求将这些文件解码成 Bitmap
,Android
中能够烘托 Bitmap
Raw
格局的图片。而 Decoder
的作业便是将这些紧缩的图片格局转换成 Bitmap
。
咱们先看看 Decoder
的接口规划:
public interface ResourceDecoder<T, Z> {
boolean handles(@NonNull T source, @NonNull Options options) throws IOException;
@Nullable
Resource<Z> decode(@NonNull T source, int width, int height, @NonNull Options options)
throws IOException;
}
handles()
办法便是用来判别是否能够解码对应的 source
;decode()
办法便是履行解码。
后续咱们就以输入是 InputStream
,解码后输出是 Bitmap
的 Decoder
作为例子来剖析(Glide
的处理办法分为 Android 9
及其以上版别和 Android 9
以下版别两种办法),其他的类型是类似的,我就不全部都剖析了,gif
格局的图片也是类似的,需求的同学自行去找源码。
Android 9 及其以上版别
Android 9
及其以上的版别解码 InputStream
并输出 Bitmap
的 Decoder
是 InputStreamBitmapImageDecoderResourceDecoder
。
咱们来看看它的 decode()
办法:
private final BitmapImageDecoderResourceDecoder wrapped = new BitmapImageDecoderResourceDecoder();
@Override
public Resource<Bitmap> decode(
@NonNull InputStream stream, int width, int height, @NonNull Options options)
throws IOException {
ByteBuffer buffer = ByteBufferUtil.fromStream(stream);
Source source = ImageDecoder.createSource(buffer);
return wrapped.decode(source, width, height, options);
}
首先将传过来的 InputStream
读取到 ByteBuffer
中,然后经过 ByteBuffer
构建一个 Source
目标(只要 Android 9
及其以上版别才能用),然后调用 BitmapImageDecoderResourceDecoder#decode()
办法完结解码,它是支撑输入是 Source
,输出是 Bitmap
的 Decoder
。
咱们再来看看 BitmapImageDecoderResourceDecoder#decode()
办法的完结:
public Resource<Bitmap> decode(
@NonNull Source source, int width, int height, @NonNull Options options) throws IOException {
Bitmap result =
ImageDecoder.decodeBitmap(
source, new DefaultOnHeaderDecodedListener(width, height, options));
if (Log.isLoggable(TAG, Log.VERBOSE)) {
// ...
}
return new BitmapResource(result, bitmapPool);
}
直接调用 ImageDecoder#decodeBitmap()
完结解码,这是 Android
中的完结,这儿要注意 DefaultOnHeaderDecodedListener
回调目标,它其间做了一些装备处理。
咱们来看看 DefaultOnHeaderDecodedListener#onHeaderDecoded()
办法的处理:
@Override
public void onHeaderDecoded(
@NonNull ImageDecoder decoder, @NonNull ImageInfo info, @NonNull Source source) {
// 设置是否支撑 HARDWARE 解码
if (hardwareConfigState.isHardwareConfigAllowed(
requestedWidth,
requestedHeight,
isHardwareConfigAllowed,
/* isExifOrientationRequired= */ false)) {
decoder.setAllocator(ImageDecoder.ALLOCATOR_HARDWARE);
} else {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
}
// 设置内存策略
if (decodeFormat == DecodeFormat.PREFER_RGB_565) {
decoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
}
decoder.setOnPartialImageListener(
new OnPartialImageListener() {
@Override
public boolean onPartialImage(@NonNull DecodeException e) {
// Never return partial images.
return false;
}
});
// 核算目标的尺度
Size size = info.getSize();
int targetWidth = requestedWidth;
if (requestedWidth == Target.SIZE_ORIGINAL) {
targetWidth = size.getWidth();
}
int targetHeight = requestedHeight;
if (requestedHeight == Target.SIZE_ORIGINAL) {
targetHeight = size.getHeight();
}
float scaleFactor =
strategy.getScaleFactor(size.getWidth(), size.getHeight(), targetWidth, targetHeight);
int resizeWidth = Math.round(scaleFactor * size.getWidth());
int resizeHeight = Math.round(scaleFactor * size.getHeight());
if (Log.isLoggable(TAG, Log.VERBOSE)) {
// ...
}
// 设置解码后的 Bitmap 的尺度
decoder.setTargetSize(resizeWidth, resizeHeight);
// 设置色域,P3 或许 sRGB。
if (preferredColorSpace != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
boolean isP3Eligible =
preferredColorSpace == PreferredColorSpace.DISPLAY_P3
&& info.getColorSpace() != null
&& info.getColorSpace().isWideGamut();
decoder.setTargetColorSpace(
ColorSpace.get(isP3Eligible ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB));
}
}
}
设置的代码比较简略:
- 设置是否支撑
HARDWARE
解码。 - 设置内存策略。
- 核算并设置最终输出的
Bitmap
尺度。 - 设置色彩空间,
P3
或许sRGB
。
Android 9 以下版别
Android 9
以下的版别解码 InputStream
并输出 Bitmap
的 Decoder
是 StreamBitmapDecoder
。
咱们来看看它的 decode()
办法:
@Override
public Resource<Bitmap> decode(
@NonNull InputStream source, int width, int height, @NonNull Options options)
throws IOException {
final RecyclableBufferedInputStream bufferedStream;
final boolean ownsBufferedStream;
// 用 RecyclableBufferedInputStream 包裹 InputStream
if (source instanceof RecyclableBufferedInputStream) {
bufferedStream = (RecyclableBufferedInputStream) source;
ownsBufferedStream = false;
} else {
bufferedStream = new RecyclableBufferedInputStream(source, byteArrayPool);
ownsBufferedStream = true;
}
// 用 ExceptionPassthroughInputStream 包裹 InputStream
ExceptionPassthroughInputStream exceptionStream =
ExceptionPassthroughInputStream.obtain(bufferedStream);
// 用 MarkEnforcingInputStream 包裹 InputStream
MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream);
UntrustedCallbacks callbacks = new UntrustedCallbacks(bufferedStream, exceptionStream);
try {
// 调用 Downsampler#decode() 办法完结解码
return downsampler.decode(invalidatingStream, width, height, options, callbacks);
} finally {
exceptionStream.release();
if (ownsBufferedStream) {
bufferedStream.release();
}
}
}
输入的 InputStream
会被 RecyclableBufferedInputStream
(其间会运用 LruArrayPool
来获取 ByteArray
,在第一篇文章中介绍过,一般这个缓存池的巨细是 4MB
)、 ExceptionPassthroughInputStream
(用于拦截反常) 和 MarkEnforcingInputStream
(用于读取标记)包裹。最终调用 Downsampler#decode()
办法完结解码。
咱们再来看看 Downsampler#decode()
办法的完结:
public Resource<Bitmap> decode(
InputStream is,
int requestedWidth,
int requestedHeight,
Options options,
DecodeCallbacks callbacks)
throws IOException {
return decode(
new ImageReader.InputStreamImageReader(is, parsers, byteArrayPool),
requestedWidth,
requestedHeight,
options,
callbacks);
}
将 InputStream
封装成 InputStreamImageReader
持续调用 decode()
办法,咱们持续追寻。
private Resource<Bitmap> decode(
ImageReader imageReader,
int requestedWidth,
int requestedHeight,
Options options,
DecodeCallbacks callbacks)
throws IOException {
// 从 LruArrayPool 中获取解码进程中用到的暂时缓存,巨细为 64K。
byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
// 获取默许的 Options
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
// 增加解码进程中用到的暂时缓存
bitmapFactoryOptions.inTempStorage = bytesForOptions;
// 获取解码格局 565 或许 8888
DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
// 获取色彩空间,P3 或许 sRGB
PreferredColorSpace preferredColorSpace = options.get(PREFERRED_COLOR_SPACE);
// 获取裁剪策略,比如 FitCenter,或许 CenterCrop。
DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
// 是否支撑 HARDWARE。
boolean isHardwareConfigAllowed =
options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);
try {
// 解码
Bitmap result =
decodeFromWrappedStreams(
imageReader,
bitmapFactoryOptions,
downsampleStrategy,
decodeFormat,
preferredColorSpace,
isHardwareConfigAllowed,
requestedWidth,
requestedHeight,
fixBitmapToRequestedDimensions,
callbacks);
return BitmapResource.obtain(result, bitmapPool);
} finally {
releaseOptions(bitmapFactoryOptions);
byteArrayPool.put(bytesForOptions);
}
}
上面的代码比较简略,看其间注释就好了,这儿要强调一下,这个从 LruArrayPool
中获取了 64K
的 ByteArray
,用于解码时用到的暂时内存,经过 Options#inTempStorage
参数设置。
咱们持续看 decodeFromWrappedStreams()
办法的完结:
private Bitmap decodeFromWrappedStreams(
ImageReader imageReader,
BitmapFactory.Options options,
DownsampleStrategy downsampleStrategy,
DecodeFormat decodeFormat,
PreferredColorSpace preferredColorSpace,
boolean isHardwareConfigAllowed,
int requestedWidth,
int requestedHeight,
boolean fixBitmapToRequestedDimensions,
DecodeCallbacks callbacks)
throws IOException {
long startTime = LogTime.getLogTime();
// 核算图片的巨细
int[] sourceDimensions = getDimensions(imageReader, options, callbacks, bitmapPool);
int sourceWidth = sourceDimensions[0];
int sourceHeight = sourceDimensions[1];
String sourceMimeType = options.outMimeType;
if (sourceWidth == -1 || sourceHeight == -1) {
isHardwareConfigAllowed = false;
}
int orientation = imageReader.getImageOrientation();
// 经过 Exif 来读取图片的旋转视点
int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);
// 核算目标 bitmap 尺度
int targetWidth =
requestedWidth == Target.SIZE_ORIGINAL
? (isRotationRequired(degreesToRotate) ? sourceHeight : sourceWidth)
: requestedWidth;
int targetHeight =
requestedHeight == Target.SIZE_ORIGINAL
? (isRotationRequired(degreesToRotate) ? sourceWidth : sourceHeight)
: requestedHeight;
ImageType imageType = imageReader.getImageType();
// 核算装备放缩
calculateScaling(
imageType,
imageReader,
callbacks,
bitmapPool,
downsampleStrategy,
degreesToRotate,
sourceWidth,
sourceHeight,
targetWidth,
targetHeight,
options);
// 核算运用 565 还是 8888
calculateConfig(
imageReader,
decodeFormat,
isHardwareConfigAllowed,
isExifOrientationRequired,
options,
targetWidth,
targetHeight);
boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// Prior to KitKat, the inBitmap size must exactly match the size of the bitmap we're decoding.
if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
int expectedWidth;
int expectedHeight;
if (sourceWidth >= 0
&& sourceHeight >= 0
&& fixBitmapToRequestedDimensions
&& isKitKatOrGreater) {
expectedWidth = targetWidth;
expectedHeight = targetHeight;
} else {
// 核算最终输出的 Bitmap 尺度
float densityMultiplier =
isScaling(options) ? (float) options.inTargetDensity / options.inDensity : 1f;
int sampleSize = options.inSampleSize;
int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize);
int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize);
expectedWidth = Math.round(downsampledWidth * densityMultiplier);
expectedHeight = Math.round(downsampledHeight * densityMultiplier);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
// ...
}
}
if (expectedWidth > 0 && expectedHeight > 0) {
// 设置中增加输出的 Bitmap,从 BitmapPool 中获取
setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
}
}
// 设置最终输出的色域
if (preferredColorSpace != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
boolean isP3Eligible =
preferredColorSpace == PreferredColorSpace.DISPLAY_P3
&& options.outColorSpace != null
&& options.outColorSpace.isWideGamut();
options.inPreferredColorSpace =
ColorSpace.get(isP3Eligible ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
}
}
// 解码
Bitmap downsampled = decodeStream(imageReader, options, callbacks, bitmapPool);
callbacks.onDecodeComplete(bitmapPool, downsampled);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
// ...
}
Bitmap rotated = null;
// 履行旋转操作
if (downsampled != null) {
downsampled.setDensity(displayMetrics.densityDpi);
rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);
if (!downsampled.equals(rotated)) {
bitmapPool.put(downsampled);
}
}
return rotated;
}
上面的代码略微有一点点复杂,理一下:
- 经过
getDimensions()
办法获取图片的尺度。 - 读取图片中
Exif
信息里边的旋转视点信息。 - 经过
calculateScaling()
办法核算装备放缩的信息,图片的编码办法也会影响这个值,图片的放缩是经过修改BitmapFactory.Options
中inDensity
、inTargetDensity
和inSampleSize
等等参数,不太清楚的同学能够看看[小笔记] Android 中 Bitmap 的放缩装备。 - 经过
calculateConfig()
办法核算运用565
还是8888
。 - 经过
setInBitmap()
设置缓存的Bitmap
到设置中,这样就不用每次解码时都创立一个新的Bitmap
,优化内存的运用。 - 调用
decodeStream()
办法完结解码。 - 根据
Exif
中的信息,履行旋转操作。
上面的一些办法咱们一同剖析一下,有的没有剖析到的,咱们感兴趣的话能够自己去找找代码。
咱们先来看看 getDimenssions()
办法是怎样获取图片的尺度的:
private static int[] getDimensions(
ImageReader imageReader,
BitmapFactory.Options options,
DecodeCallbacks decodeCallbacks,
BitmapPool bitmapPool)
throws IOException {
options.inJustDecodeBounds = true;
decodeStream(imageReader, options, decodeCallbacks, bitmapPool);
options.inJustDecodeBounds = false;
return new int[] {options.outWidth, options.outHeight};
}
上面的代码很简略仅仅将设置中的 inJustDecodeBounds
参数设置为 true
,然后调用 decodeStream()
办法去解码,最终 options
中的 outWith
和 outHeight
便是对应的图片的尺度。
再看看 setInBitmap()
办法是怎样设置输入的 Bitmap
的:
private static void setInBitmap(
BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
@Nullable Bitmap.Config expectedConfig = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (options.inPreferredConfig == Config.HARDWARE) {
return;
}
expectedConfig = options.outConfig;
}
if (expectedConfig == null) {
expectedConfig = options.inPreferredConfig;
}
options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
}
当装备是 HARDWARE
时,不需求设置输出的 Bitmap
,由于这种装备下不会占用内存而是占用显存,一起 HARDWARE
装备的 Bitmap
也无法存放在 LruBitmapPool
中,由于这种 Bitmap
是无法修改的。
输出的 Bitmap
是从 LruBitmapPool
中获取的,第一篇文章中现已介绍过 LruBitmapPool
,在 Android 8
以下的许多设备上面 Pool
的巨细是 4 倍屏幕巨细的图片占用的内存巨细,在 Android 8
及其以上的设备中这个 Pool
的巨细设置为 0,由于 Android 8
的设备默许的 Bitmap
装备是 HARDWARE
,所以它不需求 BitmapPool
来缓存(不过我看到新的版别又把这个值由 0 调整为 1 倍屏幕巨细的图片的内存)。
咱们再看看 decodeStream()
办法是怎样解码图片的:
private static Bitmap decodeStream(
ImageReader imageReader,
BitmapFactory.Options options,
DecodeCallbacks callbacks,
BitmapPool bitmapPool)
throws IOException {
if (!options.inJustDecodeBounds) {
callbacks.onObtainBounds();
imageReader.stopGrowingBuffers();
}
int sourceWidth = options.outWidth;
int sourceHeight = options.outHeight;
String outMimeType = options.outMimeType;
final Bitmap result;
TransformationUtils.getBitmapDrawableLock().lock();
try {
// 经过 ImageReader 解码
result = imageReader.decodeBitmap(options);
} catch (IllegalArgumentException e) {
IOException bitmapAssertionException =
newIoExceptionForInBitmapAssertion(e, sourceWidth, sourceHeight, outMimeType, options);
if (Log.isLoggable(TAG, Log.DEBUG)) {
// ...
}
if (options.inBitmap != null) {
// 假设解码失利,一起有设置输出的 Bitmap,将输出的 Bitmap 移除后再尝试解码一次。
try {
bitmapPool.put(options.inBitmap);
options.inBitmap = null;
return decodeStream(imageReader, options, callbacks, bitmapPool);
} catch (IOException resetException) {
throw bitmapAssertionException;
}
}
throw bitmapAssertionException;
} finally {
TransformationUtils.getBitmapDrawableLock().unlock();
}
return result;
}
上面代码看似许多,其实逻辑十分简略,直接调用 ImageReader#decodeBitmap()
办法完结解码,假设解码失利一起有设置自界说的 Bitmap
,那么就移除这个 Bitmap
再重试一次,我想的是可能这个自界说的 Bitmap
可能不对,导致的反常,所以移除后再试一次。
咱们看看 InputStreamImageReader#decodeBitmap()
办法是怎样完结的:
@Nullable
@Override
public Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException {
return BitmapFactory.decodeStream(dataRewinder.rewindAndGet(), null, options);
}
我咱们自己写的代码也没有什么不同,也是直接调用 BitmapFactory#decodeStream()
办法完结解码。
最终
本篇文章是 Glide
源码阅览系列的最终一篇,Glide
源码的复杂程度比我想象的确实要高许多,假设你看懂了 Glide
处理网络图片的办法,我相信你一定会有所收成。
今天多年作业的同事被裁了 lastday,不开心 T_T