picasso 是一个强大的图片加载缓存框架
1.首先看下picasso 如何使用:
Picasso.with(this)
.load("url")
.placeholder(R.drawable.leak_canary_icon)
.error(R.drawable.leak_canary_icon)
.resize(480,800)
.centerCrop()
.rotate(360)
.priority(Picasso.Priority.HIGH)
.tag("picasso listview")
.memoryPolicy(MemoryPolicy.NO_CACHE)
.networkPolicy(NetworkPolicy.NO_CACHE)
.into(imageView);
Picasso和Glide相似都是使用链式调用 我们可以把Picasso类视为一个管理类,管理图片的加载,转换,缓存的工作.
.with()获取Picasso的单例,参数是上下文.
.load()方法完成加载图片的方法.参数是url或file路径
.resize()方法 参数单位像素 因为在项目中要考虑网络带宽,手机的内存使用,下载速度等情况.结合考虑.这时候有一种场景服务器给我们的图片的宽和高与我们实际的imageview的宽和高是不一致的.服务器给我们一些奇奇怪怪的图片,这时我们就要调用resize方法.对图片的宽高进行设置.
.centerCrop() 将整个图片充满imageview边界 裁减掉多余部分
.rotate() 对图片加载进行旋转设置.旋转点坐标0.0
.priority(Picasso.Priority.HIGH) 设置图片加载优先级 不是100%保证 因为picasso内部只是向高的优先级靠拢.但并不会保证
.tag() Picasso 允许我们为每个请求设置一个tag 为什么用到tag呢 比如listview 快速滑动这样的场景,如果不去设置响应事件就会加载所有的图片,浪费性能,造成体验不好.UI卡顿,而我们为每一个加载事件做标记tag,监听响应事件.做出处理操作.
mlsitview.setOnScrollStateChanged(AbsListView view,int scrollState){
final Picasso picasso = Picasso.with(MainActivity.this);
if(scrollState == SCROLL_STATE_IDLE){//停止状态加载图片
picasso.resumeTag("picasso listview");
}else{
picasso.pauseTag("picasso listview");
}
}
.memoryPolicy(MemoryPolicy.NO_CACHE) picasso的缓存策略 内存缓存
.networkPolicy(NetworkPolicy.NO_CACHE)文件缓存
2.picasso源码分析
1)with方法:内存缓存Lrucache和线程池调度
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
.build()方法,生成Picasso对象,返回该对象实例,按照需求配置下载器,缓存,线程池,转换器等.
public Picasso build() {
Context context = this.context;
if (downloader == null) {
downloader = Utils.createDefaultDownloader(context);//下载器
}
if (cache == null) {
cache = new LruCache(context);//缓存
}
if (service == null) {
service = new PicassoExecutorService();//线程池
}
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;//转换器
}
Stats stats = new Stats(cache);
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
.build()方法.-->downloader = Utils.createDefaultDownloader(context);
-->
static Downloader createDefaultDownloader(Context context) {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");//通过反射查找是否引用了okhttp,如果引用了采用okhttp进行网络连接
return OkHttpLoaderCreator.create(context);
} catch (ClassNotFoundException ignored) {
}
return new UrlConnectionDownloader(context);//没有引用,采用httpurlConnection进行网络请求
}
继续看build方法中
if (cache == null) {
cache = new LruCache(context);//lruCache主要采用linkedhashmap 将图片添加到链表尾部//( final LinkedHashMap<String, Bitmap> map;)
}
if (service == null) {
service = new PicassoExecutorService();
}
接着来看PicassoExecutorService
PicassoExecutorService 是一个线程并发池
PicassoExecutorService extends ThreadPoolExecutor
ThreadPoolExecutor是java并发库中的内容 无论是图片库还是网络库都离不开这个线程池
public ThreadPoolExecutor(int corePoolSize, //核心线程数目
int maximumPoolSize,//线程池中允许的最大线程数
long keepAliveTime,//线程池中线程数目比核心线程数目多的时候,超过这个时间,就会将多余的线程回收
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//阻塞队列 线程安全队列
ThreadFactory threadFactory,//线程工厂 产生线程
RejectedExecutionHandler handler) {}//当线程池 由于线程数目和队列导致任务阻塞时 ,线程池的处理方式
1.如果线程池中的线程数目少于corePoolSize , 这时候线程池是会重新创建线程的,直到线程数目达到corePoolSize.
2.如果线程池中线程数目大于或者等于corePoolSize,但是工作队列workQueue没有满, 这时新的任务还是会放进队列中的,按照先进先出的原则来进行执行.
3.如果线程池中的线程数目大于等于corePoolSize,并且工作队列workQueue满了,但是总线程数目小于maximumPoolSzie 这时候还是可以直接创建线程 来处理添加的任务
4.如果工作队列满了,并且线程池中线程的数目达到了最大数目maximumPoolSize 这时就会由RejectedExecutionHandler来处理
默认的处理方式就是 丢弃到任务,同时抛出异常
继续看
非常重要的 Dispatcher 是来管理非常重要的线程之间的切换.
dispatcher如何完成线程切换
首先我们进入Dispatcher这个类,Dispatcher类中首先开启线程DispatcherThread
final DispatcherThread dispatcherThread;
this.dispatcherThread = new DispatcherThread();
this.dispatcherThread.start();
首先DispatcherThread extends HandlerThread
static class DispatcherThread extends HandlerThread {
DispatcherThread() {
super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
}
}
所以先分析下HandlerThread
HandlerThread extends Thread 注释中这样写到他会开启一个新的线程,线程内部会有一个Looper对象,通过这个Looper对象可以去循环遍历我们的消息队列.
看下run()方法
run方法的作用就是完成Looper的创建 通过Looper的循环方法构造一个循环的线程.HandlerThread也要调用start方法,完成线程的运行的.
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();//完成Looper对象的创建
synchronized (this) {//通过同步锁机制锁住当前对象
mLooper = Looper.myLooper();//mLooper在我们的同步代码块中
notifyAll();//唤醒等待线程 和notifyAll()同步出现的就是wait()方法.
}
Process.setThreadPriority(mPriority);//设置线程优先级
onLooperPrepared();//内部是个空实现 留给我们自己去重写
Looper.loop();//完成整个的线程消息启动工作
mTid = -1;
}
//我们回到同步代码块中 我们为什么要通过同步代码块中的方法去唤醒等待线程呢?有在哪里去wait的操作呢?
在getLooper方法中
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {//这里同样用同步锁机制锁住当前对象
while (isAlive() && mLooper == null) {//判断线程存货且不为空的情况下
try {
wait();//线程等待
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
继续看Dispatcher构造方法中的其他参数
Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
Downloader downloader, Cache cache, Stats stats) {
this.dispatcherThread = new DispatcherThread();
this.dispatcherThread.start();
Utils.flushStackLocalLeaks(dispatcherThread.getLooper());
this.context = context;
this.service = service;
this.hunterMap = new LinkedHashMap<String, BitmapHunter>();
this.failedActions = new WeakHashMap<Object, Action>();
this.pausedActions = new WeakHashMap<Object, Action>();
this.pausedTags = new HashSet<Object>();
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);//dispatherThread线程内部的handler(因为dispatcherThread.getLooper()参数)
this.downloader = downloader;
this.mainThreadHandler = mainThreadHandler;//主线程的handler
this.cache = cache;
this.stats = stats;
this.batch = new ArrayList<BitmapHunter>(4);
this.airplaneMode = Utils.isAirplaneModeOn(this.context);
this.scansNetworkChanges = hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE);
this.receiver = new NetworkBroadcastReceiver(this);//广播接收者,主要用于监听网络变化的操作的,这也是比较重要的
receiver.register();
}
回到picasso的build()方法中
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
HANDLER是picasso内部定义的
static final Handler HANDLER = new Handler(Looper.getMainLooper()){//getMainLooper是主线程
}//定义为静态内部类,就是静态内部类不持有外部类的引用
3.NetworkRequestHandler处理图片请求与回调
继续看build()方法
build()方法最后 还是返回一个picasso对象
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
我们进入这个方法看一下
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
this.context = context;
this.dispatcher = dispatcher;
this.cache = cache;
this.listener = listener;
this.requestTransformer = requestTransformer;
this.defaultBitmapConfig = defaultBitmapConfig;
int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
List<RequestHandler> allRequestHandlers =
new ArrayList<RequestHandler>(builtInHandlers + extraCount);
// ResourceRequestHandler needs to be the first in the list to avoid
// forcing other RequestHandlers to perform null checks on request.uri
// to cover the (request.resourceId != 0) case.
allRequestHandlers.add(new ResourceRequestHandler(context));
if (extraRequestHandlers != null) {
allRequestHandlers.addAll(extraRequestHandlers);//将extraRequestHandlers集合加入到allRequesthandlers集合中,看下
allRequestHandler这个ArrayList集合.
}
allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
allRequestHandlers.add(new MediaStoreRequestHandler(context));
allRequestHandlers.add(new ContentStreamRequestHandler(context));//集合中加入处理contentprovider中图片的handler
allRequestHandlers.add(new AssetRequestHandler(context));//处理asset中图片
allRequestHandlers.add(new FileRequestHandler(context));//处理file中图片
allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));//处理网络请求图片的handler
requestHandlers = Collections.unmodifiableList(allRequestHandlers);
this.stats = stats;
this.targetToAction = new WeakHashMap<Object, Action>();
this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();
this.indicatorsEnabled = indicatorsEnabled;
this.loggingEnabled = loggingEnabled;
this.referenceQueue = new ReferenceQueue<Object>();
this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
this.cleanupThread.start();
}
我们主要看下处理网络请求图片的handler:NetworkRequestHandler.主要看其中的load()方法:
@Override public Result load(Request request, int networkPolicy) throws IOException {
Response response = downloader.load(request.uri, request.networkPolicy);
if (response == null) {
return null;
}
Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;
Bitmap bitmap = response.getBitmap();
if (bitmap != null) {
return new Result(bitmap, loadedFrom);
}
InputStream is = response.getInputStream();
if (is == null) {
return null;
}
// Sometimes response content length is zero when requests are being replayed. Haven't found
// root cause to this but retrying the request seems safe to do so.
if (loadedFrom == DISK && response.getContentLength() == 0) {
Utils.closeQuietly(is);
throw new ContentLengthException("Received response with 0 content-length header.");
}
if (loadedFrom == NETWORK && response.getContentLength() > 0) {//请求响应长度>0
stats.dispatchDownloadFinished(response.getContentLength());//stats派发响应完成结果
}
return new Result(is, loadedFrom);
}
看下stats的dispatchDownloadFinished方法:
void dispatchDownloadFinished(long size) {
handler.sendMessage(handler.obtainMessage(DOWNLOAD_FINISHED, size));
}
发现了熟悉的sendMessage()方法
其中的handler是StatsHandler 看其中handleMessage()方法:
@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case CACHE_HIT:
stats.performCacheHit();
break;
case CACHE_MISS:
stats.performCacheMiss();
break;
case BITMAP_DECODE_FINISHED:
stats.performBitmapDecoded(msg.arg1);
break;
case BITMAP_TRANSFORMED_FINISHED:
stats.performBitmapTransformed(msg.arg1);
break;
case DOWNLOAD_FINISHED:
stats.performDownloadFinished((Long) msg.obj);
break;
default:
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new AssertionError("Unhandled stats message." + msg.what);
}
});
}
}
Stats中的performDownloadFinished()方法
void performDownloadFinished(Long size) {
downloadCount++;
totalDownloadSize += size;
averageDownloadSize = getAverage(downloadCount, totalDownloadSize);
}
4.picasso源码load方法
load 方法return load(Uri.parse(path)).
该函数有四种重载方法,其中Uri,String,File最终都转化为Uri进行请求,而int则是app内部的资源访问。
load()函数返回RequestCreator对象,
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
再看RequestCreator();
RequestCreator的成员变量里有一个重要的对象是Request.Builder(其有一个内部类Builder),RequestCreator里很多函数都是桥 接到该Builder。
RequestCreator(Picasso picasso, Uri uri, int resourceId) {
if (picasso.shutdown) {
throw new IllegalStateException(
"Picasso instance already shut down. Cannot submit new requests.");
}
this.picasso = picasso;
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
看最后一行是创建了 Request.Builder对象,通过Request.Builder对象我们可以配置更多对象.
picasso源码into方法:Action&BitmapHunter
with()方法完成了Picasso对象的创建,内部通过单例的形式进行构建,这样就保证了在app使用Picasso对象的时候,只会存在一个
picasso对象,同时他也是我们整个Picasso加载的入口.同时with()方法还做了一些基础的配置工作.比如说Dispatcher这个类,在picasso框架中主要完成线程的切换工作,其内部实现原理是handler.
load()方法主要完成了RequsetCreate()方法的创建.同时获取到了我们要加载的一些资源的路径
into()方法完成图片的加载工作.
DeferredRequestCreator类
当我们创建请求的时候,我们新建一个图片加载请求,但是我们还不能获取到当imageview的宽高,这时我们会创建DeferredRequestCreator,通过这个类会我们的imageview的target去进行监听,直到获取到当前imageview的宽高,这时候我们重新执行我们的请求创建.所以删除请求的时候,同时我们还应该删除DeferredRequestCreator对target的监听事件.
public void into(Target target) {
long started = System.nanoTime();
//线程检查
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
if (deferred) {
throw new IllegalStateException("Fit cannot be used with a Target.");
}
//没设置url以及resId则取消请求
if (!data.hasImage()) {
picasso.cancelRequest(target);//取消请求
target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);//取消后设置占位符.
return;
}
Request request = createRequest(started);
String requestKey = createKey(request);
if (shouldReadFromMemoryCache(memoryPolicy)) {
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
if (bitmap != null) {
picasso.cancelRequest(target);
target.onBitmapLoaded(bitmap, MEMORY);
return;
}
}
target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);
Action action =
new TargetAction(picasso, target, request, memoryPolicy, networkPolicy, errorDrawable,
requestKey, tag, errorResId);
//提交请求,Picasso内部维护了一个map,key是imageView,value是Action
//提交时做判断,如果当前imageView已经在任务队列里了。判断当前任务与之前的任务是否相同,
//如果不相同则取消之前的任务并将新的key-value加入到map
picasso.enqueueAndSubmit(action);
}
RequestCreator最重要的一个方法是into(),在into()方法调用之前所调用的一切配置函数都只是把配置信息储 存起来,而没有做网络请求,当调用into()函数后才开始网络请求。into()函数有五个重载方法,用于把请求结果存 放或显示到指定位置。
submit()函数:用于把RequestCreator提交上来的Action添加到队列里,该函数其实是把提交任务交给Dispatcher
而Dispatcher最后则通过handler把任务切换到Dispatcher所在的线程(后台线程,因为要进行网络访问).
RequestCreator最重要的一个方法是into(),在into()方法调用之前所调用的一切配置函数都只是把配置信息储 存起来,而没有做网络请求,当调用into()函数后才开始网络请求。into()函数有五个重载方法,用于把请求结果存 放或显示到指定位置。
这里分析into到ImageView里,该函数首先会查看缓存里是否有请求的Bitmap,如果有那最好,都不用进行网络请求, 直接把Bitmap显示到ImageView里。如果缓存里没有,则会把请求加入到请求队列里,之后进行网络请求。
到这里,又把控制权交给Picasso类。
底层分析:
Action 类:是一个request请求类包装类,最终把包装好的类,交给线程还执行.(一个没有Set的Bean,包含各种动作信息,如网络请求策略,内存策略,请求配置等。)
BitmapHuntyer 就是一个runnable,是一个开启子线程的工具.是一个Runnable的子类,用来进行Bitmap的获取(网络,硬盘,内存等),处理(角度,大小等),然后执行分发器(dispatcher)的回调处理
picasso源码into方法:线程池&PicassoFutureTask
Stats:对请求整个过程的一个记录,如命中(hit)缓存的次数,不命中(miss)缓存的次数,Bitmap下载解析次数, 下载完成次数等。
ImageViewAction:Action的子类,内部有complete()和error()函数,用于把请求结果显示到ImageView上
其实Picasso里用来在指定目标上显示结果都是通过PicassoDrawable类来实现的。
再次 强调下最重要的类
RequestHandler
//抽象类,由不同的子类来实现不同来源的图片的获取与加载,比如:
//AssetRequestHandler:加载asset里的图片
//FileRequestHandler:加载硬盘里的图片
//ResourceRequestHandler:加载资源图片
//NetworkRequestHandler:加载网络图片
BitmapHunter
//是一个Runnable的子类,用来进行Bitmap的获取(网络,硬盘,内存等),处理(角度,大小等),
//然后执行分发器(dispatcher)的回调处理
PicassoDrawable
//实现了引入图片渐变功能和debug状态的标识的Drawable,用来在最后bitmap转换成PicassoDrawable
//然后设置给ImageView,根据图片的来源可以在图片的左上角显示一个不同颜色的三角形色块
MEMORY(Color.GREEN) //内存加载
DISK(Color.BLUE) //本地加载
NETWORK(Color.RED) //网络加载
DeferredRequestCreator
//ViewTreeObserver.OnPreDrawListener的实现类,即将绘制视图树时执行的回调函数。
//这时所有的视图都测量完成并确定了框架。 客户端可以使用该方法来调整滚动边框,
//甚至可以在绘制之前请求新的布局,这里用来实现当开发者修改图片尺寸时的逻辑
Action
//Action代表了一个具体的加载任务,主要用于图片加载后的结果回调,有两个抽象方法,complete和error,
//并保存了每个请求的各种信息(),具体的实现类如下
//GetAction:同步执行请求时使用。
//FetchAction:当不需要ImageView来安置bitmap时的异步请求,通常用来预热缓存
//RemoteViewsAction:用来更新远程图片(notification等)的抽象类。
//TargetAction:一般在View(不只是ImageView)或者ViewHolder中用来加载图片,需要实现Target接口
//ImageViewAction:最常用的Action,主要用来给ImageView加载图片