Picasso使用Target无法回调的分析与解决
2017-06-14 19:44
344 查看
一异步回调的陷阱
二解决方案
1 阻止gc不建议
2 使用get的方式获取Bitmap
在加载图片的场景中,有时需要异步拿到Bitmap做一些操作:bitmap预热、bitmap裁剪等,当加载成功的时候通过回调的形式来获取Bitmap,然后进行处理。Picasso提供了一种回调的方式获取Bitmap。客户端实现Target接口即可在加载成功的时候通过回调的方式返回bitmap。代码如下:
通过上面的回调函数,我们就可以获取Bitmap,然后进行bitmap的自定义处理。
但是有时候回调却没有触发,也没有异常,最后开启Picasso日志,才发现target引用被gc掉了:
![](http://img.blog.csdn.net/20170614185843886?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd29zaGltYWxpbmd5aQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
首先,先看into(target)源码:
这里我们可以看到,首先picasso会判断是否从内存中读取,如果不从内存中读取,那么就创建一个新的Action任务,将target作为参数给TargetAction持有。重要关注TargetAction这个类,我们再看一看TargetAction类的构造有什么内容:
这里可以看到,TargetAction继承了Action类,target引用传给了父类Action的构造函数:
在Action的构造函数中将target包裹为弱引用,同时关联至picasso的referenceQueue中。这里原因已经出来了,就是因为target是弱引用,因此无法阻止正常的gc过程,只要回调之前发生了gc回收,那么target很有可能就被回收掉了。一旦target被回收,那么也就无法回调了。
将target的弱引用关联至Picasso.referenceQueue是为了监听target被回收的状态,Picasso有一个专门监听target引用的线程CleanupThread,该线程会将监听到的GC事件传递给Picasso的Handler:
该线程从Picasso构造函数起执行:
当Picasso的Handler收到REQUEST_GCED消息时会撤销当前请求:
从上面的分析我们可以得出结论:使用Target获取bitmap并不保险,无法保证一定能够获得Bitmap。
设计者的原因很简单:如果一个view实现了target接口,那么view的生命周期就会被target影响,造成内存泄漏。
比如:在图片加载期间,View可能已经离开了屏幕,将要被回收;或者Activity将要被销毁。但是由于picasso还没有加载完成,持有着view的引用,而view又持有Activity的引用,造成View和Activity都无法被回收。
BitmapHunter:
我们如果想通过get来实现异步获取,那么就使用一个线程池进行get()方法调用就可以了:
二解决方案
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) { //开始加载 } });
通过上面的回调函数,我们就可以获取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); }
这里我们可以看到,首先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); } // 代码省略 }
这里可以看到,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); //...省略 }
在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; } } }
该线程从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(); }
当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); } } };
从上面的分析我们可以得出结论:使用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,...); }
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; } }
我们如果想通过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); } }
相关文章推荐
- Picasso使用Target无法回调的分析与解决
- Apache服务器无法使用的问题分析及解决方法
- 使用ajaxfileupload.js上传无法进回调问题终极解决
- MFC使用CEF并实现js与C++交互功能,解决Render进程中OnContextCreated绑定与OnWebKitInitialized的js扩展无法回调问题
- 使用线程回调解决子线程无法操作主线程中定义的控件
- WPF应用无法使用Snoop分析的解决办法
- MFC使用CEF并实现js与C++交互功能,解决Render进程中OnContextCreated绑定与OnWebKitInitialized的js扩展无法回调问题
- GridView导出Excel使用UpdatePanel导致“无法分析从服务器收到的消息”解决方法.
- JS和jQuery使用submit方法无法提交表单的原因分析及解决办法
- 部分地区移动手机无法使用QQ邮箱IMAP收件原因的分析[附解决方法]
- ecplise3.2在安装Oracle9i后无法使用的解决方法。
- VSTS有Bug,分析数据库的维度和维度属性使用中文命名时候,作为报表参数会出错。虽然有解决办法但是头大。
- 使用installshild解决某些安装文件无法在win2003上安装的问题
- 无法使用POP方式收取Gmail邮件的解决方法【应用】
- 在ASP.net中使用TinyMCE的小tip-解决无法使用中文语言包
- 使用LdapContext的modifyAttributes方法无法更改用户密码的解决
- 安装完vs.2005之后,重新安装iis后无法使用http方式访问asp.net工程的页面的问题的解决方法
- ASP.NET2.0中"无法显示 XML 页。 使用 XSL 样式表无法查看 XML 输入。"问题的解决
- [导入]使用installshild解决某些安装文件无法在win2003上安装的问题
- word2003符号栏无法使用的解决方法