图像加载
一般来说一图片加载框架应该具有以下基本特性:
1、图片的同步/异步加载
2、图片缓存(内存缓存/磁盘缓存)
3、网络加载
4、图片处理(压缩、裁剪、左右变幻等)
基本使用
UIL
1、设置全局配置
ImageLoaderConfiguration config = new ImageLoaderConfiguration
.Builder(context)
.memoryCacheExtraOptions(480, 800) // max width, max height,即保存的每个缓存文件的最大长宽
.threadPoolSize(3)//线程池内加载的数量
.threadPriority(Thread.NORM_PRIORITY - 2)
.denyCacheImageMultipleSizesInMemory()
.memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024)) // You can pass your own memory cache implementation/你可以通过自己的内存缓存实现
.memoryCacheSize(2 * 1024 * 1024)
.discCacheSize(50 * 1024 * 1024)
.discCacheFileNameGenerator(new Md5FileNameGenerator())//将保存的时候的URI名称用MD5 加密
.tasksProcessingOrder(QueueProcessingType.LIFO)
.discCacheFileCount(100) //缓存的文件数量
.defaultDisplayImageOptions(DisplayImageOptions.createSimple())
.imageDownloader(new BaseImageDownloader(context, 5 * 1000, 30 * 1000)) // connectTimeout (5 s), readTimeout (30 s)超时时间
.writeDebugLogs() // Remove for release app
.build();//开始构建
ImageLoader.getInstance().init(config);
2、设置具体ImageView配置
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.ic_launcher) //设置图片在下载期间显示的图片
.showImageForEmptyUri(R.drawable.ic_launcher)//设置图片Uri为空或是错误的时候显示的图片
.showImageOnFail(R.drawable.ic_launcher) //设置图片加载/解码过程中错误时候显示的图片
.cacheInMemory(true)//设置下载的图片是否缓存在内存中
.cacheOnDisc(true)//设置下载的图片是否缓存在SD卡中
.considerExifParams(true) //是否考虑JPEG图像EXIF参数(旋转,翻转)
.imageScaleType(ImageScaleType.EXACTLY_STRETCHED)//设置图片以如何的编码方式显示
.bitmapConfig(Bitmap.Config.RGB_565)//设置图片的解码类型//
.decodingOptions(android.graphics.BitmapFactory.Options decodingOptions)//设置图片的解码配置
//.delayBeforeLoading(int delayInMillis)//int delayInMillis为你设置的下载前的延迟时间
//设置图片加入缓存前,对bitmap进行设置
//.preProcessor(BitmapProcessor preProcessor)
.resetViewBeforeLoading(true)//设置图片在下载前是否重置,复位
.displayer(new RoundedBitmapDisplayer(20))//是否设置为圆角,弧度为多少
.displayer(new FadeInBitmapDisplayer(100))//是否图片加载好后渐入的动画时间
.build();//构建完成
3、展示图片
ImageLoader.getInstance().displayImage(uri.getPath(), im, options, new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
}
});
fresco
Fresco.initialize(getApplicationContext());
SimpleDraweeView testFre;//需要在xml布局文件中使用fresco自定义的布局和设置
testFre.setImageURI(uri);
picasso&glide
ImageView img;
Glide.with(this)
.load(uri)
.into(img);
Picasso.with(this)
.load(uri)
.into(img);
想比起UIL繁杂的设置和用法,其他的则显得简洁得多。
UIL
Universal Imager loader是比较悠久的图片加载框架。
其核心类是ImageLoader类,采用经典的单例模式,核心的方法有初始化设置的:
public synchronized void init(ImageLoaderConfiguration configuration)
以及展示图片的一系列重载方法:
displayImage()和loadImage();
以及操作缓存及一些基本配置的方法。
框架结构设计相对清晰明了。
其中ImageLoaderConfiguration采用的是Builder设计模式,主要功能是为ImageLoader配置一些设置,如缓存大小、线程数量、默认DisplayImageOptions设置等。DisplayImageOptions也是采用的Builder设计模式,但是主要是正对特定的ImageView进行设置,主要配置是否缓存,圆角、动画、默认显示图片、网络获取失败显示图片等。
1、在display()方法中首先会判断url是否为空,如果为空先会判断在DisplayImageOptions对象options中是否设置了Uri为空或是错误的时候显示的图片,如果有则加载给图片,否则直接设置图片为空,并返回renturn结束方法。
2、一旦uri不为空,那么首先会从内存缓存中取得相应的Bitmap(注意不是磁盘缓存),如果Bitmap不为空并且没有被回收,如果不需要进行处理,那么直接加载图片,否则会构建一个ProcessAndDisplayImageTask对象,其本质上是一个Runnable即线程,如果在options中设置了同步加载那么会直接执行线程的run方法,否则使用异步加载,即通过缓存线程池(taskExecutorForCachedImages)执行线程。
3、如果2中的Bitmap为空,即缓存为空或者已经被回收了,如果设置了加载过程中显示的图片那么设置为该图片,然后构造一个LoadAndDisplayImageTask,其本质上也是一个线程,也分同步和异步执行。
fresco
fresco是由facebook开源的图片加载框架,导入的时候fresco会自动的导入5个包:fresco、fbcore、drawee、imagepipeline、imagepipeline-base。其构成是有些复杂的。采用典型的MVC设计模式。有点晕。。。下午写完
首先需要在application中调用Fresco.initialize(Context context,@Nullable ImagePipelineConfig imagePipelineConfig,@Nullable DraweeConfig draweeConfig)),这一步看似无关紧要,其实是串联整个流程的至关重要一部,在这里,初始化了context,imagePipelineConfig(如果为null,则通过传递的context进而新建一个ImagePipelineConfig)创建了一个ImagePipelineFactory对象,并且调用initializeDrawee(context, draweeConfig),在其中初始化了一个PipelineDraweeControllerBuilderSupplier,同时通过 SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier),将sDraweeControllerBuilderSupplier传递到具体的View中。
其次Fresco的使用很简单,在全局初始化完成之后,直接调用setImageURI(Uri uri)方法即可实现所有的流程。
public void setImageURI(Uri uri, @Nullable Object callerContext) {
DraweeController controller = mSimpleDraweeControllerBuilder
.setCallerContext(callerContext)
.setUri(uri)
.setOldController(getController())
.build();
setController(controller);
}
注意这里的controller是一个SimpleDraweeControllerBuilder对象向上转型。关键实现是setController(controller),一步步追踪进去:(DraweeView.java)setController()–>(DraweeHolder.java)setController()–>(DraweeHolder.java)attachController()–>mController.onAttach(),此时来到AbstractDraweeController以及PipelineDraweeController中的onAttach()方法,核心为submitRequest();此时已经接触到了框架的核心实现。mDataSource = getDataSource();定位方法,发现AbstractDraweeController类中只是定义了一个抽象方法,其具体实现在其子类PipelineDraweeController中:
@Override
protected DataSource<CloseableReference<CloseableImage>> getDataSource() {
if (FLog.isLoggable(FLog.VERBOSE)) {
FLog.v(TAG, "controller %x: getDataSource", System.identityHashCode(this));
}
return mDataSourceSupplier.get();
}
mDataSourceSupplier是全局初始化的时候传递的PipelineDraweeControllerBuilderSupplier,定位到其中的get()方法,发现是新建了一个PipelineDraweeControllerBuilder实例,最终会执行其中的obtainController()方法,然后进入到AbstractDraweeControllerBuilder中的obtainDataSourceSupplier()方法,并调用getDataSourceSupplierForRequest方法以及getDataSourceForRequest()方法,具体的实现在PipelineDraweeControllerBuilder中:
@Override
protected DataSource<CloseableReference<CloseableImage>> getDataSourceForRequest(
ImageRequest imageRequest,
Object callerContext,
CacheLevel cacheLevel) {
return mImagePipeline.fetchDecodedImage(
imageRequest,
callerContext,
convertCacheLevelToRequestLevel(cacheLevel));
}
定位到fetchDecodedImage中:
public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
ImageRequest imageRequest,
Object callerContext,
ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit) {
try {
//获取数据
Producer<CloseableReference<CloseableImage>> producerSequence =
mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
//主要是传递数据
return submitFetchRequest(
producerSequence,
imageRequest,
lowestPermittedRequestLevelOnSubmit,
callerContext);
} catch (Exception exception) {
return DataSources.immediateFailedDataSource(exception);
}
}
定位getDecodedImageProducerSequence(imageRequest),进入到ProducerSequenceFactory中的getDecodedImageProducerSequence,核心实现为:
public Producer<Void> getDecodedImagePrefetchProducerSequence(
ImageRequest imageRequest) {
return getDecodedImagePrefetchSequence(getBasicDecodedImageSequence(imageRequest));
}
private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence(
ImageRequest imageRequest) {
Preconditions.checkNotNull(imageRequest);
Uri uri = imageRequest.getSourceUri();
Preconditions.checkNotNull(uri, "Uri is null.");
switch (imageRequest.getSourceUriType()) {
case SOURCE_TYPE_NETWORK:
return getNetworkFetchSequence();
case SOURCE_TYPE_LOCAL_VIDEO_FILE:
return getLocalVideoFileFetchSequence();
case SOURCE_TYPE_LOCAL_IMAGE_FILE:
return getLocalImageFileFetchSequence();
case SOURCE_TYPE_LOCAL_CONTENT:
return getLocalContentUriFetchSequence();
case SOURCE_TYPE_LOCAL_ASSET:
return getLocalAssetFetchSequence();
case SOURCE_TYPE_LOCAL_RESOURCE:
return getLocalResourceFetchSequence();
case SOURCE_TYPE_QUALIFIED_RESOURCE:
return getQualifiedResourceFetchSequence();
case SOURCE_TYPE_DATA:
return getDataFetchSequence();
default:
throw new IllegalArgumentException(
"Unsupported uri scheme! Uri is: " + getShortenedUriString(uri));
}
}
到这里源码就比较清晰了,会根据不同情况,使用不同的获取序列数据。
ps:Fresco是笔者看到过的源代码最复杂的图片框架,复杂到分了5个包和铺天盖地的类,虽然其使用较简单,但是使用起来的是时候总感觉太重。
picasso
该框架采用的单例模式,通过Picasso.with(this)方法取得Picasso对象的实例。
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
可以看出这里还采用了Builder模式,Builder中初始化了关键的一些参数,如:线程池、下载器、缓存等。
然后调用load(Uri uri)方法,返回一个RequestCreator对象,构造器传递了一个Picasso对象的实例,并且将uri封装初始化了一个Request.Builder()对象。
再调用with(imageview,callback)方法,如果有内存缓存则直接设置显示图片,没有则将imageview以及request打包成一个Action对象,同时调用picasso.enqueueAndSubmit(action),此时运行流程又回到了Picasso对象中,然后调用dispatcher.dispatchSubmit(action),进入Dispatcher,这是一个分发器,使用了Handler消息机制,最终的运行落实在performSubmit、performCancel等一些列perform方法上,performSubmit方法主要是进行网络获取,通过submit方法在线程池中运行BitmapHunter线程,在BitmapHunter的run()方法中核心是Bitmap result = hunt(),其中会调用requestHandler.load(data, networkPolicy),RequestHandler共有7个具体的实现类。
网络获取的类为:NetworkRequestHandler,在构造器中初始了一个下载器Downloader类,Downloader是一个接口,具体的实现类有两个:OkHttpDownloader和UrlConnectionDownloader。
其中OkHttpDownloader封装的是OkHttp,而UrlConnectionDownloader封装的是HttpURLConnection,默认使用OkHttpDownloader。NetworkRequestHandler中重载了load方法,通过Response response = downloader.load(request.uri, request.networkPolicy)取得最终的响应,取得InputStream,并最终封装成为一个new Result(is, loadedFrom)对象。
glide
glide与picasso的较为相似,相似度为90%。首先调用with()方法,但是glide做了一定程度的优化,picasso中with方法必须以context为参数,glide中则支持Activity、Fragment等,此方法会返回一个RequestManager。
然后调用RequestManager中的load方法,会返回一个RequestBuilder对象,调用该类的into方法,会触发buildRequest(target),返回一个SingleRequest实例,然后调用 requestManager.track(target, request)–>requestTracker.runRequest(request)–> request.begin(),也就是最终会调用SingleRequest的begin()方法,会调用onSizeReady以及其中的engine.load,然后调用其中的
engineJob.addCallback(cb);
engineJob.start(decodeJob);
其中engineJob代表线程池,decodeJob代表具体的线程。所以最终会调用其中的run()方法。可以看到其核心实现是runWrapped()方法,而runWrapped()中只存在一个一个switch语句,如果不细看会造成很大的困扰,runReason的初始值在构造器中初始为INITIALIZE,并且这里通过getNextStage和getNextGenerator不断的,遍历stage的五个值(),同时根据stage选定不同的DataFetcherGenerator,并且在runGenerators()方法中,不断的调用DataFetcherGenerator的startNext()方法,并且回调FetcherReadyCallback方法,然后在其中进行相应的处理。startNext()方法中关键代码如下:
loadData.fetcher.loadData(helper.getPriority(), this);
实际调用的是DataFetcher的loadData方法。DataFetcher的具体实现类有7个,会根据构造策略选用不同的DataFetcher。
后续
这四个图片框架,从使用便捷角度来说,UIL最复杂,fresco、picasso、glide差不多。
从源码角度来说,UIL和picasso逻辑最清晰,glide较复杂,比一般的网络框架都要复杂,fresco的复杂程度简直。。。或许作为一个单纯的图片框架,显得太重了,如果对图片显示不是太专业级别,那么不推荐使用Fresco。查看其包可以发现,其大小在达到了3M。picasso在120KB左右,UIL:160KB,glide:475KB。picasso是不错的选择,当需要在Activity或者Fragment中使用到时,可以参考使用glide。