更新
前段时间面试和面试官聊到了这个问题,下文解决方案本质就是一个设置锚点,由此去处理回调的过程。这样看,整件事情就的就变得很简单了。
稍微复杂点的就是,这个锚点并不是在开启异步任务时可以确定的,插入一张图片的position是受到它前边的图片的影响的。ImageSpan插入EditText中有占位的String的(一般设置为图片的url),那么图片插入和未插入会影响到下一张图的position。因此设置一个标志类还是有必要的。
情景
在尝试自定义一个Html.TagHandler解析一段图文混排的文本时遇到一个问题:IO线程加载多张图像时,它们的回调时间是不可控的,导致他们插入到文本中的位置出错。
问题描述
- 显然我们的问题是:回调的图像显示在图文中时要求能够保持原来的模样,即实现异步回调信息按序到达。
- 这个问题不仅在加载图像时出现,当同时在子线程执行多个耗时操作但又希望它们按序返回结果,因为任务耗时时长不同,就会出现回调顺序不一样的问题。
解决思路
这个问题让我想到了计算机网络中,TCP实现可靠传输中的按序到达。这两者的问题是一样的,由于网络阻塞等因素,每一个TCP包到达接收端的时间是不一样的。计网通过给每一个TCP包编号,确定它们的顺序,出现丢包则要求重传。
知道每个包的编号是确定顺序的关键。
我们也可以对每个回调做一些标记信息,再按照这些标记信息去做相应的处理。
那么问题解决模型就清晰了:
- 为每个任务做标记(编号)
- 根据标记信息做相应处理
比如有两张图片:图片一和图片二(按序),即便图片二先回调,那么我只要:
- 知道它当前应该插入文章的位置(做标记);
- 根据位置插入图文(根据标记信息做相应处理);
等到图片一回调时,再用同样的方法插入图片即可。
实践
做一个标记信息类
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))
val scope = CoroutineUtil.getScope(editText.hashCode()) val deferred = scope.async(Dispatchers.IO) { drawableGet.getDrawable(src) }
scope.launch(Dispatchers.Main) {
val drawable = deferred.await() 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 }
|
最后
遇到难题时,可以想一下有没有其它类似的问题?能否将问题转化成另一种简单的模型,将复杂的问题简化。