UniversalImageLoader 源码笔记(1)

  1. UniversalImageLoader:

  2. ImageDownloader接口:

    • 定义了getStream(…)接口, 这个抽象屏蔽了所有image来源以及如何提取的细节, 统一为一个返回InputStream的接口.
    • 以enum形式定义了支持的几种Scheme, 还集成了剥离url scheme的方法.
  3. BaseImageDownloader implements ImageDownloader:

    • 根据Scheme的类型调用具体的getStreamFromXXX(…)方法:
      1. getStreamFromNetwork(…), 使用了HttpURLConnection作为从网络获取信息的手段, 并且加了一个while循环来检测重定向以及一个最大重定向次数的检测. 在返回了非3XX的status code的情况下,只有200才是唯一认定可以继续进行处理的status code, 最后返回的inputStream会在HttpURLConnection getStream()的到的stream的基础上进行BufferedInputStream + ContentLengthInputStream的封装构造.
      2. getStreamFromFile(…), 会从给定的imageUri中的到真正的file的路径, 并且要对file的类型进行判断: isVideoFileUri(…)(通过webkit的MimeTypeMap类来得到uri的扩展名从而的到对应的MIME type, 如果是以”video/”开头的就认为是video),
        • 如果是video, 那么显然不能是直接作为image使用的, 会尝试生成video的thumbnail, 并转化为Byte Stream.
        • 否则,直接打开一个FileInputStream + BufferedInputStream的组合就可以, 当然,最后还会套一层ContentLengthInputStream.
      3. getStreamFromContent(…): 因为是为了从context中的到资源,那么显然需要一个ContentResolver来解析Uri. 这里同样要进行是否是video的判断(使用context.getContentResolver().getType(uri)的到Uri指向的资源的MIME类型):
        • 在video的情况下,会直接使用MediaStore.Video.Thumbnails.getThumbnail(res, origId, MediaStore.Images.Thumbnails.MINI_KIND, null)来的到相应video的thumbnail的bitmap. 在得到bitmap以后, 会使用compress(CompressFormat.PNG, 0, bos(ByteArrayOutputStream))将bitmap转换为一个byte stream.
        • 否则如果发现uri指向的是联系人照片(“content://com.android.contacts/”), 那么直接调用ContactsContract.Contacts.openContactPhotoInputStream(res, uri)获得inputStream.
        • 否则直接调用ContentResolver的openInputStream(uri).
      4. getStreamFromAssets(…): 该uri指向的文件是在Asset中, 得到对应的path以后直接context.getAssets().open(filePath).
      5. getStreamFromDrawable(…): 从drawable中提取出inputStream, 直接调用context.getResources().openRawResource(drawableId).
  4. 从上面对资源提取的过程看, Downloader的运行应该是在非UI线程中, 因为这些函数的调用都是直接同步调用IO的.

  5. 因为涉及到很多的配置项, 因此专门有一个ImageLoaderConfiguration对设置项进行了封装, 而ImageLoaderConfiguration实例的生成则是使用了builder模式. 其构造函数是private的, 构造函数中从builder中获取信息并赋给自己相应的field, 只有networkDeniedDownloader/slowNetworkDownloader这两个Downloader是基于builder的downloader进行构造的. Builder本身也很简单,一系列的类setXXX方法用于设定各项设置,最后一个build() new出一个根据当前配置信息生成的DisplayImageOptions.

  6. ImageLoader, 单例类, 为了适应多线程的构造和初始化, static方法基本都会同步. 其init()函数也很简单, 如果用户没有提供一个自己的 ImageLoaderConfiguration, 那么被认定为非法抛异常. 否则如果发现此时的ImageLoader单例对象确实还没有非空的ImageLoaderConfiguration, 那么就会new一个ImageLoaderEngine并将此输入的configuration保存起来. 如果已经有了有效的configuration, 那么就直接忽略.

  7. ImageLoader有一些和Activity生命周期对应的函数: pause()/resume()/stop()/ destroy(), 前三者会开始/停止 ImageLoaderEngine, 后者除了会停止engine外, 还会将diskCache清空, 将engine和configuration 引用设空来释放资源.

  8. 除此之外的ImageLoader提供的就是dispalyImage(…)及loadImage(…)的操作了. 两者的区别很简单:

    • dispalyImage(…)会要求指定一个ImageAware接口对象,在image被load以后,会被此对象进行显示(display).
    • loadImage(…)则会指定一个listener, 在image被load以后, listener的实现者自己决定如何处理
    • 可以认为前者是后者的一个具体情况, 并且在dispalyImage(…)也有额外使用listener的形式.
    • dispalyImage(…)虽然不同参数类型的重载很多,但是具体实在只在一个函数里,其处理逻辑是:
      1. 首先检查是否有可用的configuration, 如果没有,则抛异常.
      2. 如果没有指定imageAware, 也会抛一个异常.
      3. 如果没有指定listener,那么会挂接一个默认的defaultListener.
      4. 如果DisplayImageOptions没有给出, 那么会使用configuration设定的defaultDisplayImageOptions.
      5. 检查要load的url是否为空, 如果为空,那么首先cancel之前调度上去的会设置此imageAware的任务(因为有了最新的url了), 在这里,并不把空的url视为一种非法的值, 而是当作一个正常的不需要额外加载的url看待,因此会调用listener的onLoadingStarted(…), 如果DisplayImageOptions中为空Uri指定了要显示的imahe,这时候就会将imageAware设置为指定的image, 否则调用imageAware.setImageDrawable(null)由实现者自己处理. 最后调用listener的onLoadingComplete(…)来作为对空url加载完毕的回调,return.
      6. 为了便于进行cache, 会对这次要显示的imageAware的尺寸进行获取, ImageSizeUtils.defineTargetSizeForView(…). 根据uri和生成的ImageSize的到一个memoryCacheKey, MemoryCacheUtils.generateKey(uri, targetSize).
      7. 将imageAware和cacheKey作为参数传入engine的prepareDisplayTaskFor(…)函数中. 这一步会将imageAware和cacheKey关联起来. 将此pair保存在ImageLoaderEngine的cacheKeysForImageAwares中.
      8. 调用listener的onLoadingStarted()..
      9. 利用cacheKey从configuration指定的memCache中尝试获取bitmap:
        • 如果找到了可用的没有被recycle的bitmap, 那么可以尝试使用此cache, 如果options里指定了还要做后期处理, 那么就生成一个ImageLoadingInfo, 以此new一个ProcessAndDisplayImageTask(Runnable)来进行对cacheBitmap的处理和显示, 如果option中指定了要syncLoading, 那么直接run此Task即可, 否则将此Task submit到engine中异步执行(之所以异步的原因是Process可能也会是一个耗时的操作). 如果不要求后期处理,那么就可以直接display了, 然后调用listener的onLoadingComplete.
        • 如果cache中没有可用的, 那么就老老实实的放在engine中进行load吧, 这时候可以认为是loading了,可以选择性的设置loading中的要显示的img, 将相关信息封装为一个ImageLoadingInfo并以此构造一个LoadAndDisplayImageTask(Runnable), 还是会做sync的判断.
  9. loadImage(…)的内部实现其实还是调用的displayImage(…), 只不过因为不需要imageAware, 所以专门搞了一个NonViewAware(…)做为占位符来避免抛出异常.

  10. ImageLoaderEngine: 真正干活的, 因为一定是异步的run被add的Task, 所以内部搞了三个Executor:

    1. taskExecutor
    2. taskExecutorForCachedImages
    3. taskDistributor
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值