-
前言
安卓开源的图片加载框架有很多。最初的imageloader(目前已经不再维护),目前的picasso, glide。picasso和glide在加载展示图片这个模块其实相似度蛮高,谷歌也在很多项目中使用了glide。但这并不是说glide就在任何项目都是最适合的,我们必须在理解每个框架的原理的基础上选择适合我们的框架。接下来的几篇文章会通过分析picasso与glide的使用方式以及源码解析来比较他们两个的差异。首先我们先比较简单的看picasso的使用方法。具体源码分析在:使用方法与原理(二)
-
添加依赖
//picasso
implementation 'com.squareup.picasso:picasso:2.71828'
直接在模块build.gradle文件添加依赖就可以 ,目前的版本是18.03.08发布的2.71828.如果想通过自己编译源码获取jar包的方式需要注意,现在的源码和828版本有了一些改变。在使用方式上面还是有一些差距的。本篇博客就以828版本为准。
-
加载图片
加载图片最常用的就是请求网络图片,代码如下
//firstUrl 是一个图片的网络地址,second是一个imageview
Picasso.get().load(firstUrl).into(second);
其中get方法是获得一个picasso的实例,load方法是进行加载,into是指定要加载图片的容器。本例是一个imageview。我们通过查看Picasso的类可以看到如下所示的load方法的重载:
由此我们可以看出不只是支持网络请求,本地文件,已经本地资源文件都可以支持。
比如获取显示资源文件:
Picasso.get().load(R.mipmap.ic_launcher).into(second);
Load(String)的方法参数不止是url。本地图片路径也可以识别。就不一一举例。加载的时候需要异常处理或者占位图。都是可以的:
Picasso.get().load(firstUrl).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).into(second);
placeholder和error参数也可以是drawable类型的。使用picasso加载图片,在显示的时候都有一个渐进渐出效果,如果需要关闭调用nofade函数:
Picasso.get().load(firstUrl).noPlaceholder()
.noFade().error(R.mipmap.ic_launcher).into(second);
通过get方法获取的都是默认的picasso,我们来看看picasso的构造函数:
Picasso(Context context, Dispatcher dispatcher, Call.Factory callFactory,
@Nullable okhttp3.Cache closeableCache, PlatformLruCache cache, Listener listener,
List<RequestTransformer> requestTransformers, List<RequestHandler> extraRequestHandlers,
Stats stats, Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled,
boolean loggingEnabled) {
}
缓存,还有图片的config等等都是默认的,这个时候如果需要定制的还,可以通过builder的方式来创建。
我们来看看buidler的构造函数
public static class Builder {
private final Context context;
private Call.Factory callFactory;
private ExecutorService service;
private PlatformLruCache cache;
private Listener listener;
private final List<RequestTransformer> requestTransformers = new ArrayList<>();
private final List<RequestHandler> requestHandlers = new ArrayList<>();
private Bitmap.Config defaultBitmapConfig;
这些变量都是可以通过get/set方法来设置。所以我们自己可以定制缓存方式,ExecutorService等。
-
修改图片的尺寸
有时候我们需要的大小和服务器返回的图片大小尺寸不符合,这个时候可以通过接口重置大小:
Picasso.get().load(firstUrl).resize(220,200).into(second);
Picasso.get().load(firstUrl).resizeDimen(R.dimen.widget_margin,R.dimen.widget_margin).into(second);
第一个resize的参数单位是像素。这样可以修改图片的大小。其实这种方法需要我们自己计算图片的大小,而picasso中的fit()方法会自己计算imagview的大小,然后自己对图片进行设置。
Picasso.get().load(firstUrl).fit().into(second);
需要注意的是fit作用对象是imageview,并且因为要计算大小,imageview长宽不能使用wrap_content的属性。
改变图片大小我们都有过图片会变形的经历, 而这个时候需要使用centerCrop()这个函数实现了拉伸截取中间部分。因此centerCrop()一般会配合resize和fit使用:
Picasso.get().load(firstUrl).resize(220,200).centerCrop().into(second);
Picasso.get().load(firstUrl).fit().centerCrop().into(second);
这样做会铺满全屏,但是也许会将图片四周截去一部分。如果想看清楚全貌,使用centerinside()方法,但是这样做有可能出现图片无法充满整个view的情况
比如这样,imagview是全屏窗宽,但是无法铺满全屏。
-
操作bitmap的方法包括旋转,灰度处理。
1 上面的方法都是我们直接将下载的bitmap直接显示到imagview中,如果我们想自己处理的话。可以通过方法拿到bitmap,
/**
* Synchronously fulfill this request. Must not be called from the main thread.
*/
@Nullable // TODO make non-null and always throw?
public Bitmap get() throws IOException {
long started = System.nanoTime();
//检测是否是主线程,如果是在主线程执行则抛出异常
checkNotMain();
if (deferred) {
throw new IllegalStateException("Fit cannot be used with get.");
}
//url不为空或者默认图不为空,表示需要有要展示的图片
if (!data.hasImage()) {
return null;
}
//创建请求对象,request描述了需要请求的最终图片的属性。
Request request = createRequest(started);
//action是一个抽象类,表示了请求的一个实体
Action action = new GetAction(picasso, request);
//请求结果
RequestHandler.Result result =
forRequest(picasso, picasso.dispatcher, picasso.cache, picasso.stats, action).hunt();
//判断是否需要写入缓存。如果需要则保存。
if (result.hasBitmap() && shouldWriteToMemoryCache(request.memoryPolicy)) {
//保存的时候如果需要保存的bitamp太大,超过缓存大小。则直接放弃
picasso.cache.set(request.key, result.getBitmap());
}
return result.getBitmap();
}
这个get接口,将获取的bitmap返回,需要注意的是不能在主线程进行这个操作。
2 picasso还提供了图片的旋转功能
//旋转90度,默认以(0,0)为原点旋转
picasso.load(firstUrl).centerCrop().rotate(90).into(iv);
//以(20,30)为原点旋转90度
picasso.load(firstUrl).centerCrop().rotate(90,20,30).into(iv);
3 在项目中经常会有对图片进行处理的需求,比较常见的是圆角,灰度处理,高斯模糊,倒影。这些都可以自定义实现 。
picasso.load(firstUrl).transform(new BlurTransformation(this)).into(iv);
picasso通过transform(Transformation)来实现,而Transformation是一个接口,我们要自己自己实现这个接口来对bitmap进行处理。
/** Image transformation. */
public interface Transformation {
/**
* Transform the source bitmap into a new bitmap. If you create a new bitmap instance, you must
* call {@link android.graphics.Bitmap#recycle()} on {@code source}. You may return the original
* if no transformation is required.
*/
Bitmap transform(Bitmap source);
/**
* Returns a unique key for the transformation, used for caching purposes. If the transformation
* has parameters (e.g. size, scale factor, etc) then these should be part of the key.
*/
String key();
在这里bitmap就是我们要处理的主要的东西。我们来实现模糊处理
public class BlurTransformation implements Transformation {
private static int MAX_RADIUS = 25;
private static int DEFAULT_DOWN_SAMPLING = 1;
private Context mContext;
private int mRadius;
private int mSampling;
public BlurTransformation(Context context) {
this(context, MAX_RADIUS, DEFAULT_DOWN_SAMPLING);
}
public BlurTransformation(Context context, int radius) {
this(context, radius, DEFAULT_DOWN_SAMPLING);
}
public BlurTransformation(Context context, int radius, int sampling) {
mContext = context.getApplicationContext();
mRadius = radius;
mSampling = sampling;
}
@Override public Bitmap transform(Bitmap source) {
int scaledWidth = source.getWidth() / mSampling;
int scaledHeight = source.getHeight() / mSampling;
Bitmap bitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.scale(1 / (float) mSampling, 1 / (float) mSampling);
Paint paint = new Paint();
paint.setFlags(Paint.FILTER_BITMAP_FLAG);
canvas.drawBitmap(source, 0, 0, paint);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
try {
bitmap = RSBlur.blur(mContext, bitmap, mRadius);
} catch (RSRuntimeException e) {
bitmap = FastBlur.blur(bitmap, mRadius, true);
}
} else {
bitmap = FastBlur.blur(bitmap, mRadius, true);
}
source.recycle();
return bitmap;
}
@Override public String key() {
return "BlurTransformation(radius=" + mRadius + ", sampling=" + mSampling + ")";
}
然后通过如下代码,可以实现图片的模糊处理。
Picasso.get().load(firstUrl).transform(new BlurTransformation(this)).into(four);
通过看源码,transfrom也是一个重载函数:
/**
* Add a list of custom transformations to be applied to the image.
* <p>
* Custom transformations will always be run after the built-in transformations.
*/
@NonNull
public RequestCreator transform(@NonNull List<? extends Transformation> transformations) {
data.transform(transformations);
return this;
}
也可以自定义几个效果,放到列表里面,然后会对图片进行一系列的操作。比如在定义一个灰度处理:
List<Transformation> ts = new ArrayList<>();
ts.add(new BlurTransformation(this));
ts.add(new ColorFilterTransformation(Color.RED));
Picasso.get().load(firstUrl).transform(ts).into(four);
这样就可以对图片进行2次处理。使用这个方法的时候要注意顺序。
在这里提供一个开源的Transformation项目,针对当前版本可以用,以后因为picasso修改了Transformation这个接口的回调方法,所以这个就不再适用了。但是本质上还是可以借鉴的。
-
有意思的预加载
/**
* Asynchronously fulfills the request without a {@link ImageView} or {@link Target},
* and invokes the target {@link Callback} with the result. This is useful when you want to warm
* up the cache with an image.
* <p>
* <em>Note:</em> The {@link Callback} param is a strong reference and will prevent your
* {@link android.app.Activity} or {@link android.app.Fragment} from being garbage collected
* until the request is completed.
*/
public void fetch(@Nullable Callback callback) {
long started = System.nanoTime();
if (deferred) {
throw new IllegalStateException("Fit cannot be used with fetch.");
}
if (data.hasImage()) {
// Fetch requests have lower priority by default.
if (!data.hasPriority()) {
data.priority(Priority.LOW);
}
Request request = createRequest(started);
String key = createKey(request, new StringBuilder());
if (shouldReadFromMemoryCache(memoryPolicy)) {
Bitmap bitmap = picasso.quickMemoryCacheCheck(key);
if (bitmap != null) {
if (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
if (callback != null) {
callback.onSuccess();
}
return;
}
}
Action action =
new FetchAction(picasso, request, memoryPolicy, networkPolicy, tag, key, callback);
picasso.submit(action);
}
}
根据注释我们可以看出。这个函数,没有target目标,也不会返回获取的数据,所以你觉得执行了一点意思没用 。但是他会把获取的图片存入内存。因此可以算是预加载,我们可以再使用图片之前偷偷的先预加载,然后加载的时候就会显得比较快。但是不要迷信这个方法 ,内存存储这个不是我们能准确控制的,所以慎用。
-
Tag管理
Picasso本质上也是进行很多网络请求,像Volley一样,它也提供了tag的管理方法。
Picasso.get().load(firstUrl).fit().centerCrop().tag("alvin").into(second);
在我们请求的时候添加tag,如果页面销毁的时候,还没有请求成个,那么我们可以通过tag取消这个请求。当然如果只是一个请求,它的效果不足以体现,但是我们经常会用到listview,recyleview,这个时候如果有十几二十多个甚至更多,那么这个取消就比较有用了。
@Override
protected void onStop(){
super.onStop();
Picasso.get().cancelTag("alvin");
}
除了这个还有PauseTag()和ResumeTag()这个方法。与activity的对应方法意思类似。
-
缓存:内存,本地,网络三种方式
1 首先是内存缓存,通过源码可以看出是一个app所允许的15%
static int calculateMemoryCacheSize(Context context) {
ActivityManager am = ContextCompat.getSystemService(context, ActivityManager.class);
boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
int memoryClass = largeHeap ? am.getLargeMemoryClass() : am.getMemoryClass();
// Target ~15% of the available heap.
return (int) (1024L * 1024L * memoryClass / 7);
}
2 硬盘的缓存,picasso网络请求用的okhttp,而本地缓存直接使用的ok的缓存方式。一般大小也不超过50m,一个请求进入的时候,会进行缓存检查,顺序是:memory->disk->network
在开发过程中,我们可以通过接口设置本次请求是否需要缓存已经使用哪种缓存。默认是内存和磁盘缓存都开启。
Picasso.get().load(firstUrl).centerCrop().memoryPolicy(MemoryPolicy.NO_CACHE).into(second);
memoryPolicy()函数可以进行设置,参数大家可以通过源码学习,很简单的东西就不写了。
在我上面手机截屏的图片可以看到左上角有一个蓝色三角,这个是picasso的一个特色,通过颜色来表示图片的来源。memory,disk,network分别由不同的颜色保存,绿色表示来自memroy,蓝色来自disk,红色来自network
/**
* Describes where the image was loaded from.
*/
public enum LoadedFrom {
MEMORY(Color.GREEN),
DISK(Color.BLUE),
NETWORK(Color.RED);
final int debugColor;
LoadedFrom(int debugColor) {
this.debugColor = debugColor;
}
}
这个颜色显示默认是关闭的,需要通过setIndicatorsEnabled(true)来打开。
Picasso picasso = PicassoProvider.get();
picasso.setIndicatorsEnabled(true);
//旋转90度,默认以(0,0)为原点旋转
picasso.load(firstUrl).centerCrop().rotate(90).into(iv);
-
总结
以上就是picasso的基本用法,主旨是为了介绍使用方法,很多细节没有详解,稍后会在讲解源码的时候进行解释。如果有遗漏或者错误之处希望大伙指出,不胜感激。