Github: drawee-text-view 分析版本:37c372

drawee-text-view 是一个基于 Fresco 的简易的 spannable TextView。

drawee-text-view

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;

//------ 与View的生命周期相关 -----
@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();
}
//------ 与View的生命周期相关 -----

@Override
public void invalidateDrawable(Drawable dr) {
if (mHasDraweeInText) {
/* invalidate the whole view in this case because it's very
* 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]; //TODO: pool empty typed array
}

/**
* Detach all of the DraweeSpans in text
*/

final void onDetach() {
DraweeSpan[] images = getImages();
for (DraweeSpan image : images) {
Drawable drawable = image.getDrawable();
// reset callback first
if (drawable != null) {
unscheduleDrawable(drawable);
}
image.onDetach();
}
}

@Override
public void setText(CharSequence text, BufferType type) {
if (mHasDraweeInText) {
onDetach(); // detach all old images
mHasDraweeInText = false;
}
if (text instanceof Spanned) {
// find DraweeSpan in text
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;//Fresco的访问资源
private boolean mIsRequestSubmitted;//是否进行了IO操作
private Drawable mDrawable;//最终的drawable
private Drawable mPlaceHolder;//占位
private View mAttachedView;//TextView
private String mImageUri;//地址
private boolean mIsAttached;//是否在TextView中了

/**
* @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;
//Fresco中的类,作用发送一个Message到主Looper中,当Looper循环分发Message出去时候进行回调
mDeferredReleaser = DeferredReleaser.getInstance();
//占位Drawable
mPlaceHolder = placeHolder;
// create forwarding drawable with placeholder
//创建ForwardingDrawable
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() 中返回的是 ForwardingDrawableDraweeSpan 还实现了 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) {
//是否attach到View上的判断标志位
mIsAttached = true;
//第一次进来mAttachedView为null,那么肯定不相等
if (mAttachedView != view) {
mActualDrawable.setCallback(null);
if (mAttachedView != null) {
throw new IllegalStateException("has been attached to view:" + mAttachedView);
}
mAttachedView = view;
mActualDrawable.setCallback(mAttachedView);
}
//取消release的回调
mDeferredReleaser.cancelDeferredRelease(this);
//如果没有进行IO操作
if (!mIsRequestSubmitted) {
try {
ImagePipelineFactory.getInstance();
} catch (NullPointerException e) {
// Image pipeline is not initialized
ImagePipelineFactory.initialize(mAttachedView.getContext().getApplicationContext());
}
submitRequest();
}
}

private void submitRequest() {
//IO操作标志位设置为true
mIsRequestSubmitted = true;
//通过URI的hashcode得到的当做id
final String id = getId();
//得到ImagePipeline
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(), /* isFinished */ true);
}
}

@Override
protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
//失败
onFailureInternal(id, dataSource, dataSource.getFailureCause(), /* isFinished */ 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) {
// ignored this result
//如果数据不对的话,忽略
if (!getId().equals(id)
|| dataSource != mDataSource
|| !mIsRequestSubmitted) {
CloseableReference.closeSafely(result);
dataSource.close();
return;
}
//得到drawable
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
mFetchedImage = result;
try {
// set the new image
//设置新的drawable
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 closeableImage = result.get();
//如果是CloseableStaticBitmap的话
if (closeableImage instanceof CloseableStaticBitmap) {
//强转
CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) closeableImage;
//得到BitmapDrawable
BitmapDrawable bitmapDrawable = createBitmapDrawable(closeableStaticBitmap.getUnderlyingBitmap());
//判断方向,返回旋转后的drawable
return (closeableStaticBitmap.getRotationAngle() != 0 && closeableStaticBitmap.getRotationAngle() != -1
? new OrientedDrawable(bitmapDrawable, closeableStaticBitmap.getRotationAngle()) : bitmapDrawable);
} else if (closeableImage instanceof CloseableAnimatedImage) {//如果是CloseableAnimatedImage类型,比如gif?
//得到结果
AnimatedImageResult image = ((CloseableAnimatedImage) closeableImage).getImageResult();
//获得帧数
int frame = image.getFrameForPreview();
CloseableReference<Bitmap> bitmap;
//得到最后一帧的bitmap或者预览帧
if (frame >= 0) {
bitmap = image.getDecodedFrame(frame);
} else {
bitmap = image.getPreviewBitmap();
}
if (bitmap != null && bitmap.get() != null) {
//转成BitmapDrawable
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 {
// can't happen
drawable = new BitmapDrawable(null, bitmap);
}
return drawable;
}

public void setImageWithIntrinsicBounds(Drawable drawable) {
if (mDrawable != drawable) {
//释放之前的drawable
releaseDrawable(mDrawable);
//将新的drawable设置给ForwardingDrawable
mActualDrawable.setDrawable(drawable);
//赋值给成员变量mDrawable
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) {
//这里返回到DraweeTextView中,刷新自己
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(), /* restart */ 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);
}
// ignored this result
if (!getId().equals(id)
|| dataSource != mDataSource
|| !mIsRequestSubmitted) {
dataSource.close();
return;
}
if (isFinished) {
mDataSource = null;
// Set the previously available image if available.
if (mDrawable != null) {
mActualDrawable.setDrawable(mDrawable);
}
}
}
}

还是将原来的数据设置进去。

当 View 从 Activity 或者 Fragment 中 detach 的时候,会调用 DraweeSpanonDetach

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();
//释放,调用release()
mDeferredReleaser.scheduleDeferredRelease(this);
}

public void reset() {
mActualDrawable.setDrawable(mPlaceHolder);
}
}

总结

  1. 资源的释放很关键,在 DraweeSpan 中体现的很明显
  2. 几个关键的回调很重要,不然无法刷新 TextView 中的 Drawable
  3. 将 Fresco 替换掉其实也可以的,自己写 io 操作,主要是 ForwardingDrawable 中的那几部关键操作