Android Picasso图片加载库源码剖析

来源:互联网 发布:mac 207 编辑:程序博客网 时间:2024/05/17 06:40

Picasso是一个优秀的轻量级网络图片加载缓存库。花了两天时间研读了下的阅读了下他的源码。做一下的剖析:

Picasso的优点:

  • 足够轻量级:maven打包出来的jar只有130kb左右
  • 二级缓存策略,分别缓存内存和磁盘空间
  • 自动监控内存大小数据
  • 很好的线程控制,根据网络状态控制线程数量、具有优先级调度策略。
  • 图片适应、压缩处理策略
  • 预加载功能
  • 代码质量高、易拓展。

1 Picasso整体画像

1 流程图

这里写图片描述

2 Picasso 基本使用和概括流程

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

看下初始化的方法。with()获的Picasso的全局单例。

public static Picasso with(Context context) {    if (singleton == null) {      synchronized (Picasso.class) {        if (singleton == null) {          singleton = new Builder(context).build();        }      }    }    return singleton; }

使用Builder模式获得实例,看起来比较清晰明了。

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);    }

建造者获得实例的时候会初始好Download(网络下载模块)、LruCache(缓存核心)、RequestTransformer(Request运输类)、Stats(检测类)、Dispatch(事务分发中心)。

调用load(uri)开始执行图片加载
load(Uri)
load(String)
load(File)
其中#load(Uri)

public RequestCreator load(Uri uri) {    return new RequestCreator(this, uri, 0);}

RequestCreator提供了图片相关处理相关的所有API,RequestCreator所有的api方法结果return this。可以理解他同样为一个builder模式的建造者。着重看下装载图片的into()方法实现。

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.");    }    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.enqueueAndSubmit(action);}

到这里可以大体看到的图片加载的流程代码,Picasso模块初始化之后,初始了各个核心模块,并创建RequestCreator提供出图片相关的所有操作API,在执行启动into下载图片的时机优先使用缓存中的数据。那么他们各个模块是怎么协调工作的呢?下面分块来揭秘。

2 线程控制

BitmapHunter implements Runnable
这是一个单独的图片处理的线程单元。 run()方法中调用hunt方法获取bitmap执行的核心代码

Bitmap hunt() throws IOException {    Bitmap bitmap = null;    //优先读取内存    if (shouldReadFromMemoryCache(memoryPolicy)) {      bitmap = cache.get(key);      if (bitmap != null) {        stats.dispatchCacheHit();        loadedFrom = MEMORY;        if (picasso.loggingEnabled) {          log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");        }        return bitmap;      }    }//根据网络状况执行图片的加载    data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;    RequestHandler.Result result = requestHandler.load(data, networkPolicy);    if (result != null) {      loadedFrom = result.getLoadedFrom();      exifOrientation = result.getExifOrientation();      bitmap = result.getBitmap();      // If there was no Bitmap then we need to decode it from the stream.      if (bitmap == null) {        InputStream is = result.getStream();        try {          bitmap = decodeStream(is, data);        } finally {          Utils.closeQuietly(is);        }      }    }    if (bitmap != null) {      if (picasso.loggingEnabled) {        log(OWNER_HUNTER, VERB_DECODED, data.logId());      }      stats.dispatchBitmapDecoded(bitmap);      //图片适配的处理,由于是多线程所以做了同步加锁的处理DECODE_LOCK      if (data.needsTransformation() || exifOrientation != 0) {        synchronized (DECODE_LOCK) {          if (data.needsMatrixTransform() || exifOrientation != 0) {            bitmap = transformResult(data, bitmap, exifOrientation);            if (picasso.loggingEnabled) {              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());            }          }          if (data.hasCustomTransformations()) {            bitmap = applyCustomTransformations(data.transformations, bitmap);            if (picasso.loggingEnabled) {              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");            }          }        }        if (bitmap != null) {          stats.dispatchBitmapTransformed(bitmap);        }      }    }    return bitmap;  }

Dispatch类是一个控制的中心,控制线程的加载和取消、网络监听、消息处理等。

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;    ···    代码省略  }

