从设计到实现,一步步教你实现Android-Universal-ImageLoader-解码与显示

转载请标明出处,本文出自:chaossss的博客


Android-Universal-ImageLoader Github 地址



在上一篇博文中我给大家剖析了 Android-Universal-ImageLoader 中缓存功能的设计和实现,希望大家可以在里面学到一丢丢东西哈。今天呢,我将接着向下讲解,介绍 AUI 核心类中的图片解码与显示功能,如果大家没有看过上一篇博文的话,可以戳我进去看哈,废话不多说,下面进入正题:

图片解码

在考虑具体实现之前,我们不妨先想想一个解码器的职责是什么?我相信答案大家都能脱口而出:解码。没错,解码器的核心功能在于解码,换言之,我们在设计 Decoder 类的时候,就应该遵循单一职责原则:

public interface ImageDecoder {

    Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;
}

小伙伴们看到 decode() 方法里的 ImageDecodingInfo 参数可能会很困惑,这是干什么的啊?我们不是只要解码图片么。大家别急,我们先在脑海里模拟一整个图片解码的流程吧:

要解码图片,我们得获得图片的“来源”,图片可能来自网络(那我们就得调用下载模块去下载),图片可能来自本地的文件(那我们就得通过 IO 流去读取)。在获得图片的来源之后,我们就要开始解码了,我们进行解码肯定不只有一种方案,有时候可能能显示出来就行了,有时候则要图片高清不失真地显示出来,又或者我们需要缩放/放大图片,又甚至是需要定制图片的长和宽,这就意味着我们需要一个解码辅助类存储这些解码所需的信息,以便于解码操作的完成。

所以大家现在应该能领会 ImageDecodingInfo 的作用了吧?我们一起来看看它的源码吧:

public class ImageDecodingInfo {
    private final String imageKey;
    private final String imageUri;
    private final String originalImageUri;
    private final ImageSize targetSize;

    private final ImageScaleType imageScaleType;
    private final ViewScaleType viewScaleType;

    private final ImageDownloader downloader;
    private final Object extraForDownloader;

    private final boolean considerExifParams;
    private final Options decodingOptions;

    public ImageDecodingInfo(String imageKey, String imageUri, String originalImageUri, ImageSize targetSize, ViewScaleType viewScaleType,
                             ImageDownloader downloader, DisplayImageOptions displayOptions) {
        this.imageKey = imageKey;
        this.imageUri = imageUri;
        this.originalImageUri = originalImageUri;
        this.targetSize = targetSize;

        this.imageScaleType = displayOptions.getImageScaleType();
        this.viewScaleType = viewScaleType;

        this.downloader = downloader;
        this.extraForDownloader = displayOptions.getExtraForDownloader();

        considerExifParams = displayOptions.isConsiderExifParams();
        decodingOptions = new Options();
        copyOptions(displayOptions.getDecodingOptions(), decodingOptions);
    }

    private void copyOptions(Options srcOptions, Options destOptions) {
        destOptions.inDensity = srcOptions.inDensity;
        destOptions.inDither = srcOptions.inDither;
        destOptions.inInputShareable = srcOptions.inInputShareable;
        destOptions.inJustDecodeBounds = srcOptions.inJustDecodeBounds;
        destOptions.inPreferredConfig = srcOptions.inPreferredConfig;
        destOptions.inPurgeable = srcOptions.inPurgeable;
        destOptions.inSampleSize = srcOptions.inSampleSize;
        destOptions.inScaled = srcOptions.inScaled;
        destOptions.inScreenDensity = srcOptions.inScreenDensity;
        destOptions.inTargetDensity = srcOptions.inTargetDensity;
        destOptions.inTempStorage = srcOptions.inTempStorage;
        if (Build.VERSION.SDK_INT >= 10) copyOptions10(srcOptions, destOptions);
        if (Build.VERSION.SDK_INT >= 11) copyOptions11(srcOptions, destOptions);
    }

    //get方法略去
    …………

    @TargetApi(10)
    private void copyOptions10(Options srcOptions, Options destOptions) {
        destOptions.inPreferQualityOverSpeed = srcOptions.inPreferQualityOverSpeed;
    }

    @TargetApi(11)
    private void copyOptions11(Options srcOptions, Options destOptions) {
        destOptions.inBitmap = srcOptions.inBitmap;
        destOptions.inMutable = srcOptions.inMutable;
    }
}

BaseImageDecoder

