Android-Universal-Image-Loader简单分析-Core部分

接着Android-Universal-Image-Loader简单分析-Cache部分, 继续看第二部分Core部分:

下图是Core文件结构:


文件夹部分:

decode  主要负责获取不同类型的图片流,转换成bitmap

display 主要工作就是把转换成的bitmap按照指定的特性显示出来。

download decode就是通过download 来获取不同的图片流。

imageaware 主要软应用一个ImageView,保存获取imageView各种属性。

listener 主要负责图片下载状态(完成,失败,取消等),图片下载进度, 列表快速滑动处理,辅助完成UI界面的显示。

process  主要是给用户来对图片做个性化需求设计的接口。

主要类:

DefaultConfigurationFactory 主要是创建线程池执行器,文件名生成策略,磁盘缓存策略,内存缓存策略,图片下载器,解码,显示方式等等。

DisplayBitmapTask 一个Runnable被发送到主线程消息队列里面执行,通知图片下载取消或者下载完成的工作。

类 ImageLoader 主要是协调各个功能类协同工作实现图片下载控制。

类 ImageLoaderEngine 如其名是一个引擎,承担提交下载任务的核心工作,下载任务暂停控制(比如:list快速滑动任务暂停监听),处理网络缓慢或者没有网络情况,以及ImageView被重复多次加载控制(比如上下滑动listview, 一个item就会被触发至少两次加载图片操作。)

类 DisplayImageOptions  图片显示选项配置 在ImageDecodingInfo构造函数中被转换成BitmapFactory.Option 来实现控制图片。

ImageLoaderConfiguration 主要是配置DefaultConfigurationFactory  在创建线程池以及各种策略中所需的参数,比如说,磁盘线程池 常驻线程个数,最大线程个数,内存缓存和磁盘缓存大小等等。

ImageLoadingInfo 这个是图片下载数据类,包含图片基本信息ImageAware,图片显示保存配置DisplayImageOptions 和一些监听等等。

LoadAndDisplayImageTask  Runnable子类,线程池里面执行的一个执行体,包含个各类协作完成图片下载,通知保存等等。

ProcessAndDisplayImageTask 在bitmap已经被缓存,在要显示前,用户可以自定义自己的bitmap显示前处理的功能。
各个类功能介绍基本完了,刀已近锋利了,接下来就砍材吧!

github上关于ImageLoader 和使用在分析一下ImageLoader的初始化和使用流程。

第一步:初始化ImageLoader流程:

3. Application or Activity class (before the first usage of ImageLoader)

public class MyActivity extends Activity {

    @Override

    public void onCreate() {

        super.onCreate();

        // Create globalconfiguration and initialize ImageLoader with this config

        ImageLoaderConfigurationconfig = new ImageLoaderConfiguration.Builder(this)

            ...

            .build();

       ImageLoader.getInstance().init(config);

        ...

    }

}

先通过ImageLoaderConfiguration.Builder构造ImageLoaderConfiguration

public ImageLoaderConfiguration build(){

   initEmptyFieldsWithDefaultValues();

    return newImageLoaderConfiguration(this);

}

build函数首先调用initEmptyFieldsWithDefaultValues()初始化默认值。

private void initEmptyFieldsWithDefaultValues(){

  if (taskExecutor == null) {

    taskExecutor =DefaultConfigurationFactory.createExecutor(threadPoolSize, threadPriority,tasksProcessingType);

  } else {

    customExecutor = true;

  }

//第一步:创建线程池用来执行LoadAndDisplayImageTask的任务。

  if (taskExecutorForCachedImages ==null) {

    taskExecutorForCachedImages =DefaultConfigurationFactory.createExecutor(threadPoolSize, threadPriority,tasksProcessingType);

  } else {

    customExecutorForCachedImages =true;

  }

//第二步:创建线程池用来执行ProcessAndDisplayImageTask进行图片自定义处理。

if (diskCache == null) {

  if (diskCacheFileNameGenerator ==null) {

    diskCacheFileNameGenerator =DefaultConfigurationFactory.createFileNameGenerator();

  }

    diskCache =DefaultConfigurationFactory.createDiskCache(context,diskCacheFileNameGenerator, diskCacheSize, diskCacheFileCount);

}

//第三步:创建磁盘缓存。

if (memoryCache == null) {

  memoryCache =DefaultConfigurationFactory.createMemoryCache(memoryCacheSize);

}

if (denyCacheImageMultipleSizesInMemory) {

  memoryCache = newFuzzyKeyMemoryCache(memoryCache, MemoryCacheUtils.createFuzzyKeyComparator());

}

//第四步:创建内存缓存。默认情况下denyCacheImageMultipleSizesInMemoryfalse 内存缓存不会使用FuzzyKeyMemoryCache策略。

FuzzyKeyMemoryCache策略是相同keyvaluebitmap保存前,会被先移除。

if (downloader == null) {

  downloader =DefaultConfigurationFactory.createImageDownloader(context);

}

//第五步:创建图片下载实例这里是BaseImageDownloader 对象

if (decoder == null) {

  decoder =DefaultConfigurationFactory.createImageDecoder(writeLogs);

}

//第六步:创建图片Decoder实例这里是 BaseImageDecoder 对象他会根据BaseImageDownloader 拿到的图片流,在根据ImageLoadingInfo里面DisplayImageOptions 的配置信息decoder想要的bitmap对象。

if (defaultDisplayImageOptions == null) {

  defaultDisplayImageOptions =DisplayImageOptions.createSimple();

}

//第七步:创建默认的DisplayImageOptions 对象。

}

 

