美文网首页
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资源的加载与处理

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