ARTICLE · 1173369
在庞大的Android WebView源码中寻找定位问题根源
当代码出现问题,如果只是调整代码修复,难以杜绝未来还会发生,需要去探究底层到底除了什么,才能得出完善的方案解决问题。而底层代码非常庞大,导致无从开始探寻。 本文从一个现网反馈问题出发,通过一定的代码阅读方法,逐步解析底层原理。
问题
现网有多个用户反馈,特定富文本内容在预览页,重新编辑内容卡死, 内容有多张图片,并且图片尺寸很大加载慢,网络异常无法下载(墙外资源)。也就是连续启动加载相同内容导致卡死。
分析
问题表象
经过多次测试,发现大量图片的内容会导致进入编辑页面卡顿,等待图片加载完进入正常。
当前图片加载流程:

深入源码探索
由于软件使用系统WebView 渲染内容,在开发工程上根本看不到底层源码,并且 Android WebView是chromium 项目编译而来,是一个非常庞大的代码仓库。
https://source.chromium.org/chromium
面对庞大的源码库,如何找到目标代码?
方法一:代码断点看堆栈。
对于运行在 Android虚拟机上的代码,通过断点得到运行堆栈,就可以知道调用链, 找到源码。但对于 WebView,在 Android 上是动态加载到 App 上,能够时时更新安全补丁,并且WebView大部分只是一个壳,核心代码都是c++编写的native 层实现。这个时候代码断点就找不到源头了。
方法二:问大模型。
大模型能够回答架构设计原理,但不能帮你找到目标源码,更不知道你的问题在哪。大模型给出的错误代码路径,可能是大模型幻觉了。
方法三:关键字搜索。
最后只能靠自己去代码库上搜索。
图片如何渲染到webview上(为何会卡死)
客户端为了拦截资源下载,会实现一个WebViewClient,并重载shouldInterceptRequest方法。
@Overridepublic final WebResourceResponse shouldInterceptRequest(WebView view,String url){try{returnshouldSafeInterceptRequest(view, url);} catch (Throwable tr){return null;}}
那么在源码中必定有一个地方执行这个方法,因此我们把shouldInterceptRequest作为关键字字符搜索源码库,得到结果:

shouldInterceptRequestFromNative方法就是应用层拦截资源加载的关键入口。从名字可知,这个java方法是给native回调到vm虚拟机的入口,我们往下挖就能找到想要的东西。

native执行StartShouldInterceptRequest的callback拿到应用层拦截的结果,这里在一层一层往上回溯找到callback位置:


done_closure再看回来。。。

执行资源加载:

代码深入往下找到读取数据的地方:


任务抛到线程池里执行,到这里,其实我们已经找到了卡顿的原因。
根据chrome的源代码,发现根本原因是图片加载卡住了WebView底层线程池,导致编辑页的WebView初始化卡死。

预览webview底层有个基础全局线程池,最大活动线程有限,而我们的图片加载请求网络和解码的时候,会卡住这个线程,直到下载解码完成。如果图片过多,卡住的线程会塞满线程池。
转发回复跳转会初始化下一个WebView,初始化操作有大量需要线程池执行,导致编辑页的WebView初始化异常。
数据如何读取(为何大图片会加载慢)
一张5M的图片,抛给WebView加载缓慢,表象上看是数据传输慢,需要继续往下看int ReadRawData(net::IOBuffer* buffer, int buffer_size)

WebView会一个接一个buffer的读取数据

那这个buffer又多大呢。。。

太小的 buffer,如果读取一个较大的文件,将有频繁的io 请求,如何是用FileInputStream则每次io都有一定的执行成本耗时。
卡顿解决方案
方案1 简单修改,影响范围小
跳转编辑器编辑内容时,仍保持阻塞WebView线程,停止加载图片,编辑页面返回后再重新加载内容
方案优点是改动小,但编辑页返回预览页会重新加载,体验有闪动。
方案2 体验更好
图片下载不阻塞WebView读取图片,异步加载完图片后,再触发重新读取
方案改动大,但体验更好,WebView不用重新reload
改造后图片加载流程:















































