build函数然后调用ImageLoaderConfiguration(this);创建ImageLoaderConfiguration对象。

这个里面就是一个赋值的过程了。

值得一说的是,在ImageLoaderConfiguration构造函数里面还会构造这样两个对象。

                                networkDeniedDownloader= new NetworkDeniedImageDownloader(downloader);

                                slowNetworkDownloader= new SlowNetworkImageDownloader(downloader);

networkDeniedDownloader  这个是在网络关闭的时候,如果用户发送获取网络请求直接返回异常处理。

slowNetworkDownloader   这个是在网络慢的时候对图片流用FilterInputStream做了一下封装。

当然我们也可以根据自己的情况配置自己的ImageLoader下面应用github一段代码。

Configuration

All options in Configuration builder are optional. Use only those youreally want to customize.

See default values for config options in Java docs for every option.

// DON'T COPY THIS CODE TO YOUR PROJECT! This is just example of ALLoptions using.

// See the sample project how to use ImageLoader correctly.

File cacheDir = StorageUtils.getCacheDirectory(context);

ImageLoaderConfiguration config = newImageLoaderConfiguration.Builder(context)

       .memoryCacheExtraOptions(480, 800) // default = device screen dimensions

                               //配置内存缓存图片宽高

        .diskCacheExtraOptions(480,800, null)

                               //配置磁盘缓存图片宽高

        .taskExecutor(...)

       .taskExecutorForCachedImages(...)

//如果这里初始化了,在前面第一步和第二部中就不会创建的线程池

        .threadPoolSize(3) //default

       .threadPriority(Thread.NORM_PRIORITY - 2) // default

       .tasksProcessingOrder(QueueProcessingType.FIFO) // default

//线程池中一些设置

       .denyCacheImageMultipleSizesInMemory()

//前面第四步中有提到denyCacheImageMultipleSizesInMemory

        .memoryCache(newLruMemoryCache(2 * 1024 * 1024))//指定缓存策略

        .memoryCacheSize(2 * 1024 *1024)  //指定大小

       .memoryCacheSizePercentage(13) // default//根据当前内存最大值得百分比来确定内存缓存大小。

//内存缓存策略以及一些参数

        .diskCache(newUnlimitedDiscCache(cacheDir)) // default/指定缓存策略也是默认值

        .diskCacheSize(50 * 1024 *1024)             //指定大小

        .diskCacheFileCount(100)        //指定文件个数

       .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) //default//指定缓存文件生成策略。

//磁盘缓存策略以及一些参数

        .imageDownloader(newBaseImageDownloader(context)) // default //图片下载类

        .imageDecoder(newBaseImageDecoder()) // default     //图片解码类

       .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) //default //图片默认显示选项。

        .writeDebugLogs()

        .build();

好了,配置初始化完成之后,会吧配置对象保存到 ImageLoader中。

ImageLoader.getInstance().init(config);

 

到这里也配置完了,万事俱备,开始显示图片了:继续从github摘录一段code开始分析。

// Load image, decode it to Bitmap and display Bitmap in ImageView (or anyother view

//  which implements ImageAwareinterface)

imageLoader.displayImage(imageUri, imageView);

displayImage函数如下:

public void displayImage(String uri, ImageAware imageAware,DisplayImageOptions options,

        ImageLoadingListenerlistener, ImageLoadingProgressListener progressListener) {

    checkConfiguration();

    if (imageAware == null) {

      throw newIllegalArgumentException(ERROR_WRONG_ARGUMENTS);

                                }

                                if(listener == null) {

                                   listener= emptyListener;

                                }

                                if(options == null) {

                                   options= configuration.defaultDisplayImageOptions;

                                }

//检查我们前面的是否初始化配置以及空判断检查。

                                if(TextUtils.isEmpty(uri)) {

                                   engine.cancelDisplayTaskFor(imageAware);

                                   listener.onLoadingStarted(uri,imageAware.getWrappedView());

                                   if(options.shouldShowImageForEmptyUri()) {

                                               imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));

                                   } else {

                                       imageAware.setImageDrawable(null);

                                   }

                                   listener.onLoadingComplete(uri,imageAware.getWrappedView(), null);

                                   return;

                                }

