欢迎光临散文网 会员登陆 & 注册

【安卓开发】TextView显示带图片HTML踩坑记录

2023-09-10 17:33 作者:スレーブ_スレイヤー  | 我要投稿

需求是显示一个网页,并且内容要可控,可以跟原生UI交互。

我用的是Compose,原生的Text是没有这种功能的,从0开始造轮子也不是不行,但是View体系已经有两个完整的解决方案了,没那个必要:

  1. WebView

    可以,但没必要,我只需要显示一个网页,并不需要太多交互,也不用跳转,WebView过于臃肿;其次就是交互很麻烦,得通过js hook,因此驳回。

  2. TextView

    可以通过Html.fromHtml()得到一个可以直接设置给TextView的对象,而且交互比较方便。


选择第二种方案以后,就开启了踩坑之路:

  1. TextView无法滚动

    解决方案:

2.无法显示图片

    这里坑就比较多了,我按时间顺序整理一下:

    a.异步加载

        Html.fromHtml()的第三个参数是一个ImageGetter接口,要实现一个getDrawable方法,给你一个url,让你返回Drawable。

那么问题来了,这个方法是在主线程调用的,如果你在主线程加载图片,则卡死,如果            你开IO线程,那就没办法返回结果。

仔细想一下就会发现,这个问题无解,这一次加载一定会阻塞主线程,因此需要设计一套缓存机制,在第一次加载的时候记录下url,加载完成以后再次调用getDrawable,返回已经缓存到本地的图片。

所以我就写了一个帮助类:

        

首先是为什么不写成单例类,因为可能有多个页面都要使用,而且这个类的生命周期是跟页面绑定的,所以不能写成单例。

其次是图片数量限制,因为没办法控制HTML的内容,为了防止OOM把最大图片数量限制在了10张。

然后这里虽然涉及到了多线程访问,但是不同线程并不会互相干扰,所以没用ConcurrentHashMap,也没加锁。

scope是协程上下文,基本就是ViewModel的scope,前面说过这个类的生命周期跟页面绑定,所以页面销毁的时候,viewModel销毁,所有协程也跟着销毁。

以及页面没有销毁,但是需要加载新的HTML,也得调用reset()刷新缓存。

我预想中的调用流程是这样的:

getDrawable内部查询缓存,不为空直接使用,否则调用load,load会立即返回一个Drawable作为占位图片,然后去加载网络图片,加载完成以后缓存,然后回调onFinished,回调函数内部再次给TextView设置HTML,于是上述流程被重复。

因为每一次设置HTML都会加载所有图片,所以为了防止某个图片在加载过程中被重复加载,直接把占位符缓存进去,所以开头加了判断,直接返回占位符。

剩下没什么好说的,非常质朴的缓存。


b.ImageGetter()的缺陷

    ImageGetter会把img的src属性传到getDrawable里,但我的src属性长这样:

真正的url在src里。

一开始我想修改ImageGetter类,或者别的什么,但是明显官方没给这个接口,反射也不是很好操作;然后想着要不提前把图片加载好,但是这样getDrawable内部依旧拿不到作为key的url,只能根据顺序决定使用哪一张图片,不太稳定;最后索性直接修改了HTML文档,把src替换成了src,原来的src换成别的不影响显示的属性。


c.Drawable

一切都弄好以后,图片还是不显示,然后发现Drawable还需要设置Rect。这个值直接给图片原大小的话,会显示不全,所以简单的缩放了两倍:

后续可以判断一下,超出屏幕就铺满宽度,然后根据宽度决定高度的缩放比例,就完美了。


中间还有一些小问题,省略。

以上。



过了很多天,发现一个新的问题:

页面销毁,不意味着HTML会改变,同一段HTML的图片不应该被多次加载,浪费流量。

不过好在coil是自带缓存的,替我兜了个底,不然还是得写成单例,然后根据当前url来决定是否清理缓存。


【安卓开发】TextView显示带图片HTML踩坑记录的评论 (共 条)

分享到微博请遵守国家法律