实现一套自己的WebKit
大家应该都听过腾讯的X5浏览器,据官方介绍其有以下优势:
速度快:相比系统webview的网页打开速度有30+%的提升;
省流量:使用云端优化技术使流量节省20+%;
更安全:安全问题可以在24小时内修复;
更稳定:经过亿级用户的使用考验,CRASH率低于0.15%;
。。。
就不一一列举了,详细见(x5.tencent.com/docs/index.… )
既然自定义webkit有能力做到这些,那我们为什么不自己试着整一个?
打包chromium,生成官方的apk
具体实现见Chromium网络请求
安装打开chrome_public_apk,它其实就是chrome浏览器app,那么我们怎么实现一套webkit呢?
webkit解构
webkit的包结构(从快手这看,容易理解)
这是快手app解开后webkit下的内容,整体上看,其实就是拷贝一套系统的webkit的api
上图1 mProvider就是webview的实现类,2 则为系统实现,既然是自定义webkit为什么要有2呢
主要应该是两个原因
是chromium内核包太大了
实现快速修复问题的目的,这可能就是X5提到的安全这点能在24小时之内修复
所以为了处理以上这两个问题,chromium内核一般都是插件化的实现,因此在插件还没有加载到的时候,我们只能去展示系统的webview,这里回到第一张图片,webkit adapter包下的就包含了所有webview参数和返回对象的包装,意在实现两套webkit对应的转换,比如
这里我们使用的地方调用的是com.kuaishou.webkit.CookieManager.getInstance()
,在自己的内核没有加载完成的时候,所有调用都中转到了系统的android.webkit.CookieManager
。
难点攻坚
内核模块调整,我们需要调整内核代码,把所有指向系统webkit包名全部改成我们自定义webkit的包名,为了使内核编译能通过,我们需要拷贝我们自定义的webkit这个模块到内核里去,在编译的时候把其剔除,这里我们需要去稍微了解一下gn构建配置相关的内容。
插件化的实现,插件化现在应该都烂大街了,省略。
// 这里稍微提一下,加载内核apk的Classloader的实现
public static class DexClassLoaderOptimize extends DexClassLoader {
@Override // java.lang.ClassLoader
public Class loadClass(String str, boolean z) throws ClassNotFoundException {
// 所有系统相关的类,或者我们的webkit层全部在我们app层找
if (str.startsWith("java.") || ((str.startsWith("android.")
&& !str.startsWith("android.support.")) ||
str.startsWith("com.kuaishou.webkit"))) {
return super.loadClass(str, z);
}
// 否则直接在插件中找
try {
return findClass(str);
} catch (Exception unused) {
return super.loadClass(str, z);
}
}
}
整个webkit层,可以参照着快手的实现。
在内核的加载过程中,主要涉及两个动态库一个是webview.so,另一个是webview_plat_support.so,其中webview_plat_support.so是Android系统内部的一个so,内核渲染的一个支持模块,我们需要使用到这个模块去做渲染的事情。(实际上Android10版本以上其实并不依赖该so去实现渲染能力)
webview_plat_support问题攻坚
webview_plat_support.so(以下简称plat_support)
一开始想过直接拷贝一份对应的so到我们的内核里,但是plat_support依赖了一些系统的动态库,api23以后ndk的动态库(见public),有些我们是引用不到的, 另外不同版本可能不同机型的plat_support的实现也有些差异,内核里需要维护太多的plat_support,增加复杂度
采用反射回调系统实现(所有内核里的native方法转到系统webview的实现),一开始在android10上测试ok,以为绕过了该问题,然而当我们在Android10以下的版本上,同时开启系统WebView和自定义webview就出现问题了,运气好的话要么系统WebView黑屏,要么自定义的WebView黑屏,多数情况下会崩溃,主要是plat_support其中一个方法nativeSetChromiumAwDrawGLFunction,保存chromium 内部渲染出口对象是一个单例实现,所以会出现两边同时都调用到这个方法,要么系统WebView黑屏,要么系统自定义Webview黑屏,所以我们需要自己实现一个plat_support,一开始参考快手的实现,因为从第三个图中,我们看到它有对应的plat_support
同时还看到了,它对系统库libskia.so的调用
实现上主要是GraphicsUtils对应的两个native方法比较复杂涉及到GraphicBuffer类 GraphicBuffer这个类位于libui.so,然而快手的plat_support中并没有找到libui.so的调用,既然不引用libui.so,我们就要自己去创建对应的对象,这里我们可以把它改成结构体
匹配所有的成员变量,就可以强转成目标对象(这和java完全不一样,一般没有关联的类的实例不能强转成目标类,不能调用的目标类的方法),只是我们需要该类原本实现的方法在本地重新实现一次,然而目标类的方法里又有别的类的调用,延展开来,最终处理的内容就很多了,还有一大堆版本适配问题,到这里暂时hold。
好了那么我们再来看看X5的实现,
好家伙,这直接反回0,然后我立马测试了一下,通过~~~
也就是说前面海量的代码实现,其实是可以删除的,chromium内部有自己默认的实现(这里我们还是可以反射转回到系统webview的实现,包括nativeGetFunctionTable)。
剩下的我们只需处理以下几个native方法即可 这个几个方法处理就非常简单了,注意kAwDrawGLInfoVersion的版本号适配就Ok了。
结语
当我们整完这一套,就可以开始我们的内核定制了,包括小程序领域所谓的同城渲染能力,JS ServerWorker的定制等等。
作者:北纬34点8度
来源:https: //juejin.cn/post/7037807249103798285