我们在获得了图片解码器的基类 ImageDecoder 后,就得完成我们的具体实现了。那么我们现在就得想想,基于 ImageDecoder 的抽象:decode() 方法,我们会衍生出哪些 ImageDecoder 的实现细节。为了完成图片解码操作,我们会按照下面的步骤实现:

获得图片的“来源” —> 获得图片输入流并判断图片的实际大小、是否需要被裁减以及是否需要旋转 —> 获得图片 —> 根据图片信息以及解码设置处理图片 —> 得到符合要求的图片

既然实现思路已经定下来了,那具体实现肯定也不难拉:

    @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);
            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;
    }

代码我就不全放出来了,大家可以根据刚刚的思路顺着这里的实现进入相应的方法看就行了。而 ExifInfo、ImageFileInfo 两个类只是解码过程需要的辅助类,提供处理图片需要的一些信息,例如:图片文件的大小、图片是否需要裁减、旋转:

protected static class ExifInfo {

    public final int rotation;
    public final boolean flipHorizontal;

    protected ExifInfo() {
        this.rotation = 0;
        this.flipHorizontal = false;
    }

    protected ExifInfo(int rotation, boolean flipHorizontal) {
        this.rotation = rotation;
        this.flipHorizontal = flipHorizontal;
    }
}

protected static class ImageFileInfo {

    public final ImageSize imageSize;
    public final ExifInfo exif;

    protected ImageFileInfo(ImageSize imageSize, ExifInfo exif) {
        this.imageSize = imageSize;
        this.exif = exif;
    }
}

图片显示

事实上大家会发现,图片显示和图片解码两个功能在实现上会很相似:抽象单一(图片显示/图片解码),具体实现只要根据相应的需求依据抽象实现细节就可以了。

public interface BitmapDisplayer {

    void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom);
}

我们显示图片的时候可能需要显示圆形/矩形/圆角图片,或者设置相应的图片动画等等等等……实际上我们只需要依据相应的图片形状、动画设置/实现相应的 Drawable 和 Animation 就可以了。我觉得这里没有啥好说的……但在这里引入了一个接口 ImageAware,我倒觉得值得我们注意一下:

public interface ImageAware {
    int getWidth();

    int getHeight();

    ViewScaleType getScaleType();

    View getWrappedView();

    boolean isCollected();

    int getId();

    boolean setImageDrawable(Drawable drawable);

    boolean setImageBitmap(Bitmap bitmap);
}

通过这个接口我们在图片显示类内获得显示图片的 View 的信息,还可以修改 View 显示的图片,图片的形状,以及显示图片的 ID。可能有人会说,然而这并没有什么卵用……但你仔细想想,真的是这样么?如果没有这个接口,我们要怎么在 Display 类里面直接让图片显示为圆形,抑或是添加相应的动画呢?要知道这些效果的最终实现都是应用到 View 上面的。

当然了,显示圆形图片和动画肯定能实现,例如自定义 View,或者是把 View 作为参数传入 Display,在 Display 类里对每一个显示图片的 View 进行处理或者是获得 View 的属性。AUI 中的做法也是值得参考的,因为它把这一块逻辑剥离,避免耦合。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-07 19:24:37

从设计到实现,一步步教你实现Android-Universal-ImageLoader-解码与显示的相关文章

一步步教你js原生瀑布流效果实现

一步步教你js原生瀑布流效果实现 什么是瀑布流效果 首先,让我们先看一段动画: 在动画中,我们不难发现,这个动画有以下特点: 1.所有的图片的宽度都是一样的 2.所有的图片的高度是不一样的 3.图片一张挨着一张竖直排列 4.鼠标向下滚动,一直不停的加载图片 5.浏览器的宽度改变,图片的列数会进行相应的更改 那么这种效果类似现实生活中的瀑布,所以我们叫它瀑布流的效果. Js原生瀑布流效果的实现 从上述分析中,我们可以把整个效果分为以下四个部分: html+css界面搭建 瀑布流效果 浏览器向下滚动

【java项目实战】一步步教你使用MyEclipse搭建java Web项目开发环境(一)

首先,在开始搭建MyEclipse的开发环境之前,还有三步工具的安装需要完成,只要在安装配置成功之后才可以进入下面的java Web项目开发环境的搭建. 1.安装工具 第一步,下载并安装JDK,到官网上下载安装即可,之后需要细心的配置环境变量,我给大家推荐百度文库的一篇文章,猛戳这里. 第二步,下载Tomcat,当然可以去Apache Tomcat的官网,同样,您可以移驾到我的资源下载,外送API文档(免资源分). 第三步,下载MyEclipse,MyEclipse官网,傻瓜式安装即可. ===

