本帖最后由 我是楠楠 于 2017-8-17 16:49 编辑
前面写的5篇文章让我们对Picasso的用法有了很详细的了解,Android的框架层出不穷,每年都会开源出大量的优秀的框架,如果我们只是站在一个使用者的角度去不断的接触框架,而不去学习框架底层的实现原理,那样顶多也只能一直当新手,而不是老司机了,下面我们就从最基本的入手,从源码的角度去剖析Picasso的工作原理(该分析都是基于Picasso 2.5.2版本).Let’s Go. 官方给出的一个最简单的用法,一个链式调用就能实现一张图片的请求及展示:
- <font color="rgb(85, 85, 85)">Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(target);</font>
复制代码
只需这一行代码,Picasso就能从我们提供的URL地址中去请求图片,然后将图片展示到targetImageView中,我们就从最简单的开始,一层层的还原Picasso最底层的本质.
Picasso.with(context)
- public static Picasso with(Context context) {
- if (singleton == null) {
- synchronized (Picasso.class) {
- if (singleton == null) {
- singleton = new Builder(context).build();
- }
- }
- }
- return singleton;
- }
复制代码
这段代码就是为了初始化Picasso,获取一个Picasso的实例,该类代码具有很大的共性,很多框架在获取实例时基本都是采用了 单例模式+建造者模式 - 单例模式特点:
- 采用后加载机制,保证实例只有在被使用的时候才被创建
- 采用双重检验锁模式,保证了在多个线程同时请求的过程中,只创建唯一的单例对象
- 同步对象为类本身,保证了该对象的同步范围在整个java虚拟机中,也就是全局的
- 利用建造者模式创建一个默认的Picasso对象,一般来说使用建造者模式,是因为构建该对象本身时需要创建其他的类对象,也是保证了多个类的初始化,看代码new Builder(context).build()
- <font color="rgb(85, 85, 85)">/** Create the {@link Picasso} instance. */
- 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);
- picasso.Dispatcher dispatcher = new picasso.Dispatcher(context, service, HANDLER, downloader, cache, stats);
- return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
- defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
- }
- }</font>
复制代码
在该方法中主要对初始化了以下参数: - Downloader
- 下载工具类,该Utils.createDefaultDownloader(context);如果OKHttp可用的话,会返回一个OkHttpDownloader,否则的话会返回一个UrlConnectionDownloader(内部使用HttpURLConnection实现)
Cache
该类的默认实现为LruCache,也就是利用最近最少使用算法进行缓存,内部是一个LinkedHashMap,这里要注意的是,一般我们在初始化LinkedHashMap时,会限制initialCapacity容量值,但此处是在初始化LruCache时通过调用Utils.calculateMemoryCacheSize(context)来计算出最大的堆内存值然后划分出7分之一,也就是15%作为容量大小,所以在每次调用set()方法进行缓存时,都会调用trimToSize()不停的计算当前可使用的空间大小,超出范围就删除之前保存的数据; Utils.calculateMemoryCacheSize(context)的代码块
- <font color="rgb(85, 85, 85)">static int calculateMemoryCacheSize(Context context) {
- ActivityManager am = getService(context, ACTIVITY_SERVICE);
- //计算出最大的堆内存值,也就是可用大小值
- boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
- int memoryClass = am.getMemoryClass();
- if (largeHeap && SDK_INT >= HONEYCOMB) {
- memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);
- }
- //分配各15%的大小
- // Target ~15% of the available heap.
- return 1024 * 1024 * memoryClass / 7;
- }</font>
复制代码
ExecutorService
- 默认实现类PicassoExecutorService,也就是ThreadPoolExecutor子类而已,默认线程数是3,但是PicassoExecutorService会在不同的网络状态下调整线程数,wifi下为4个线程,4G状态为3个线程,3G状态下为2个线程,2G状态下为1个线程.
- RequestTransformer
- Stats
Dispatcher
上面就是关于.with(context)所做的分析,我们继续向下分析.
.load(String url)方法源码解析
我们现在看.load(String url)方法都做了什么操作,其实.load(url)最后调用的依然是.load(Uri uri)
- <font color="rgb(85, 85, 85)">public RequestCreator load(String path) {
- //对path进行判空,省略部分代码
- ....
- return load(Uri.parse(path));
- }
- //其实调用的是这个方法
- public RequestCreator load(Uri uri) {
- return new RequestCreator(this, uri, 0);
- }</font>
复制代码
所以加载图片的本质就是我们通过Picasso调用了load(url)返回了一个new出来的RequestCreator示例,同时将Picasso ,uri地址, 还有resourceId也就是0,作为构造参数传入,下面我们就看看该构造函数都做了哪些操作;
- <font color="rgb(85, 85, 85)"> 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);
- }</font>
复制代码
- public Builder(Uri uri) { setUri(uri); }
- public Builder(int resourceId) { setResourceId(resourceId); }
复制代码
最重要的方法.into(target)方法解析
.into(target)方法是ReqeustCreator中的方法,所以我们去一窥究竟,其实.into(target)还是在内部调用的.into(target,callback),看代码
- <font color="rgb(85, 85, 85)">public void into(ImageView target) {
- into(target, null);
- }</font>
复制代码
下面我们就着重看一下.into(target,callback)都做了哪些操作,代码较多,我们逐一进行分析
- <font color="rgb(85, 85, 85)">public void into(ImageView target, Callback callback) {
- long started = System.nanoTime();
- //1 检测是否在主线程中执行,如果不是就抛异常
- checkMain();
- if (target == null) {
- throw new IllegalArgumentException("Target must not be null.");
- }
- //2 该判断是只有在uri为null,并且resourceId为0的情况下才会执行相应逻辑,取消请求显示默认图片,然后结束
- if (!data.hasImage()) {
- picasso.cancelRequest(target);
- if (setPlaceholder) {
- setPlaceholder(target, getPlaceholderDrawable());
- }
- return;
- }
- //3 是否延时加载,默认为false,只有当调用了 .fit() 才会执行该逻辑
- if (deferred) {
- //调用 .fit() 之前,不能调用 .resize(w,h) ,否则抛出异常
- if (data.hasSize()) {
- throw new IllegalStateException("Fit cannot be used with resize.");
- }
- int width = target.getWidth();
- int height = target.getHeight();
- //target是否已经完全绘制出来,如果没有,则执行相关操作
- if (width == 0 || height == 0) {
- //是否设置了默认图片,在进行延时请求前先展示默认图片
- if (setPlaceholder) {
- setPlaceholder(target, getPlaceholderDrawable());
- }
- // 加入延时队列,同时创建一个DeferredRequestCreator去测量target的宽高,结束任务
- picasso.defer(target, new DeferredRequestCreator(this, target, callback));
- return;
- }
- //target已经绘制完成,调用resize方法,并继续向下执行
- data.resize(width, height);
- }
- //4 创建一个request,并根据request生产一个requestKey
- Request request = createRequest(started);
- String requestKey = createKey(request);
- //5 判断可否从内存中读取资源,默认为可以
- if (shouldReadFromMemoryCache(memoryPolicy)) {
- //从内存中获取,如果能获取到就取消请求操作,并执行callback.onSuccess()的回调,结束任务
- Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
- if (bitmap != null) {
- picasso.cancelRequest(target);
- setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
- if (picasso.loggingEnabled) {
- log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
- }
- if (callback != null) {
- callback.onSuccess();
- }
- return;
- }
- }
- //6 判断是否设置了默认图片,在进行请求前先展示默认图片
- if (setPlaceholder) {
- setPlaceholder(target, getPlaceholderDrawable());
- }
- //7 这时是的确需要联网请求了,生成对应的 ImageViewAction,并将 action 添加到 Picasso的请求队列中
- Action action =
- new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
- errorDrawable, requestKey, tag, callback, noFade);
- picasso.enqueueAndSubmit(action);
- }</font>
复制代码
部分功能说明已在代码中标出,但还是用一张示意图来进行解析比较简单易懂
图画的比较丑,但是看着还算比较清晰,我们在按照代码,图示,再用文字描述一遍,让更有深的认识; - 检测是否在主线程中执行,如果不是就抛异常,可能是要更新UI显示,所以要在主线程中执行代码逻辑
- 该判定方法data.hasImage()容易让人误解为是否已经有图片获取好了,但其实是为了判断在Builder创建的时候,是否设置了uri或者resourceId,如果没有设置uri并且resourceId为0,说明该请求是一个错误请求,就可以加载默认图片并取消请求操作;
- 是否延时加载,默认为false,只有当调用了 .fit() 才会执行该逻辑,如果有同学不太理解.fit()的作用,可翻看我之前写的博客,其实就是为了重新计算target的宽高值,而且不能在调用了.resize()之后再次调用.fit()
- 创建一个request,并根据request生产一个requestKey
- 判断可否从内存中读取资源,默认为true,如果有同学不知道怎么设置跳过内存读取这一步骤,可翻看我之前写的博客
- 判断是否设置了默认图片,在进行请求前先展示默认图片
- 这时是的确需要联网请求了,生成对应的 ImageViewAction,并将 action 添加到 Picasso的请求队列中
OK,到现在我们已经大致了解了Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(target);都做了哪些操作,至于第7步之后又做了哪些操作,我们将继续分析,还原她的本质,愿大家都有美好的一天… |