美文网首页
Glide资源的加载与处理

Glide资源的加载与处理

作者: 伍小伟 | 来源:发表于2019-01-26 23:30 被阅读0次

Glide源码很多,直接的钻进去看的话会感到头大,我们现在已用过一些加载框架,也可能大概看过一些源码,在这个基础上,假如自己要构建一个图片加载框架的话,我们需要怎么去设计。

我们可能需要考虑到:
1、从哪里加载,根据图片资源的来源,可以大概分为网络上的图片和本地的图片
2、既然从不同地方加载,那么就需要不同的方法去加载,比如网络上的我们需要用的http请求获取,本地的图需要用的流去获取。
3、我们第二步获取到的是图片的二进制流,那么就需要转换成我们需要的类型,比如bitmap、Drawable、File
3、我们同时可能加载多张图片,那么就需要用到多线程加载,比如线程池
4、我们可能多次加载同一张图,那么我们不应该重复去加载,所以需要用到缓存,缓存的话又涉及到内存缓存与磁盘缓存,缓存如何高效的去管理
5、图片占用的内存很大,如何避免内存溢出
6、如何去管理加载的请求
7、还有一些我们考虑不到的东西,等等。。。。。

Glide的设计者肯定也考虑到了这些,只多不少,通过阅读源码,确实是这样子的,但是并不像我们上面说的那么简单,每一点都够我们看个半天,其中的设计思想也值得我们学习。

资源加载

来源

Glide有一个构造方法,它里面有很多东西,我们现在只看其中一部分

register(File.class, ParcelFileDescriptor.class, new FileDescriptorFileLoader.Factory());
register(File.class, InputStream.class, new StreamFileLoader.Factory());
register(int.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
register(int.class, InputStream.class, new StreamResourceLoader.Factory());
register(Integer.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
register(Integer.class, InputStream.class, new StreamResourceLoader.Factory());
register(String.class, ParcelFileDescriptor.class, new FileDescriptorStringLoader.Factory());
register(String.class, InputStream.class, new StreamStringLoader.Factory());
register(Uri.class, ParcelFileDescriptor.class, new FileDescriptorUriLoader.Factory());
register(Uri.class, InputStream.class, new StreamUriLoader.Factory());
register(URL.class, InputStream.class, new StreamUrlLoader.Factory());
//注意一下这个,后面我们会用到
register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
register(byte[].class, InputStream.class, new StreamByteArrayLoader.Factory());

---------------------
 public <T, Y> void register(Class<T> modelClass, Class<Y> resourceClass, ModelLoaderFactory<T, Y> factory) {
        ModelLoaderFactory<T, Y> removed = loaderFactory.register(modelClass, resourceClass, factory);
        if (removed != null) {
            removed.teardown();
        }
    }

modelClass,有File、resourceId、文件路径、网络链接、Uri、GlideUrl、二进制数组这集中类型

加载后的目标资源类型

resourceClass,有ParcelFileDescriptor、InputStream这两类

资源加载工厂

ModelLoaderFactory,资源加载工厂,通过它来确定如何加载,它会根据modelClass和resouceClass构建一个ModelLoader,这个ModelLoader接口有一个getResourceFetcher方法,它返回一个DataFetcher,就是通过这个DataFetcher去实际加载了资源

Glide会通过register(Class<T> modelClass, Class<Y> resourceClass, ModelLoaderFactory<T, Y> factory)方法,将modelClass、resourceClass、ModelLoaderFactory的保存在GenericLoaderFactory中,当我们去加载具体的modelClass类型的时候,就去这个GenericLoaderFactory当中查找对应的ModelLoaderFactory。

public class GenericLoaderFactory {
    //以modelClass为key,resourceClass和ModelLoaderFactory组成的map为value保存
    private final Map<Class/*T*/, Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/>> modelClassToResourceFactories =
            new HashMap<Class, Map<Class, ModelLoaderFactory>>();
......
}

DataFetcher

DataFetcher有以下实现类,我们从名字可以看出来它们具体用来加载哪种类型的modelClass

DataFetcher.png

我们最常用的就是HttpUrlFetcher了,我们看一下是怎么通过图片链接(modelClass)找到DataFetcher,也就是HttpUrlFetcher的。

modelClass是String,我们去GenericLoaderFactory找,找到FileDescriptorStringLoader.Factory和StreamStringLoader.Factory,然后分别得到FileDescriptorStringLoader和StreamStringLoader,它们都继承了StringLoader

public class StringLoader<T> implements ModelLoader<String, T> {
    private final ModelLoader<Uri, T> uriLoader;
    //子类传入
    public StringLoader(ModelLoader<Uri, T> uriLoader) {
        this.uriLoader = uriLoader;
    }
}
public static class Factory implements ModelLoaderFactory<String, ParcelFileDescriptor> {
        @Override
        public ModelLoader<String, ParcelFileDescriptor> build(Context context, GenericLoaderFactory factories) {
            return new FileDescriptorStringLoader(factories.buildModelLoader(Uri.class, ParcelFileDescriptor.class));
        }
}
public static class Factory implements ModelLoaderFactory<String, InputStream> {
        @Override
        public ModelLoader<String, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new StreamStringLoader(factories.buildModelLoader(Uri.class, InputStream.class));
        }
    }

发现他们的modelLoader分别通过factories.buildModelLoader(Uri.class, ParcelFileDescriptor.class)和factories.buildModelLoader(Uri.class, InputStream.class)从注册的工厂类中查找,得到FileDescriptorUriLoader和StreamUriLoader

    public static class Factory implements ModelLoaderFactory<Uri, ParcelFileDescriptor> {
        @Override
        public ModelLoader<Uri, ParcelFileDescriptor> build(Context context, GenericLoaderFactory factories) {
            return new FileDescriptorUriLoader(context, factories.buildModelLoader(GlideUrl.class,
                    ParcelFileDescriptor.class));
        }
    }
   public static class Factory implements ModelLoaderFactory<Uri, InputStream> {
        @Override
        public ModelLoader<Uri, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new StreamUriLoader(context, factories.buildModelLoader(GlideUrl.class, InputStream.class));
        }
    }

