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
- Picasso使用Target无法回调的分析与解决
- Picasso使用Target无法回调的分析与解决
- Picasso的简介与使用
- Picasso使用target不显示图片
- openfilter 断电宕机后,iscsi target 无法使用故障解决一列
- Picasso使用简介及分析
- picasso框架的使用
- Picasso的使用
- picasso的使用
- Picasso框架的使用
- Picasso的使用
- Picasso的使用
- Picasso的使用
- Picasso的基本使用
- Picasso的使用
- Picasso的使用
- Picasso框架的使用
- Picasso的基本使用
- 树链剖分bzoj3626
- Linux中MAVEN环境配置
- 软件工程论文书写设计步骤及如何降低重复率
- 网站 502 解决方法
- ##好好好####知识图谱的应用#######
- Picasso使用Target无法回调的分析与解决
- 是否可能触发滥用#链接低质量站点规则
- mysql数据库基本语法
- c++ 采集nvidia GPU使用率
- 批量重命名
- 杂七杂八
- Ubuntu下无法手动启动VMware Tools安装
- Ubuntu 蓝屏拯救
- Ajax返回xml类型数据