注册

实现一套自己的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的包结构(从快手这看,容易理解)

202112060916923.png 这是快手app解开后webkit下的内容,整体上看,其实就是拷贝一套系统的webkit的api202112060917490.png
上图1 mProvider就是webview的实现类,2 则为系统实现,既然是自定义webkit为什么要有2呢

主要应该是两个原因

  • 是chromium内核包太大了
    202112060918156.png

  • 实现快速修复问题的目的,这可能就是X5提到的安全这点能在24小时之内修复

所以为了处理以上这两个问题,chromium内核一般都是插件化的实现,因此在插件还没有加载到的时候,我们只能去展示系统的webview,这里回到第一张图片,webkit adapter包下的就包含了所有webview参数和返回对象的包装,意在实现两套webkit对应的转换,比如

202112060913722.png

202112060919497.png 这里我们使用的地方调用的是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),有些我们是引用不到的, 202112060913591.png另外不同版本可能不同机型的plat_support的实现也有些差异,内核里需要维护太多的plat_support,增加复杂度

采用反射回调系统实现(所有内核里的native方法转到系统webview的实现),一开始在android10上测试ok,以为绕过了该问题,然而当我们在Android10以下的版本上,同时开启系统WebView和自定义webview就出现问题了,运气好的话要么系统WebView黑屏,要么自定义的WebView黑屏,多数情况下会崩溃,主要是plat_support其中一个方法nativeSetChromiumAwDrawGLFunction,保存chromium 内部渲染出口对象是一个单例实现,所以会出现两边同时都调用到这个方法,要么系统WebView黑屏,要么系统自定义Webview黑屏,所以我们需要自己实现一个plat_support,一开始参考快手的实现,因为从第三个图中,我们看到它有对应的plat_support

202112060920593.png 同时还看到了,它对系统库libskia.so的调用

实现上主要是GraphicsUtils对应的两个native方法比较复杂涉及到GraphicBuffer类 202112060920131.png GraphicBuffer这个类位于libui.so,然而快手的plat_support中并没有找到libui.so的调用,既然不引用libui.so,我们就要自己去创建对应的对象,这里我们可以把它改成结构体

202112060921689.png 匹配所有的成员变量,就可以强转成目标对象(这和java完全不一样,一般没有关联的类的实例不能强转成目标类,不能调用的目标类的方法),只是我们需要该类原本实现的方法在本地重新实现一次,然而目标类的方法里又有别的类的调用,延展开来,最终处理的内容就很多了,还有一大堆版本适配问题,到这里暂时hold。

好了那么我们再来看看X5的实现,

202112060913716.png 好家伙,这直接反回0,然后我立马测试了一下,通过~~~
也就是说前面海量的代码实现,其实是可以删除的,chromium内部有自己默认的实现(这里我们还是可以反射转回到系统webview的实现,包括nativeGetFunctionTable)。

剩下的我们只需处理以下几个native方法即可 202112060922025.png 这个几个方法处理就非常简单了,注意kAwDrawGLInfoVersion的版本号适配就Ok了。

结语

当我们整完这一套,就可以开始我们的内核定制了,包括小程序领域所谓的同城渲染能力,JS ServerWorker的定制等等。

作者:北纬34点8度
来源:https: //juejin.cn/post/7037807249103798285

0 个评论

要回复文章请先登录注册