Picasso使用Target无法回调的分析与解决

来源:互联网 发布:java class method 编辑:程序博客网 时间:2024/05/17 04:53

注:文章转载自文章转载自RowandJJ的博客:http://blog.csdn.net/chdjj/article/details/49964901

    • 一异步回调的陷阱
    • 二解决方案
      • 1 阻止gc不建议
      • 2 使用get的方式获取Bitmap

在加载图片的场景中,有时需要异步拿到Bitmap做一些操作:bitmap预热、bitmap裁剪等,当加载成功的时候通过回调的形式来获取Bitmap,然后进行处理。Picasso提供了一种回调的方式获取Bitmap。客户端实现Target接口即可在加载成功的时候通过回调的方式返回bitmap。代码如下:

Picasso.with(context).load(url).into(new Target() {  @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {    //加载成功,进行处理  }  @Override public void onBitmapFailed(Drawable errorDrawable) {    //加载失败  }  @Override public void onPrepareLoad(Drawable placeHolderDrawable) {    //开始加载  }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

通过上面的回调函数,我们就可以获取Bitmap,然后进行bitmap的自定义处理。
但是有时候回调却没有触发,也没有异常,最后开启Picasso日志,才发现target引用被gc掉了:
这里写图片描述

一、异步回调的陷阱

后面查看源码之后才发现,由于Picasso将target引用包装成了一个弱引用,当gc发生时target引用就很可能被回收从而无法回调。
首先,先看into(target)源码:

public void into(Target target) {//代码省略....//将target作为参数,实例化一个targetAction,此处Action表示picasso的一个抽象行为。Action action = new TargetAction(picasso, target, request, memoryPolicy, networkPolicy, errorequestKey, tag, errorResId);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这里我们可以看到,首先picasso会判断是否从内存中读取,如果不从内存中读取,那么就创建一个新的Action任务,将target作为参数给TargetAction持有。重要关注TargetAction这个类,我们再看一看TargetAction类的构造有什么内容:

final class TargetAction extends Action<Target> {   TargetAction(Picasso picasso, Target target, Request data, int memoryPolicy,Drawable errorDrawable, String key, Object tag, int errorResId) {      super(picasso, target, data, memoryPolicy, networkPolicy, errorResId, errorDraw,false);   }// 代码省略}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这里可以看到,TargetAction继承了Action类,target引用传给了父类Action的构造函数:

abstract class Action<T> {//picasso实现的弱引用  static class RequestWeakReference<M> extends WeakReference<M> {    final Action action;    public RequestWeakReference(Action action, M referent, ReferenceQueue<? super>){       super(referent, q);       this.action = action;    }  }  final Picasso picasso;  final Request request;  final WeakReference<T> target;  final boolean noFade;  Action(Picasso picasso, T target, Request request, int memoryPolicy, int network,int errorResId, Drawable errorDrawable, String key, Object tag, boolean ){    this.picasso = picasso;    this.request = request;    //如果target不是null,那么就将其包裹为弱引用!同时关联到    //picasso的referenceQueue中。    this.target = target == null ? null : new         RequestWeakReference<T>(this, target,                picasso.referenceQueue);    //...省略}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

在Action的构造函数中将target包裹为弱引用,同时关联至picasso的referenceQueue中。这里原因已经出来了,就是因为target是弱引用,因此无法阻止正常的gc过程,只要回调之前发生了gc回收,那么target很有可能就被回收掉了。一旦target被回收,那么也就无法回调了。
将target的弱引用关联至Picasso.referenceQueue是为了监听target被回收的状态,Picasso有一个专门监听target引用的线程CleanupThread,该线程会将监听到的GC事件传递给Picasso的Handler:

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 {       //这里开启了一个死循环,每秒钟从referenceQueue中拿到被       //gc标志的target引用       RequestWeakReference<?> remove =       referenceQueue.remove(THREAD_LEAK_CLEANING_M);       Message message = handler.obtainMessage();       //如果引用尚未为空,说明尚未gc掉(但仍然会gc),则发出被       //GC的通知,REQUEST_GCED通知       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;  } }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

该线程从Picasso构造函数起执行:

Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,...){   //省略  //创建引用队列,被gc标志的引用在被gc前都会首加入其中  this.referenceQueue = new ReferenceQueue<Object>();  //创建并执行监听线程  this.cleanupThread = new   CleanupThread(referenceQueue, HANDLER);  this.cleanupThread.start();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

当Picasso的Handler收到REQUEST_GCED消息时会撤销当前请求:

static final Handler HANDLER = new Handler(Looper.getMainLooper()) {  @Override   public void handleMessage(Message msg) {    switch (msg.what) {      //图片加载成功      case HUNTER_BATCH_COMPLETE: {      @SuppressWarnings("unchecked")       List<BitmapHunter> batch = (List<Action>) msg.obj;      //noinspection ForLoopReplaceableByForEach      //发起通知     for (int i = 0, n = batch.size(); i < n; i++) {       BitmapHunter hunter = batch.get(i);       hunter.picasso.complete(hunter);     }     break;   }   //GC消息   case REQUEST_GCED: {     Action action = (Action) msg.obj;     if (action.getPicasso().loggingEnabled) {       log(OWNER_MAIN, VERB_CANCELED, action.request.logId(), "target got garbage collected!");     }   //取消当前请求    action.picasso.cancelExistingRequest(action.getTarget());    break;   }   case REQUEST_BATCH_RESUME:   @SuppressWarnings("unchecked")    List<Action> batch = (List<Action>) msg.obj;   //noinspection ForLoopReplaceableByForEach   for (int i = 0, n = batch.size(); i < n; i++) {     Action action = batch.get(i);     action.picasso.resumeAction(action);   }   break;   default:     throw new AssertionError("Unknown handler message      received: " + msg.what);   } }};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

从上面的分析我们可以得出结论:使用Target获取bitmap并不保险,无法保证一定能够获得Bitmap。

二、解决方案

2.1 阻止gc(不建议)

既然是因为弱引用造成的gc,那么让系统无法将target进行gc就可以了。开发者在加载图片的周期内持有target的强引用,在获取到bitmap之后再将其释放即可。但是这样违背了设计者的设计初衷,也容易引发内存泄漏的问题,原本设计者就是想让target异步回调的形式不影响正常的gc回调。

设计者的原因很简单:如果一个view实现了target接口,那么view的生命周期就会被target影响,造成内存泄漏。
比如:在图片加载期间,View可能已经离开了屏幕,将要被回收;或者Activity将要被销毁。但是由于picasso还没有加载完成,持有着view的引用,而view又持有Activity的引用,造成View和Activity都无法被回收。

2.2 使用get()的方式获取Bitmap

除了使用Target来进行异步获取,Picasso还提供了一个get()方法,进行同步的获取:

public Bitmap get() throws IOException {   //省略...   Request finalData = createRequest(started);   String key = createKey(finalData, new StringBuilder());   Action action = new GetAction(picasso, finalData, memoryPolicy, networkPolicy, tBitmapHunter);   //forRequest(xxx)返回的是一个BitmapHunter(继承了     Runnable),直接调用其中的hunt()方法获   return forRequest(picasso, picasso.dispatcher, picasso.cache, picasso.stats,...);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

BitmapHunter:

class BitmapHunter implements Runnable {  //...此处省略N行代码  //获取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 : networkPoli  RequestHandler.Result result =   requestHandler.load(data, networkPolicy);  if (result != null) {    loadedFrom = result.getLoadedFrom();    exifRotation = 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);      }    }  }  //bitmap的解码、transform操作  if (bitmap != null) {     if (picasso.loggingEnabled) {       log(OWNER_HUNTER, VERB_DECODED, data.logId());     }     stats.dispatchBitmapDecoded(bitmap);     if (data.needsTransformation() || exifRotation != 0) {       synchronized (DECODE_LOCK) {       if (data.needsMatrixTransform() || exifRotation != 0){         bitmap = transformResult(data, bitmap, exifRotation);         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 transformation");           }        }       }       if (bitmap != null) {          stats.dispatchBitmapTransformed(bitmap);       }    }  }  return bitmap;  }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

我们如果想通过get来实现异步获取,那么就使用一个线程池进行get()方法调用就可以了:

/** * 同步获取Bitmap,这种方式会在子线程当中同步去获取Bitmap,不会采用回调的方式,也不会存在引用被 * 要么获取成功;要么获取失败;或者抛出异常。 */ private void fetchBySync(IFacadeBitmapCallback target) {     threadPoolExecutor.submit(() -> {        Bitmap bitmap = null;        try {          bitmap = requestCreator.get();        } catch (IOException e) {           e.printStackTrace();           target.onBitmapFailed(path, e);        }        if (bitmap == null) {           Log.e(getClass().getSimpleName(), "bitmap is null");           target.onBitmapFailed(path, null);        } else {           Log.e(getClass().getSimpleName(), "bitmap " + bitmap.getClass().getSimpleName());           target.onBitmapLoaded(path, bitmap);        }  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21


原创粉丝点击