Alec's blog

唯纯粹与热爱不可辜负

0%

异步回调信息按序到达解决方案

更新

前段时间面试和面试官聊到了这个问题,下文解决方案本质就是一个设置锚点,由此去处理回调的过程。这样看,整件事情就的就变得很简单了。

稍微复杂点的就是,这个锚点并不是在开启异步任务时可以确定的,插入一张图片的position是受到它前边的图片的影响的。ImageSpan插入EditText中有占位的String的(一般设置为图片的url),那么图片插入和未插入会影响到下一张图的position。因此设置一个标志类还是有必要的。

情景

在尝试自定义一个Html.TagHandler解析一段图文混排的文本时遇到一个问题:IO线程加载多张图像时,它们的回调时间是不可控的,导致他们插入到文本中的位置出错。

在这里插入图片描述

问题描述

  1. 显然我们的问题是:回调的图像显示在图文中时要求能够保持原来的模样,即实现异步回调信息按序到达。
  2. 这个问题不仅在加载图像时出现,当同时在子线程执行多个耗时操作但又希望它们按序返回结果,因为任务耗时时长不同,就会出现回调顺序不一样的问题。

解决思路

这个问题让我想到了计算机网络中,TCP实现可靠传输中的按序到达。这两者的问题是一样的,由于网络阻塞等因素,每一个TCP包到达接收端的时间是不一样的。计网通过给每一个TCP包编号,确定它们的顺序,出现丢包则要求重传。

知道每个包的编号是确定顺序的关键。

我们也可以对每个回调做一些标记信息,再按照这些标记信息去做相应的处理。

那么问题解决模型就清晰了:

  1. 为每个任务做标记(编号)
  2. 根据标记信息做相应处理

比如有两张图片:图片一和图片二(按序),即便图片二先回调,那么我只要:

  1. 知道它当前应该插入文章的位置(做标记);
  2. 根据位置插入图文(根据标记信息做相应处理);

等到图片一回调时,再用同样的方法插入图片即可。

实践

做一个标记信息类

1
2
3
4
5
private inner class ImageControlBlock(
val startIndex: Int, //图片插入的初始位置
val src: String,
var isInsert: Boolean = false //记录图片是否已经被插入
)

具体逻辑

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
//加载图片前先标记图片信息(做标记)
val src = attributes["src"]?.get(0) ?: ""
val position = arrICB.size
arrICB.add(ImageControlBlock(startIndex, src))

//IO线程加载图片
val scope = CoroutineUtil.getScope(editText.hashCode())
val deferred = scope.async(Dispatchers.IO) {
drawableGet.getDrawable(src)
}

//主线程更新UI
scope.launch(Dispatchers.Main) {

//获取回调
val drawable = deferred.await()

//构建imageSpan
val width = drawable.intrinsicWidth
val height = drawable.intrinsicHeight
drawable.setBounds(0, 0, if (width > 0) width else 0, if (height > 0) height else 0)
val imageSpan = ClickableImageSpan(drawable, src)
val spannableString = SpannableString(src)
spannableString.setSpan(
imageSpan,
0,
spannableString.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)

//根据标记信息,做相应的处理
//先寻找真实插入位置:初始位置加上偏移位置。
var insertIndex = arrICB[position].startIndex //插入位置
for (i in 0 until position){
if(arrICB[i].isInsert){
insertIndex += arrICB[i].src.length
}
}
//插入图片
editText.editableText.insert(insertIndex, spannableString)
//记录该图片已被插入
arrICB[position].isInsert = true
}

最后

遇到难题时,可以想一下有没有其它类似的问题?能否将问题转化成另一种简单的模型,将复杂的问题简化。