然后发现我们还没有得到我们想要的ModelLoader,还得继续去找,通过factories.buildModelLoader(GlideUrl.class,ParcelFileDescriptor.class)和factories.buildModelLoader(GlideUrl.class, InputStream.class),我们之前在Glide的构造方法中注册了这个,我们要找的其实就是他了,我们想要的ModelLoader就是HttpUrlGlideUrlLoader,它返回的DataFetcher就是我们需要的HttpUrlFetcher了

register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());

public class HttpUrlGlideUrlLoader implements StreamModelLoader<GlideUrl> {
 @Override
    public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
        // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time spent parsing urls.
        GlideUrl url = model;
        if (modelCache != null) {
            url = modelCache.get(model, 0, 0);
            if (url == null) {
                modelCache.put(model, 0, 0, model);
                url = model;
            }
        }
        return new HttpUrlFetcher(url);
    }
}

看看HttpUrlFetcher

public class HttpUrlFetcher implements DataFetcher<InputStream> {
    private static final String TAG = "HttpUrlFetcher";
    private static final int MAXIMUM_REDIRECTS = 5;
    private static final HttpUrlConnectionFactory DEFAULT_CONNECTION_FACTORY = new DefaultHttpUrlConnectionFactory();

    private final GlideUrl glideUrl;
    private final HttpUrlConnectionFactory connectionFactory;

    private HttpURLConnection urlConnection;
    private InputStream stream;
    private volatile boolean isCancelled;

    public HttpUrlFetcher(GlideUrl glideUrl) {
        this(glideUrl, DEFAULT_CONNECTION_FACTORY);
    }

    // Visible for testing.
    HttpUrlFetcher(GlideUrl glideUrl, HttpUrlConnectionFactory connectionFactory) {
        this.glideUrl = glideUrl;
        this.connectionFactory = connectionFactory;
    }

    @Override
    public InputStream loadData(Priority priority) throws Exception {
        return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
    }
  
