Github: drawee-text-view 分析版本:37c372
drawee-text-view 是一个基于 Fresco 的简易的 spannable TextView。
drawee-text-view
通过 SpannableString
或者 SpannaleStringBuilder
来实现在 TextView 中显示图片,但是系统提供的 ImageSpan
只能获取本地的图片,所以这个基于 Fresco 的 TextView 应运而生。
使用 1 2 3 4 5 6 7 8 9 10 11 12 13 DraweeTextView textview = (DraweeTextView)findViewById(R.id.text); SpannableStringBuilder builder = new SpannableStringBuilder(); builder.append("2333333\n" ) start = builder.length(); builder.append("[emotion:tv_cheers]" ); DraweeSpan span = new DraweeSpan("http://static.yo9.com/web/emotions/tv_cheers.png" ); builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); builder.append("bilibili- ( ゜- ゜)つロ 乾杯~\n" ); textview.setText(builder);
源码 DraweeTextView 一个控件的源码阅读我一般是从构造函数开始,然后再看最常见的暴露出去的 API,所以这里我们从构造函数开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class DraweeTextView extends TextView { public DraweeTextView (Context context) { super (context); } public DraweeTextView (Context context, AttributeSet attrs) { super (context, attrs); } public DraweeTextView (Context context, AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); } @TargetApi (Build.VERSION_CODES.LOLLIPOP) public DraweeTextView (Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super (context, attrs, defStyleAttr, defStyleRes); } }
这里看到 DraweeTextView
是继承于 TextView ,那么再看看覆盖了哪些 API :
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 public class DraweeTextView extends TextView { private boolean mHasDraweeInText; @Override protected void onAttachedToWindow () { super .onAttachedToWindow(); onAttach(); } @Override public void onFinishTemporaryDetach () { super .onFinishTemporaryDetach(); onAttach(); } @Override protected void onDetachedFromWindow () { super .onDetachedFromWindow(); onDetach(); } @Override public void onStartTemporaryDetach () { super .onStartTemporaryDetach(); onDetach(); } @Override public void invalidateDrawable (Drawable dr) { if (mHasDraweeInText) { * hard to know what the bounds of drawables actually is. */ invalidate(); } else { super .invalidateDrawable(dr); } } * Attach DraweeSpans in text */ final void onAttach () { DraweeSpan[] images = getImages(); for (DraweeSpan image : images) { image.onAttach(this ); } } private DraweeSpan[] getImages() { if (mHasDraweeInText && length() > 0 ) return ((Spanned) getText()).getSpans(0 , length(), DraweeSpan.class); return new DraweeSpan[0 ]; } * Detach all of the DraweeSpans in text */ final void onDetach () { DraweeSpan[] images = getImages(); for (DraweeSpan image : images) { Drawable drawable = image.getDrawable(); if (drawable != null ) { unscheduleDrawable(drawable); } image.onDetach(); } } @Override public void setText (CharSequence text, BufferType type) { if (mHasDraweeInText) { onDetach(); mHasDraweeInText = false ; } if (text instanceof Spanned) { DraweeSpan[] spans = ((Spanned) text).getSpans(0 , text.length(), DraweeSpan.class); mHasDraweeInText = spans.length > 0 ; } super .setText(text, type); } }
从整个 View 的生命周期看,在生命开始的时候调用了 onAttach()
,在生命结束的时候调用了 onDetach()
。
在 onAttach
的时候去获取当前内容里面有多少 DraweeSpan
这个 Span ,然后遍历 DraweeSpan
数组,调用 DraweeSpan#onAttach(View)
。
在 onDetach
的时候同样去获取内容中有多少 DraweeSpan
这个 Span ,然后遍历 DraweeSpan
数组,调用 DraweeSpan#getDrawable()
得到 Drawable ,再通过 unscheduleDrawable()
来释放,最后再调用 DraweeSpan#onDetach();
。
而 invalidateDrawable(Drawable)
是 Drawable.Callback 的方法,TextView 实现了该接口。如果被调用,那么就去判断 mHasDraweeInText
,如果为 true 的话进行 View 的刷新。
另外一个 API 是 setText(CharSequence, BufferType)
,当外部每次调用之后,都会去判断一下 mHasDraweeInText
参数,如果为 true 的话,说明之前处理过 drawable ,那么先将之前的 drawable 回收掉,再去重新处理新数据里面的 DraweeSpan
数据。
DraweeSpan 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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 public class DraweeSpan extends DynamicDrawableSpan implements DeferredReleaser .Releasable { private final DeferredReleaser mDeferredReleaser; private final ForwardingDrawable mActualDrawable; private CloseableReference<CloseableImage> mFetchedImage; private DataSource<CloseableReference<CloseableImage>> mDataSource; private boolean mIsRequestSubmitted; private Drawable mDrawable; private Drawable mPlaceHolder; private View mAttachedView; private String mImageUri; private boolean mIsAttached; * @see ImageRequest#fromUri(String) * @see com.facebook.common.util.UriUtil */ public DraweeSpan (String uri) { this (uri, createEmptyDrawable()); } public DraweeSpan (String uri, Drawable placeHolder) { super (ALIGN_BOTTOM); mImageUri = uri; mDeferredReleaser = DeferredReleaser.getInstance(); mPlaceHolder = placeHolder; mActualDrawable = new ForwardingDrawable(mPlaceHolder); Rect bounds = mPlaceHolder.getBounds(); if (bounds.right == 0 || bounds.bottom == 0 ) { mActualDrawable.setBounds(0 , 0 , mPlaceHolder.getIntrinsicWidth(), mPlaceHolder.getIntrinsicHeight()); } else { mActualDrawable.setBounds(bounds); } } * 创建一个透明的ColorDrawable来当占位Drawable * * @return */ private static Drawable createEmptyDrawable () { ColorDrawable d = new ColorDrawable(Color.TRANSPARENT); d.setBounds(0 , 0 , 100 , 100 ); return d; } @Override public Drawable getDrawable () { return mActualDrawable; } @Override public void release () { mIsRequestSubmitted = false ; mIsAttached = false ; mAttachedView = null ; if (mDataSource != null ) { mDataSource.close(); mDataSource = null ; } if (mDrawable != null ) { releaseDrawable(mDrawable); } mDrawable = null ; if (mFetchedImage != null ) { CloseableReference.closeSafely(mFetchedImage); mFetchedImage = null ; } } void releaseDrawable (@Nullable Drawable drawable) { if (drawable instanceof DrawableWithCaches) { ((DrawableWithCaches) drawable).dropCaches(); } } }
DraweeSpan
继承于 DynamicDrawableSpan
,需要覆盖 getDrawable()
方法,而在 getDrawable()
中返回的是 ForwardingDrawable
。DraweeSpan
还实现了 DeferredReleaser.Releasable
接口,覆盖 release()
方法,实现了资源的释放。
那么我们可以从 onAttach(View)
开始看:
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 68 69 70 71 72 73 public class DraweeSpan extends DynamicDrawableSpan implements DeferredReleaser .Releasable { public void onAttach (@NonNull View view) { mIsAttached = true ; if (mAttachedView != view) { mActualDrawable.setCallback(null ); if (mAttachedView != null ) { throw new IllegalStateException("has been attached to view:" + mAttachedView); } mAttachedView = view; mActualDrawable.setCallback(mAttachedView); } mDeferredReleaser.cancelDeferredRelease(this ); if (!mIsRequestSubmitted) { try { ImagePipelineFactory.getInstance(); } catch (NullPointerException e) { ImagePipelineFactory.initialize(mAttachedView.getContext().getApplicationContext()); } submitRequest(); } } private void submitRequest () { mIsRequestSubmitted = true ; final String id = getId(); mDataSource = ImagePipelineFactory.getInstance().getImagePipeline() .fetchDecodedImage(ImageRequest.fromUri(getImageUri()), null ); DataSubscriber<CloseableReference<CloseableImage>> subscriber = new BaseDataSubscriber<CloseableReference<CloseableImage>>() { @Override protected void onNewResultImpl (DataSource<CloseableReference<CloseableImage>> dataSource) { boolean isFinished = dataSource.isFinished(); CloseableReference<CloseableImage> result = dataSource.getResult(); if (result != null ) { onNewResultInternal(id, dataSource, result, isFinished); } else if (isFinished) { onFailureInternal(id, dataSource, new NullPointerException(), true ); } } @Override protected void onFailureImpl (DataSource<CloseableReference<CloseableImage>> dataSource) { onFailureInternal(id, dataSource, dataSource.getFailureCause(), true ); } }; mDataSource.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance()); } protected String getId () { return String.valueOf(getImageUri().hashCode()); } @NonNull protected String getImageUri () { return mImageUri; } }
当进行 onAttach(View)
操作之后,先判断设置进来的 View 和之前设置的是否一样,然后判断是否进行了 io 操作了,如果没有的话通过 Fresco 进行 io 操作。使用的是 Fresco 的 ImagePipelineFactory
进行处理,对结果是否成功来分别处理。
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 public class DraweeSpan extends DynamicDrawableSpan implements DeferredReleaser .Releasable { * 结果是成功的 * * @param id * @param dataSource * @param result * @param isFinished */ private void onNewResultInternal (String id, DataSource<CloseableReference<CloseableImage>> dataSource, CloseableReference<CloseableImage> result, boolean isFinished) { if (!getId().equals(id) || dataSource != mDataSource || !mIsRequestSubmitted) { CloseableReference.closeSafely(result); dataSource.close(); return ; } Drawable drawable; try { drawable = createDrawable(result); } catch (Exception exception) { CloseableReference.closeSafely(result); onFailureInternal(id, dataSource, exception, isFinished); return ; } CloseableReference previousImage = mFetchedImage; Drawable previousDrawable = mDrawable; mFetchedImage = result; try { if (isFinished) { mDataSource = null ; setImageWithIntrinsicBounds(drawable); } } finally { if (previousDrawable != null && previousDrawable != drawable) { releaseDrawable(previousDrawable); } if (previousImage != null && previousImage != result) { CloseableReference.closeSafely(previousImage); } } } private Drawable createDrawable (CloseableReference<CloseableImage> result) { CloseableImage closeableImage = result.get(); if (closeableImage instanceof CloseableStaticBitmap) { CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) closeableImage; BitmapDrawable bitmapDrawable = createBitmapDrawable(closeableStaticBitmap.getUnderlyingBitmap()); return (closeableStaticBitmap.getRotationAngle() != 0 && closeableStaticBitmap.getRotationAngle() != -1 ? new OrientedDrawable(bitmapDrawable, closeableStaticBitmap.getRotationAngle()) : bitmapDrawable); } else if (closeableImage instanceof CloseableAnimatedImage) { AnimatedImageResult image = ((CloseableAnimatedImage) closeableImage).getImageResult(); int frame = image.getFrameForPreview(); CloseableReference<Bitmap> bitmap; if (frame >= 0 ) { bitmap = image.getDecodedFrame(frame); } else { bitmap = image.getPreviewBitmap(); } if (bitmap != null && bitmap.get() != null ) { BitmapDrawable bitmapDrawable = createBitmapDrawable(bitmap.get()); return bitmapDrawable; } } throw new UnsupportedOperationException("Unrecognized image class: " + closeableImage); } protected BitmapDrawable createBitmapDrawable (Bitmap bitmap) { BitmapDrawable drawable; if (mAttachedView != null ) { final Context context = mAttachedView.getContext(); drawable = new BitmapDrawable(context.getResources(), bitmap); } else { drawable = new BitmapDrawable(null , bitmap); } return drawable; } public void setImageWithIntrinsicBounds (Drawable drawable) { if (mDrawable != drawable) { releaseDrawable(mDrawable); mActualDrawable.setDrawable(drawable); mDrawable = drawable; } } void releaseDrawable (@Nullable Drawable drawable) { if (drawable instanceof DrawableWithCaches) { ((DrawableWithCaches) drawable).dropCaches(); } } }
上面是结果返回成功之后的处理,最主要的就是将得到的 drawable 赋值给 ForwardingDrawable
,而这里最主要的就是这个 ForwardingDrawable
,可以看一下:
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 public class ForwardingDrawable extends Drawable implements Drawable .Callback , TransformCallback , TransformAwareDrawable , DrawableParent { @Override public Drawable setDrawable (Drawable newDrawable) { return setCurrent(newDrawable); } * Sets a new drawable to be the delegate, and returns the old one (or null). * * <p>This method will cause the drawable to be invalidated. * @param newDelegate * @return the previous delegate */ public Drawable setCurrent (Drawable newDelegate) { Drawable previousDelegate = setCurrentWithoutInvalidate(newDelegate); invalidateSelf(); return previousDelegate; } * As {@code setCurrent}, but without invalidating a drawable. Subclasses are responsible to call * {@code invalidateSelf} on their own. * @param newDelegate * @return the previous delegate */ protected Drawable setCurrentWithoutInvalidate (Drawable newDelegate) { Drawable previousDelegate = mCurrentDelegate; DrawableUtils.setCallbacks(previousDelegate, null , null ); DrawableUtils.setCallbacks(newDelegate, null , null ); DrawableUtils.setDrawableProperties(newDelegate, mDrawableProperties); DrawableUtils.copyProperties(newDelegate, previousDelegate); DrawableUtils.setCallbacks(newDelegate, this , this ); mCurrentDelegate = newDelegate; return previousDelegate; } * Use the current {@link Callback} implementation to have this Drawable * redrawn. Does nothing if there is no Callback attached to the * Drawable. * * @see Callback#invalidateDrawable * @see #getCallback() * @see #setCallback(android.graphics.drawable.Drawable.Callback) */ public void invalidateSelf () { final Callback callback = getCallback(); if (callback != null ) { callback.invalidateDrawable(this ); } } }
DrawableUtils.copyProperties(newDelegate, previousDelegate);
可以看这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class DrawableUtils { * Copies various properties from one drawable to the other. * @param to drawable to copy properties to * @param from drawable to copy properties from */ public static void copyProperties (Drawable to, Drawable from) { if (from == null || to == null || to == from) { return ; } to.setBounds(from.getBounds()); to.setChangingConfigurations(from.getChangingConfigurations()); to.setLevel(from.getLevel()); to.setVisible(from.isVisible(), false ); to.setState(from.getState()); } }
先将 from 的信息复制给 to ,然后调用 invalidateSelf()
,就通知到了 DraweeTextView
中刷新自己。
io 操作失败的话处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class DraweeSpan extends DynamicDrawableSpan implements DeferredReleaser .Releasable { private void onFailureInternal (String id, DataSource<CloseableReference<CloseableImage>> dataSource, Throwable throwable, boolean isFinished) { if (BuildConfig.DEBUG) { Log.e("ImageSpan" , id + " load failure" , throwable); } if (!getId().equals(id) || dataSource != mDataSource || !mIsRequestSubmitted) { dataSource.close(); return ; } if (isFinished) { mDataSource = null ; if (mDrawable != null ) { mActualDrawable.setDrawable(mDrawable); } } } }
还是将原来的数据设置进去。
当 View 从 Activity 或者 Fragment 中 detach 的时候,会调用 DraweeSpan
的 onDetach
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class DraweeSpan extends DynamicDrawableSpan implements DeferredReleaser .Releasable { public void onDetach () { if (!mIsAttached) { return ; } mActualDrawable.setCallback(null ); mAttachedView = null ; reset(); mDeferredReleaser.scheduleDeferredRelease(this ); } public void reset () { mActualDrawable.setDrawable(mPlaceHolder); } }
总结
资源的释放很关键,在 DraweeSpan
中体现的很明显
几个关键的回调很重要,不然无法刷新 TextView 中的 Drawable
将 Fresco 替换掉其实也可以的,自己写 io 操作,主要是 ForwardingDrawable
中的那几部关键操作