其构造中获得service 即为PicassoExecutorService ,而PicassoExecutorService 集成自ThreadPoolExecutor,是一个线程池。
Picasso具有根据网络状况控制线程数量的方法就是有PicassoExecutorService来控制完成的

 void adjustThreadCount(NetworkInfo info) {    if (info == null || !info.isConnectedOrConnecting()) {      setThreadCount(DEFAULT_THREAD_COUNT);      return;    }    switch (info.getType()) {      case ConnectivityManager.TYPE_WIFI:      case ConnectivityManager.TYPE_WIMAX:      case ConnectivityManager.TYPE_ETHERNET:        setThreadCount(4);        break;      case ConnectivityManager.TYPE_MOBILE:        switch (info.getSubtype()) {          case TelephonyManager.NETWORK_TYPE_LTE:  // 4G          case TelephonyManager.NETWORK_TYPE_HSPAP:          case TelephonyManager.NETWORK_TYPE_EHRPD:            setThreadCount(3);            break;          case TelephonyManager.NETWORK_TYPE_UMTS: // 3G          case TelephonyManager.NETWORK_TYPE_CDMA:          case TelephonyManager.NETWORK_TYPE_EVDO_0:          case TelephonyManager.NETWORK_TYPE_EVDO_A:          case TelephonyManager.NETWORK_TYPE_EVDO_B:            setThreadCount(2);            break;          case TelephonyManager.NETWORK_TYPE_GPRS: // 2G          case TelephonyManager.NETWORK_TYPE_EDGE:            setThreadCount(1);            break;          default:            setThreadCount(DEFAULT_THREAD_COUNT);        }        break;      default:        setThreadCount(DEFAULT_THREAD_COUNT);    }  }

上一节我们在into方法中提交执行下载enqueueAndSubmit的过程最终交由控制中心Dispatch中performSubmit来完成

void performSubmit(Action action, boolean dismissFailed) {    if (pausedTags.contains(action.getTag())) {    ···    省略代码    hunter = forRequest(action.getPicasso(), this, cache, stats, action);    hunter.future = service.submit(hunter);    hunterMap.put(action.getKey(), hunter);    ···    代码省略    }  }

叫BitmapHunter的线程放入线程池中控制执行
hunter.future = service.submit(hunter);

@Override  public Future<?> submit(Runnable task) {    PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);    execute(ftask);    return ftask;  }

3 回收任务

为了避免oom,缓存中Target使用了weakReference弱引用,方便被系统回收。但是有些Target(比如说ImageView)已经被回收,但是所对应的Request请求还在继续任务(Action),就会浪费资源。Picasso中引入了一个叫CleanupThread的内部线程,CleanupThread是一个daemon线程,它的工作是找到那些Target(比如说ImageView)已经被回收的取消相应的任务Action。

看线程代码

private static class CleanupThread extends Thread {    private final ReferenceQueue<Object> referenceQueue;    private final Handler handler;    CleanupThread(ReferenceQueue<Object> referenceQueue, Handler handler) {      this.referenceQueue = referenceQueue;      this.handler = handler;      setDaemon(true);      setName(THREAD_PREFIX + "refQueue");    }    @Override public void run() {      Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);      while (true) {        try {          // Prior to Android 5.0, even when there is no local variable, the result from          // remove() & obtainMessage() is kept as a stack local variable.          // We're forcing this reference to be cleared and replaced by looping every second          // when there is nothing to do.          // This behavior has been tested and reproduced with heap dumps.          RequestWeakReference<?> remove =              (RequestWeakReference<?>) referenceQueue.remove(THREAD_LEAK_CLEANING_MS);          Message message = handler.obtainMessage();          if (remove != null) {            message.what = REQUEST_GCED;            message.obj = remove.action;            handler.sendMessage(message);          } else {            message.recycle();          }        } catch (InterruptedException e) {          break;        } catch (final Exception e) {          handler.post(new Runnable() {            @Override public void run() {              throw new RuntimeException(e);            }          });          break;        }      }    }    void shutdown() {      interrupt();    }  }

由此看出此线程一直在遍历ReferenceQueue,从中找到这样的reference,就交给handler,handler会从reference中拿到action.
取消:

private void cancelExistingRequest(Object target) {   checkMain();   Action action = targetToAction.remove(target);   if (action != null) {      action.cancel();      dispatcher.dispatchCancel(action);   }   if (target instanceof ImageView) {      ImageView targetImageView = (ImageView) target;      DeferredRequestCreator deferredRequestCreator =            targetToDeferredRequestCreator.remove(targetImageView);     if (deferredRequestCreator != null) {        deferredRequestCreator.cancel();      }   }}

4 LruCache缓存

Picasso 采用LruCache缓存方式,借鉴了volley。本质是使用LinkedHashMap缓存。使用LinkedHashMap是因为其具有存取快,易遍历的数据结构。

this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);

初始化缓存内存的大小,在LurCache初始化的时候可以传入自定义的大小控件。默认的大小为内存的15%。

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);    }    // Target ~15% of the available heap.    return (int) (1024L * 1024L * memoryClass / 7);}

存取很简单就是简单的从map中存取缓存对象。

@Override public void set(String key, Bitmap bitmap) {    if (key == null || bitmap == null) {      throw new NullPointerException("key == null || bitmap == null");    }    Bitmap previous;    //set、put可能为并发的操作,需要同步加锁。    synchronized (this) {      putCount++;      size += Utils.getBitmapBytes(bitmap);      previous = map.put(key, bitmap);      if (previous != null) {        size -= Utils.getBitmapBytes(previous);      }    }    //是否超过最大控件    trimToSize(maxSize);  } 

5 图形变化

图片变化由Transformation定义了接口。交由BitmapHunter的hunt核心代码中执行。

static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) {    for (int i = 0, count = transformations.size(); i < count; i++) {      final Transformation transformation = transformations.get(i);      Bitmap newResult;      try {        newResult = transformation.transform(result);      } catch (final RuntimeException e) {        Picasso.HANDLER.post(new Runnable() {          @Override public void run() {            throw new RuntimeException(                "Transformation " + transformation.key() + " crashed with exception.", e);          }        });        return null;      }      if (newResult == null) {        final StringBuilder builder = new StringBuilder() //            .append("Transformation ")            .append(transformation.key())            .append(" returned null after ")            .append(i)            .append(" previous transformation(s).\n\nTransformation list:\n");        for (Transformation t : transformations) {          builder.append(t.key()).append('\n');        }        Picasso.HANDLER.post(new Runnable() {          @Override public void run() {            throw new NullPointerException(builder.toString());          }        });        return null;      }      if (newResult == result && result.isRecycled()) {        Picasso.HANDLER.post(new Runnable() {          @Override public void run() {            throw new IllegalStateException("Transformation "                + transformation.key()                + " returned input Bitmap but recycled it.");          }        });        return null;      }      // If the transformation returned a new bitmap ensure they recycled the original.      if (newResult != result && !result.isRecycled()) {        Picasso.HANDLER.post(new Runnable() {          @Override public void run() {            throw new IllegalStateException("Transformation "                + transformation.key()                + " mutated input Bitmap but failed to recycle the original.");          }        });        return null;      }      result = newResult;    }    return result;  }

Request 维护了一个图形变换的列表。图片加载成功后 BitmapHunter遍历这个集合完成图形的变换。

1 0