UniversalImageLoader:
ImageDownloader接口:
- 定义了getStream(…)接口, 这个抽象屏蔽了所有image来源以及如何提取的细节, 统一为一个返回InputStream的接口.
- 以enum形式定义了支持的几种Scheme, 还集成了剥离url scheme的方法.
BaseImageDownloader implements ImageDownloader:
- 根据Scheme的类型调用具体的getStreamFromXXX(…)方法:
- getStreamFromNetwork(…), 使用了HttpURLConnection作为从网络获取信息的手段, 并且加了一个while循环来检测重定向以及一个最大重定向次数的检测. 在返回了非3XX的status code的情况下,只有200才是唯一认定可以继续进行处理的status code, 最后返回的inputStream会在HttpURLConnection getStream()的到的stream的基础上进行BufferedInputStream + ContentLengthInputStream的封装构造.
- getStreamFromFile(…), 会从给定的imageUri中的到真正的file的路径, 并且要对file的类型进行判断: isVideoFileUri(…)(通过webkit的MimeTypeMap类来得到uri的扩展名从而的到对应的MIME type, 如果是以”video/”开头的就认为是video),
- 如果是video, 那么显然不能是直接作为image使用的, 会尝试生成video的thumbnail, 并转化为Byte Stream.
- 否则,直接打开一个FileInputStream + BufferedInputStream的组合就可以, 当然,最后还会套一层ContentLengthInputStream.
- 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).
- getStreamFromAssets(…): 该uri指向的文件是在Asset中, 得到对应的path以后直接context.getAssets().open(filePath).
- getStreamFromDrawable(…): 从drawable中提取出inputStream, 直接调用context.getResources().openRawResource(drawableId).
- 根据Scheme的类型调用具体的getStreamFromXXX(…)方法:
从上面对资源提取的过程看, Downloader的运行应该是在非UI线程中, 因为这些函数的调用都是直接同步调用IO的.
因为涉及到很多的配置项, 因此专门有一个ImageLoaderConfiguration对设置项进行了封装, 而ImageLoaderConfiguration实例的生成则是使用了builder模式. 其构造函数是private的, 构造函数中从builder中获取信息并赋给自己相应的field, 只有networkDeniedDownloader/slowNetworkDownloader这两个Downloader是基于builder的downloader进行构造的. Builder本身也很简单,一系列的类setXXX方法用于设定各项设置,最后一个build() new出一个根据当前配置信息生成的DisplayImageOptions.
ImageLoader, 单例类, 为了适应多线程的构造和初始化, static方法基本都会同步. 其init()函数也很简单, 如果用户没有提供一个自己的 ImageLoaderConfiguration, 那么被认定为非法抛异常. 否则如果发现此时的ImageLoader单例对象确实还没有非空的ImageLoaderConfiguration, 那么就会new一个ImageLoaderEngine并将此输入的configuration保存起来. 如果已经有了有效的configuration, 那么就直接忽略.
ImageLoader有一些和Activity生命周期对应的函数: pause()/resume()/stop()/ destroy(), 前三者会开始/停止 ImageLoaderEngine, 后者除了会停止engine外, 还会将diskCache清空, 将engine和configuration 引用设空来释放资源.
除此之外的ImageLoader提供的就是dispalyImage(…)及loadImage(…)的操作了. 两者的区别很简单:
- dispalyImage(…)会要求指定一个ImageAware接口对象,在image被load以后,会被此对象进行显示(display).
- loadImage(…)则会指定一个listener, 在image被load以后, listener的实现者自己决定如何处理
- 可以认为前者是后者的一个具体情况, 并且在dispalyImage(…)也有额外使用listener的形式.
- dispalyImage(…)虽然不同参数类型的重载很多,但是具体实在只在一个函数里,其处理逻辑是:
- 首先检查是否有可用的configuration, 如果没有,则抛异常.
- 如果没有指定imageAware, 也会抛一个异常.
- 如果没有指定listener,那么会挂接一个默认的defaultListener.
- 如果DisplayImageOptions没有给出, 那么会使用configuration设定的defaultDisplayImageOptions.
- 检查要load的url是否为空, 如果为空,那么首先cancel之前调度上去的会设置此imageAware的任务(因为有了最新的url了), 在这里,并不把空的url视为一种非法的值, 而是当作一个正常的不需要额外加载的url看待,因此会调用listener的onLoadingStarted(…), 如果DisplayImageOptions中为空Uri指定了要显示的imahe,这时候就会将imageAware设置为指定的image, 否则调用imageAware.setImageDrawable(null)由实现者自己处理. 最后调用listener的onLoadingComplete(…)来作为对空url加载完毕的回调,return.
- 为了便于进行cache, 会对这次要显示的imageAware的尺寸进行获取, ImageSizeUtils.defineTargetSizeForView(…). 根据uri和生成的ImageSize的到一个memoryCacheKey, MemoryCacheUtils.generateKey(uri, targetSize).
- 将imageAware和cacheKey作为参数传入engine的prepareDisplayTaskFor(…)函数中. 这一步会将imageAware和cacheKey关联起来. 将此pair保存在ImageLoaderEngine的cacheKeysForImageAwares中.
- 调用listener的onLoadingStarted()..
- 利用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的判断.
loadImage(…)的内部实现其实还是调用的displayImage(…), 只不过因为不需要imageAware, 所以专门搞了一个NonViewAware(…)做为占位符来避免抛出异常.
ImageLoaderEngine: 真正干活的, 因为一定是异步的run被add的Task, 所以内部搞了三个Executor:
- taskExecutor
- taskExecutorForCachedImages
- taskDistributor
UniversalImageLoader 源码笔记(1)
最新推荐文章于 2024-05-03 02:00:04 发布