    private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
            throws IOException {
        //重定向超过5,就直接报异常了,这个问题开发当中还真遇到过,特别是服务器重启的时候
        if (redirects >= MAXIMUM_REDIRECTS) {
            throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
        } 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 IOException("In re-direct loop");
                }
            } catch (URISyntaxException e) {
                // Do nothing, this is best effort.
            }
        }
      //终于看到实际的请求网络了
        urlConnection = connectionFactory.build(url);
        for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
          urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
        }
        urlConnection.setConnectTimeout(2500);
        urlConnection.setReadTimeout(2500);
        urlConnection.setUseCaches(false);
        urlConnection.setDoInput(true);

        // Connect explicitly to avoid errors in decoders if connection fails.
        urlConnection.connect();
        if (isCancelled) {
            return null;
        }
        final int statusCode = urlConnection.getResponseCode();
      //请求成功
        if (statusCode / 100 == 2) {
            return getStreamForSuccessfulRequest(urlConnection);
        } else if (statusCode / 100 == 3) {
        //重定向
            String redirectUrlString = urlConnection.getHeaderField("Location");
            if (TextUtils.isEmpty(redirectUrlString)) {
                throw new IOException("Received empty or null redirect url");
            }
            URL redirectUrl = new URL(url, redirectUrlString);
          //获取到重定向的url,继续请求
            return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
        } else {
            if (statusCode == -1) {
                throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
            }
            throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
        }
    }

    private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
            throws IOException {
        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();
        }
        return stream;
    }

    @Override
    public void cleanup() {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException e) {
                // Ignore
            }
        }
        if (urlConnection != null) {
            urlConnection.disconnect();
        }
    }

    @Override
    public String getId() {
        return glideUrl.getCacheKey();
    }

    @Override
    public void cancel() {
        // TODO: we should consider disconnecting the url connection here, but we can't do so directly because cancel is
        // often called on the main thread.
        isCancelled = true;
    }

    interface HttpUrlConnectionFactory {
        HttpURLConnection build(URL url) throws IOException;
    }

    private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory {
        @Override
        public HttpURLConnection build(URL url) throws IOException {
            return (HttpURLConnection) url.openConnection();
        }
    }
}

资源的处理

DataLoadProvider

HttpUrlFetcher加载返回的是一个流,我们展示图片的时候传递给ImageView的应该是一个Bitmap或者Drawable,前面说到在Glide的构造函数里注册了很多Glide支持的资源来源类型modelClass以及加载后的资源类型resourceClass,resourceClass是如何转换成我们需要的Bitmap或者Drawable的呢,就是通过DataLoadProvider,DataLoadProvider在Glide的构造函数中注册,从名字我们可以明白它们的功能,比如StreamBitmapDataLoadProvider,就是将Stream转换成Bitmap

        dataLoadProviderRegistry = new DataLoadProviderRegistry();
        //,
        StreamBitmapDataLoadProvider streamBitmapLoadProvider =
                new StreamBitmapDataLoadProvider(bitmapPool, decodeFormat);
        dataLoadProviderRegistry.register(InputStream.class, Bitmap.class, streamBitmapLoadProvider);

        FileDescriptorBitmapDataLoadProvider fileDescriptorLoadProvider =
                new FileDescriptorBitmapDataLoadProvider(bitmapPool, decodeFormat);
        dataLoadProviderRegistry.register(ParcelFileDescriptor.class, Bitmap.class, fileDescriptorLoadProvider);

        ImageVideoDataLoadProvider imageVideoDataLoadProvider =
                new ImageVideoDataLoadProvider(streamBitmapLoadProvider, fileDescriptorLoadProvider);
        dataLoadProviderRegistry.register(ImageVideoWrapper.class, Bitmap.class, imageVideoDataLoadProvider);

        GifDrawableLoadProvider gifDrawableLoadProvider =
                new GifDrawableLoadProvider(context, bitmapPool);
        dataLoadProviderRegistry.register(InputStream.class, GifDrawable.class, gifDrawableLoadProvider);

        dataLoadProviderRegistry.register(ImageVideoWrapper.class, GifBitmapWrapper.class,
                new ImageVideoGifDrawableLoadProvider(imageVideoDataLoadProvider, gifDrawableLoadProvider, bitmapPool));

        dataLoadProviderRegistry.register(InputStream.class, File.class, new StreamFileDataLoadProvider());

我们只看StreamBitmapDataLoadProvider,其他的Provider虽然转换的源类型与目标类型不一致,其实懂了一个,其他的也就懂了,StreamBitmapDataLoadProvider实现了DataLoadProvider<T, Z>接口,


public class StreamBitmapDataLoadProvider implements DataLoadProvider<InputStream, Bitmap> {
  //加载原始数据InputStream为Bitmap
    private final StreamBitmapDecoder decoder;
//将加载以及转换后的Bitmap资源写入磁盘缓存
    private final BitmapEncoder encoder;
//将原始数据写入硬盘缓存
    private final StreamEncoder sourceEncoder;
//从硬盘读取
    private final FileToStreamDecoder<Bitmap> cacheDecoder;

    public StreamBitmapDataLoadProvider(BitmapPool bitmapPool, DecodeFormat decodeFormat) {
        sourceEncoder = new StreamEncoder();
        decoder = new StreamBitmapDecoder(bitmapPool, decodeFormat);
        encoder = new BitmapEncoder();
        cacheDecoder = new FileToStreamDecoder<Bitmap>(decoder);
    }