//显示第一步:url为空处理。

                                ImageSizetargetSize = ImageSizeUtils.defineTargetSizeForView(imageAware,configuration.getMaxImageSize());

                                StringmemoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);

                                engine.prepareDisplayTaskFor(imageAware,memoryCacheKey);

 

                                listener.onLoadingStarted(uri,imageAware.getWrappedView());

 

                                Bitmapbmp = configuration.memoryCache.get(memoryCacheKey);

                                if(bmp != null && !bmp.isRecycled()) {

                                   L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE,memoryCacheKey);

 

                                   if(options.shouldPostProcess()) {

                                       ImageLoadingInfoimageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize,memoryCacheKey,

                                              options,listener, progressListener, engine.getLockForUri(uri));

                                       ProcessAndDisplayImageTaskdisplayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,

                                              defineHandler(options));

                                       if(options.isSyncLoading()) {

                                          displayTask.run();

                                       }else {

                                          engine.submit(displayTask);

                                       }

                                   }else {

                                       options.getDisplayer().display(bmp,imageAware, LoadedFrom.MEMORY_CACHE);

                                       listener.onLoadingComplete(uri,imageAware.getWrappedView(), bmp);

                                   }

//显示第二步:要加载的图片就在内存缓存中的处理逻辑。

                                }else {

                                   if(options.shouldShowImageOnLoading()) {

                                                 imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));

                                   }else if (options.isResetViewBeforeLoading()) {

                                       imageAware.setImageDrawable(null);

                                   }

 

                                   ImageLoadingInfoimageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize,memoryCacheKey,

                                          options,listener, progressListener, engine.getLockForUri(uri));

                                   LoadAndDisplayImageTaskdisplayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,

                                          defineHandler(options));

                                   if(options.isSyncLoading()) {

                                       displayTask.run();

                                   }else {

                                       engine.submit(displayTask);

                                   }

                                }

//显示第三步:从网络加载图片以及显示处理。

                               }

四个参数分别是,loading路径,图片信息封装了一个图片软引用,图片显示配置项,图片装载状态监听,图片装载进度监听。

接下来分三步分析这个函数。

第一步:Url为空处理。

调用 engine.cancelDisplayTaskFor 把当前的Imageview 对应的memorykeycacheKeysForImageAwares这个hashmap中删除掉。

cacheKeysForImageAwares这个是干啥的呢?

                               privateboolean isViewWasReused() {

                                StringcurrentCacheKey = engine.getLoadingUriForView(imageAware);

                                return!memoryCacheKey.equals(currentCacheKey);

                               }

这个函数式判断是否ImageView是否被重用,就是根据cacheKeysForImageAwares来判断的。如何实现的呢?

每次在移动一个Task开始记载图片的时候,就会把imageView 对应生成的memorykey保存到cacheKeysForImageAwares里面。(下面就能看到这个操作)

Listview 在滑动的时候item是会被重用的,当你上下滑动的时候,是不是同一个ImageView就被要求加载了两次或者多次,所以就会启动多个Task来装载图片。

如是就通过isViewWasReused这个函数判断当前task 要加载的imageviewmemorykey 要加载的imageViewcacheKeysForImageAwares中最新的memorykey是否一致。

如果不一致,表示当前task下载任务可以取消了。

继续往下,就是通过ImageLoadingListener 通知开始装载,在根据显示选项options 判断是否显示URL为空状态预设图片。

最后通过ImageLoadingListener  通知装载完成。

 

第二步:要加载的图片就在内存缓存中的处理逻辑。

  ImageSize targetSize =ImageSizeUtils.defineTargetSizeForView(imageAware,configuration.getMaxImageSize());

  String memoryCacheKey =MemoryCacheUtils.generateKey(uri, targetSize);

 engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

  listener.onLoadingStarted(uri,imageAware.getWrappedView());

先算出要加载图片的大小,然后生成图片的memorykey, 在把要加载图片ImageView memorykey 作为键值对存入cacheKeysForImageAwares(这个的作用上面已经分析过了)

在调用listener.onLoadingStarted通知准备开始下载。

Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);

从内存缓存中获取对应memorykeybitmap,接下来判断bitmap是否在缓存中,同时没有被recycled掉。

如果判断为真,则继续看看显示配置中,有没有用户自定义显示前对bitmap做处理的类。

如果有就创建一个ProcessAndDisplayImageTask runnable对象,再判断是否是option配置中是否是同步处理图片。

