美文网首页Android源码分析
面试之——Android-Universal-Image-Loa

面试之——Android-Universal-Image-Loa

作者: 的一幕 | 来源:发表于2018-01-26 19:02 被阅读20次

    为了开年面试的准备,开始看一些之前用过的开源库,今天就主要围绕Android-Universal-Image-Loadergithub的源码带着大家浏览一遍。为了更好地理解Android-Universal-Image-Loader代码部分,这里给大家画张类的结构图:

    Android-Universal-Image-Loader类图结构.png

    大家看到上面图,其实也不知道咋回事。下面就顺着代码去看看是怎么个流程:

    ImageLoader.getInstance().displayImage(IMAGE_URLS[position], holder.image, options, animateFirstListener);
    

    上面的代码是大家经常用的使用方法,那咱们去瞧瞧displayImage方法是如何实现的,几个重载的displayImage最终都会调用该displayImage方法

    public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
        //省略空判断的代码 
    
        String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
        engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
        listener.onLoadingStarted(uri, imageAware.getWrappedView());
        Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
            //内存中bitmap存在
        if (bmp != null && !bmp.isRecycled()) {
            L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
            //如果是需要进行process操作,默认不会process操作的
            if (options.shouldPostProcess()) {
                ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                        options, listener, progressListener, engine.getLockForUri(uri));
                ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
                        defineHandler(options));
                //默认不是cync操作
                if (options.isSyncLoading()) {
                    displayTask.run();
                } else {
                    engine.submit(displayTask);
                }
            } else {
                //直接从内存中获取bitmap进行显示了
                options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
                listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
            }
        } else {
          //内存中不存在bitmap的操作
            if (options.shouldShowImageOnLoading()) {
                imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
            } else if (options.isResetViewBeforeLoading()) {
                imageAware.setImageDrawable(null);
            }
            ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                    options, listener, progressListener, engine.getLockForUri(uri));
            //内存中没有bitmap走该task
            LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
                    defineHandler(options));
            if (options.isSyncLoading()) {
                 //同步的话,就是立即执行
                displayTask.run();
            } else {
                //默认走这里的,这里不会同步执行task,而是放在线程池中等待执行的
                engine.submit(displayTask);
            }
        }
    }
    

    那咱们知道了内存中没有bitmap的时候,会创建了一个LoadAndDisplayImageTask。那咱们去瞧瞧:

    image.png
    从图上可以看出,它是一个子线程的操作。那咱们就直接去看run方法吧:
    @Override
    public void run() {
        //省略部分代码
        Bitmap bmp;
        try {
            checkTaskNotActual();
            //此处又从memory中读取了
            bmp = configuration.memoryCache.get(memoryCacheKey);
            //内存中没有数据
            if (bmp == null || bmp.isRecycled()) {
                //从disk活网络加载bitmap
                bmp = tryLoadBitmap();
           //省略代码   
                if (bmp != null && options.isCacheInMemory()) {
                    L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
                //获取完了后放到内存中    
    configuration.memoryCache.put(memoryCacheKey, bmp);
                }
            } else {
                loadedFrom = LoadedFrom.MEMORY_CACHE;
                L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
            }
            //省略代码
        } catch (TaskCancelledException e) {
            fireCancelEvent();
            return;
        } finally {
            loadFromUriLock.unlock();
        }
        //要显示的task
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
        runTask(displayBitmapTask, syncLoading, handler, engine);
    }
    

    上面代码也说明了,如果memory中获取不到bitmap会走tryLoadBitmap方法,那咱们也去看看:

    private Bitmap tryLoadBitmap() throws TaskCancelledException {
        Bitmap bitmap = null;
        try {
          //从disk中获取bitmap,注意了这里只是获取是否存在该bitmap的文件
            File imageFile = configuration.diskCache.get(uri);
          //如果disk中有我们想要的bitmap文件
            if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
                L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
                loadedFrom = LoadedFrom.DISC_CACHE;
                checkTaskNotActual();
                      //进行了decodeImage操作
                bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
            }
                  //网络操作的分支
            if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
                L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
                loadedFrom = LoadedFrom.NETWORK;
                            //默认的Scheme是网络类型的
                String imageUriForDecoding = uri;
                if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
                    imageFile = configuration.diskCache.get(uri);
                                    //如果图片的文件存在,此时会将uri格式改成FILE类型
                    if (imageFile != null) {
                        imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
                    }
                }
                checkTaskNotActual();
                bitmap = decodeImage(imageUriForDecoding);
            //省略代码
            }
        } catch (IllegalStateException e) {
            fireFailEvent(FailType.NETWORK_DENIED, null);
        } catch (TaskCancelledException e) {
            throw e;
        } catch (IOException e) {
            L.e(e);
            fireFailEvent(FailType.IO_ERROR, e);
        } catch (OutOfMemoryError e) {
            L.e(e);
            fireFailEvent(FailType.OUT_OF_MEMORY, e);
        } catch (Throwable e) {
            L.e(e);
            fireFailEvent(FailType.UNKNOWN, e);
        }
        return bitmap;
    }
    

    可以看出上面最后都进调用了decodeImage方法,那咱们去瞧瞧:

    private Bitmap decodeImage(String imageUri) throws IOException {
        ViewScaleType viewScaleType = imageAware.getScaleType();
        ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
                getDownloader(), options);
        return decoder.decode(decodingInfo);
    }
    

    这里就顺藤摸瓜呗,看看decoderdecode方法了,这里的decoder对象其实是一个BaseImageDecoder对象,那咱们进去看看:

    @Override
    public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
        Bitmap decodedBitmap;
        ImageFileInfo imageInfo;
            //获取输入流
        InputStream imageStream = getImageStream(decodingInfo);
        if (imageStream == null) {
            L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
            return null;
        }
        try {
            imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
            imageStream = resetStream(imageStream, decodingInfo);
            Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
                //输入流转成bitmap
            decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
        } finally {
            IoUtils.closeSilently(imageStream);
        }
        if (decodedBitmap == null) {
            L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
        } else {
            decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
                    imageInfo.exif.flipHorizontal);
        }
        return decodedBitmap;
    }
    

    上面做了两件事:获取输入流输入流转成bitmap,咱们要着重看下这里是怎么获取输入流的:

    protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException {
        return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
    }
    

    decodingInfo.getDownloader()实际上默认是一个BaseImageDownloader对象,那咱们瞧瞧getStream方法:

    @Override
    public InputStream getStream(String imageUri, Object extra) throws IOException {
        switch (Scheme.ofUri(imageUri)) {
                    //走网络的
            case HTTP:
            case HTTPS:
                return getStreamFromNetwork(imageUri, extra);
                    //disk的cache就是走这里获取输入流
            case FILE:
                return getStreamFromFile(imageUri, extra);
            case CONTENT:
                return getStreamFromContent(imageUri, extra);
            case ASSETS:
                return getStreamFromAssets(imageUri, extra);
            case DRAWABLE:
                return getStreamFromDrawable(imageUri, extra);
            case UNKNOWN:
            default:
                return getStreamFromOtherSource(imageUri, extra);
        }
    }
    

    那咱们也进去看看这两种inputStream都是怎么获取的吧:

    protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
          //获取connection对象
        HttpURLConnection conn = createConnection(imageUri, extra);
        int redirectCount = 0;
        while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
            conn = createConnection(conn.getHeaderField("Location"), extra);
            redirectCount++;
        }
        InputStream imageStream;
        try {
            imageStream = conn.getInputStream();
        } catch (IOException e) {
            // Read all data to allow reuse connection (http://bit.ly/1ad35PY)
            IoUtils.readAndCloseStream(conn.getErrorStream());
            throw e;
        }
        //省略代码
        return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
    }
    
    protected HttpURLConnection createConnection(String url, Object extra) throws IOException {
    //这里就是咋们熟悉不过的HttpURLConnection请求代码了
        String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);
        HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();
        conn.setConnectTimeout(connectTimeout);
        conn.setReadTimeout(readTimeout);
        return conn;
    }
    

    也顺便看下file类型的输入流获取吧:

    protected InputStream getStreamFromFile(String imageUri, Object extra) throws IOException {
        String filePath = Scheme.FILE.crop(imageUri);
        if (isVideoFileUri(imageUri)) {
            return getVideoThumbnailStream(filePath);
        } else {
    //图片格式走这里,可以看到输入流对象直接是通过filePah直接new出来的
            BufferedInputStream imageStream = new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE);
            return new ContentLengthInputStream(imageStream, (int) new File(filePath).length());
        }
    }
    

    其实咋们漏了一个点,就是什么时候存储bitmap到disk中的呢?咋们还得回去看下tryLoadBitmap方法中有这么一段:

    if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
        imageFile = configuration.diskCache.get(uri);
        if (imageFile != null) {
            imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
        }
    }
    

    其实这里的tryCacheImageOnDisk方法就是进行保存到disk中的:

    private boolean tryCacheImageOnDisk() throws TaskCancelledException {
        L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
        boolean loaded;
        try {
                  //存储到disk之前先判断有没有下载到图片
            loaded = downloadImage();
            if (loaded) {
                int width = configuration.maxImageWidthForDiskCache;
                int height = configuration.maxImageHeightForDiskCache;
                if (width > 0 || height > 0) {
                    L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
                    resizeAndSaveImage(width, height); // TODO : process boolean result
                }
            }
        } catch (IOException e) {
            L.e(e);
            loaded = false;
        }
        return loaded;
    }
    
    private boolean downloadImage() throws IOException {
            //注意了这里的getDownloader不是上面说的BaseImageDownloader了,这里是要分情况的
        InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
        if (is == null) {
            L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
            return false;
        } else {
            try {
                return configuration.diskCache.save(uri, is, this);
            } finally {
                IoUtils.closeSilently(is);
            }
        }
    }
    
    private ImageDownloader getDownloader() {
        ImageDownloader d;
            //根据网络情况去获取不同的ImageDownloader
        if (engine.isNetworkDenied()) {
            d = networkDeniedDownloader;
        } else if (engine.isSlowNetwork()) {
            d = slowNetworkDownloader;
        } else {
                    //这里是BaseImageDownloader
            d = downloader;
        }
        return d;
    }
    

    咋们知道这么回事就行了,有兴趣的去看看他们是如何获取inputstream。接着看tryCacheImageOnDisk方法中用调用了resizeAndSaveImage方法:

    private boolean resizeAndSaveImage(int maxWidth, int maxHeight) throws IOException {
        // Decode image file, compress and re-save it
        boolean saved = false;
    //获取目标file文件
        File targetFile = configuration.diskCache.get(uri);
        if (targetFile != null && targetFile.exists()) {
            ImageSize targetImageSize = new ImageSize(maxWidth, maxHeight);
            DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options)
                    .imageScaleType(ImageScaleType.IN_SAMPLE_INT).build();
            ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey,
                    Scheme.FILE.wrap(targetFile.getAbsolutePath()), uri, targetImageSize, ViewScaleType.FIT_INSIDE,
                    getDownloader(), specialOptions);
    //decode操作,将Scheme为FILE类型的inputstream转化成bitmap对象
            Bitmap bmp = decoder.decode(decodingInfo);
            if (bmp != null && configuration.processorForDiskCache != null) {
                L.d(LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISK, memoryCacheKey);
                bmp = configuration.processorForDiskCache.process(bmp);
                if (bmp == null) {
                    L.e(ERROR_PROCESSOR_FOR_DISK_CACHE_NULL, memoryCacheKey);
                }
            }
            if (bmp != null) {
    //这里才是真正保存bitmap到disk中的操作
                saved = configuration.diskCache.save(uri, bmp);
                bmp.recycle();
            }
        }
        return saved;
    }
    

    上面代码在保存到disk中,先是判断有没有下载到,如果有下载先进行保存inputstream中的内容,在进行保存的时候,通过copy的方式来保存到OutputStream中,这里在copy的时候,如果当前保存少于CONTINUE_LOADING_PERCENTAGE=75的时候,视为失败。这里追踪代码到LruDiskCachesave方法,其中save方法传入的是InputStream对象。最后通过targetFile文件进行decode操作获取到bitmap对象。最后又进行保存bitmap对象到LruDiskCache中。

    这里我提个疑问哈,为什么在保存到disk中,为什么先去save传了一个inputstream,紧接着又去save传了一个bitmap

    至此,代码分析得差不多了,如果有什么疑问,我们可以共同讨论,或者加群(184793647)

    相关文章

      网友评论

        本文标题:面试之——Android-Universal-Image-Loa

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