    @Override
    public ResourceDecoder<File, Bitmap> getCacheDecoder() {
        return cacheDecoder;
    }

    @Override
    public ResourceDecoder<InputStream, Bitmap> getSourceDecoder() {
        return decoder;
    }

    @Override
    public Encoder<InputStream> getSourceEncoder() {
        return sourceEncoder;
    }

    @Override
    public ResourceEncoder<Bitmap> getEncoder() {
        return encoder;
    }
}

转换的并不是StreamBitmapDataLoadProvider来完成,而是通过它里面的encoder、decoder、sourceEncoder、sourceEncoder

      //加载原始数据InputStream为Bitmap
      StreamBitmapDecoder decoder;
@Override
    public Resource<Bitmap> decode(InputStream source, int width, int height) {
        Bitmap bitmap = downsampler.decode(source, bitmapPool, width, height, decodeFormat);
        return BitmapResource.obtain(bitmap, bitmapPool);
    }
    //将加载以及转换后的Bitmap资源写入磁盘缓存
    BitmapEncoder encoder;
 @Override
    public boolean encode(Resource<Bitmap> resource, OutputStream os) {
        final Bitmap bitmap = resource.get();

        long start = LogTime.getLogTime();
        Bitmap.CompressFormat format = getFormat(bitmap);
        bitmap.compress(format, quality, os);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "Compressed with type: " + format + " of size " + Util.getBitmapByteSize(bitmap) + " in "
                    + LogTime.getElapsedMillis(start));
        }
        return true;
    }
    //将原始数据写入硬盘缓存
    StreamEncoder sourceEncoder;
public boolean encode(InputStream data, OutputStream os) {
        byte[] buffer = ByteArrayPool.get().getBytes();
        try {
            int read;
            while ((read = data.read(buffer)) != -1) {
                    os.write(buffer, 0, read);
            }
            return true;
        } catch (IOException e) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Failed to encode data onto the OutputStream", e);
            }
            return false;
        } finally {
            ByteArrayPool.get().releaseBytes(buffer);
        }
    }
//从硬盘读取
    FileToStreamDecoder<Bitmap> cacheDecoder;
 @Override
    public Resource<T> decode(File source, int width, int height) throws IOException {
        InputStream is = null;
        Resource<T> result = null;
        try {
            is = fileOpener.open(source);
            result = streamDecoder.decode(is, width, height);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // Do nothing.
                }
            }
        }
        return result;
    }

其实在加载与处理的过程中还涉及到很多,比如缓存,我们知道Glide的缓存包括缓存包括磁盘缓存和内存缓存,接下来的文章继续

相关文章

  • Glide资源的加载与处理

    Glide源码很多,直接的钻进去看的话会感到头大,我们现在已用过一些加载框架,也可能大概看过一些源码,在这个基础上...

  • error:duplicate value for resour

    错误:资源的“Atf/LayOutsActhRooGrime'与CONFIG”的重复值 解决:在加载glide框架...

  • Glide4.10.0加载图片进度监听

    参考:Glide —— 替换资源加载组件Android Glide4.0+图片加载进度监听 主要的6文件:Prog...

  • Android图片加载框架——Glide

    Glide使用教程 Glide是一个快速有效的开源图像加载框架,可实现内存缓存、磁盘缓存,完成对图片资源的加载,汇...

  • Glide使用详解

    Gradle配置: 添加访问网络权限 加载图片到ImageView Glide支持网络资源、assets资源、Re...

  • Glide

    Glide常用: ////原生加载,图片过大,OOM异常 使用Glide加载 Glide 1.简介 Glide,一...

  • Glide

    Glide常用: //原生加载,图片过大,OOM异常 使用Glide加载 Glide 1.简介 Glide,一个被...

  • Glide ② — 缓存机制

    阅读本文需要先了解Glide加载流程 缓存的分类 首先介绍一下Glide中对图片资源的封装类:EngineReso...

  • 初探Glide,Google推介的图片缓存库

    0 .Thanks 项目地址 Android Glide图片加载(加载监听、加载动画)【Glide】重新加载图片 ...

  • Glide 加载图片保存至本地,加载回调监听

    Glide 加载图片使用到的两个记录 Glide 加载图片保存至本地指定路径 Glide 加载图片回调方法

网友评论

      本文标题:Glide资源的加载与处理

      本文链接:https://www.haomeiwen.com/subject/udzvjqtx.html