如果是同步则直接调用ProcessAndDisplayImageTask 对象run函数。如果不是这把runnable对象提交到taskExecutorForCachedImages 线程池异步处理。

taskExecutorForCachedImages (线程池在我们配置过程中已经创建)

回到前面显示配置中,有没有用户自定义显示前对bitmap做处理类的第二种情况

  options.getDisplayer().display(bmp,imageAware, LoadedFrom.MEMORY_CACHE);

  listener.onLoadingComplete(uri,imageAware.getWrappedView(), bmp);

如果没有,则直接调用显示接口,根据预设图片显示策略来设置图片(主要策略有FadeInBitmapDisplayerRoundedBitmapDisplayerSimpleBitmapDisplayer

当然用户也可以自定义更多的显示方式,最后通过listener通知装载完成。

 

第三步:从网络加载图片以及显示处理。

    首先判断一下图片是否需要设置装载中的图片的显示状态。

创建一个ImageLoadingInfo 对象,这个对象构造函数中的参数前面大多数都有介绍,但是有一个比较陌生的 engine.getLockForUri(uri)

这个函数的作用是为每个 uri创建一个 ReentrantLock对象。同时保存在ImageLoaderEngine uriLocks 中,

之所以每一个uri对应一个,是为了防止多个imageView同时都从网络加载同一张图片的情况。它可以让其中一个task从网络下载图片,让后让后来的task等待直到第一个task下载完成

然后别的task就可以从内存中区获取该图片的bigmap.

接着创建一个 LoadAndDisplayImageTaskrunnable对象从网络去加载图片。

下面分析LoadAndDisplayImageTask.run函数我会省略重复代码的部分。

@Override

public void run() {

if (waitIfPaused()) return;

if (delayIfNeed()) return;

第一个if语句是在listView高速滑动的时候,或者用户希望暂停所有下载的情况下:设置AtomicBoolean 变量pausedtrue

然后下载的task们会获取这个变量的值,如果是true,就进入获取Object pauseLock对象进入等待状态。直到pauseLock.notificationall

第二个if语句就是检查延迟装载图片。

ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;

loadFromUriLock.lock();

这个锁就是之前ImageLoadingInfo 构造函数中传入的。就是防止同一张图片被同时从网络上加载。

  checkTaskNotActual();

上面的检查分两步,一步是检查当前要显示的imageView是否已经被回收,

第二步是检查是否是一个imageView被重复使用多次加载图片。在Url为空处理的时候分析过了。

 

  bmp =configuration.memoryCache.get(memoryCacheKey);

  if (bmp == null ||bmp.isRecycled()) {

    bmp = tryLoadBitmap();

开始下载图片。

checkTaskInterrupted()

检查线程是否中断。

  if (bmp != null &&options.isCacheInMemory()) {

   configuration.memoryCache.put(memoryCacheKey, bmp);

这个就是把下载的bitmap保存到内存缓存中。

  } finally {

    loadFromUriLock.unlock();

uri对应image下载完毕释放锁

  }

DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp,imageLoadingInfo, engine, loadedFrom);

runTask(displayBitmapTask, syncLoading, handler, engine);

显示图片。

可以看出run函数除了犀利的同步控制之外重要的就是tryLoadBitmap()函数了。

private Bitmap tryLoadBitmap()

    File imageFile =configuration.diskCache.get(uri);

    if (imageFile != null &&imageFile.exists()) {

        loadedFrom =LoadedFrom.DISC_CACHE;

        bitmap =decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));

    }

    if (bitmap == null ||bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {

        loadedFrom =LoadedFrom.NETWORK;

        String imageUriForDecoding =uri;

        if (options.isCacheOnDisk()&& tryCacheImageOnDisk()) {

            imageFile = configuration.diskCache.get(uri);

            if (imageFile != null) {

                imageUriForDecoding= Scheme.FILE.wrap(imageFile.getAbsolutePath());

            }

        }

        bitmap =decodeImage(imageUriForDecoding);

    }

精简了一下看着舒服多了。

其实这个函数主要还是分两步,

第一步:从磁盘缓存中获取图片,如果存在着调用BaseImageDecoder decode函数然后根据diplayOption选项配置decode出合适的bitmap.

第二步:判断是否缓存到磁盘中,如果是调用 tryCacheImageOnDisk从网络获取图片,缓存到磁盘缓存中。

如果不是,着直接调用decodeImage,他会调用BaseImageDownloader 从网络获取图片流,然后调用BaseImageDecoder decode函数获得bitmap

好吧,就这样吧,还有很多逻辑,大家细细去品味吧。

很少写这样的文章,因为觉得很简单,现在发现看懂了和表达出来,这个中间还是有很大的差距的。因为我一直想写的简单明了,最好还是写的邋遢没有感觉。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值