一步一步教你在 Android 里创建自己的账号系统(二)--同步数据以及设计账号页面

大家如果喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢 转载请标明出处(http://blog.csdn.net/kifile),再次感谢 在前一篇文章中(一步一步教你在 Android 里创建自己的账号系统(一)),我向大家介绍了如何在 Android 系统中创建自己的账户系统,接下来我会向大家详细介绍一下如何使用账户系统. (一)同步数据 通常而言,我们会在两种情况下使用我们的账号系统: (1)登陆验证 登陆验证其实是一个很实用的功能,试

教你写Android ImageLoader框架之图片加载与加载策略

在教你写Android ImageLoader框架之初始配置与请求调度中,我们已经讲述了ImageLoader的请求配置与调度相关的设计与实现.今天我们就来深入了解图片的具体加载过程以及加载的策略(包括按顺序加载和逆序加载) ,在这其中我会分享我的一些设计决策,也欢迎大家给我提建议. 图片的加载 Loader与LoaderManager的实现 在上一篇文章教你写Android ImageLoader框架之初始配置与请求调度中,我们聊到了Loader与LoaderManager. ImageLoa

教你写Android网络框架之Http请求的分发与执行

前言 在<教你写Android网络框架>专栏的前两篇博客中,我们已经介绍了SimpleNet框架的基本结构,以及Request.Response.请求队列的实现,以及为什么要这么设计,这么设计的考虑是什么.前两篇博客中已经介绍了各个角色,今天我们就来剖析另外几个特别重要的角色,即NetworkExecutor.HttpStack以及ResponseDelivery,它们分别对应的功能是网络请求线程.Http执行器.Response分发,这三者是执行http请求和处理Response的核心. 我

教你写Android网络框架之Request、Response类与请求队列

转载请注明出处,本文来自[ Mr.Simple的博客 ]. 我正在参加博客之星,点击这里投我一票吧,谢谢~ 前言 在教你写Android网络框架之基本架构一文中我们已经介绍了SimpleNet网络框架的基本结构,今天我们就开始从代码的角度来开始切入该网络框架的实现,在剖析的同时我们会分析设计思路,以及为什么要这样做,这样做的好处是什么.这样我们不仅学到了如何实现网络框架,也会学到设计一个通用的框架应该有哪些考虑,这就扩展到框架设计的范畴,通过这个简单的实例希望能给新人一些帮助.当然这只是一家之言

从设计到实现,一步步教你实现Android-Universal-ImageLoader-辅助类

通过前面几篇博文.我们分析了 AUI 的缓存.工具类.显示与载入这几个方面的代码.今天呢,我们继续研究 AUI 的源代码,学习当中的核心辅助工具类. 希望大家能在里面学到东西哈. Download 要下载一张图片,我们想象须要什么哈:首先我们得设定支持的协议.得有下载的链接.由对应的下载链接去下载图片.进而得到图片的输入流,剩下的就交给图片的显示/载入类处理啦. 那么我们能够设计出以下的接口: public interface ImageDownloader { InputStream getS

从设计到实现,一步步教你实现Android-Universal-ImageLoader-缓存

转载请标明出处,本文出自:chaossss的博客 Android-Universal-ImageLoader Github 地址 Cache 我们要对图片进行缓存,有两种方式:内存缓存和本地缓存.这两种方式的区别在于,内存缓存是缓存在 Android 系统为应用分配的运行内存之中,读取速度快,但是可能会带来 OOM 的问题:本地缓存一般缓存在 SD 卡中,读取速度较慢,但是缓存空间足. 那么我们要怎么来实现内存缓存和本地缓存呢?根据单一职责原则,如果 MemoryCache 和 DiskCach

从设计到实现,一步步教你实现Android-Universal-ImageLoader-工具类

转载请标明出处,本文出自:chaossss的博客 在上一篇博文中我们分析了 AUImgLoader 缓存模块的功能实现和架构设计,今天不妨着手分析 AUImgLoader 的工具类,为后面的分析作铺垫. 在 Utils 包中,有 AUImgLoader 可能用到的工具类.其中 DiskCacheUtils.StorageUtils.MemonryCacheUtils 我们在分析 AUImgLoader 缓存功能模块时已经讲解过了,今天就不再赘述.事实上,AUImgLoader 中作为工具被实现的