夜雨聆风学习资料网

ARTICLE · 1173369

在庞大的Android WebView源码中寻找定位问题根源

在庞大的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

改造后图片加载流程:

相关学习资料