测试手机型号:Oppo K9 5G CPU:高通768G 内存:8G
首先看一个Perfetto图,看看为什么打开Web页面这样的卡和慢:
![](https://img.haomeiwen.com/i3067882/a949ded6496456fe.png)
Release版本的APK,首次打开WebView在
onCreate
创建WebView时耗时243.5ms。再次打开WebView页面耗时降到了6.4ms:![](https://img.haomeiwen.com/i3067882/293c09a4b335771b.png)
由此可见首次打开WebView初始化会花费大量的时间。
接着需要看下具体是哪些方法调用花费了这么多时间:
-
WebViewChromium.init
中的WebViewChromiumFactoryProviders.startYourEngines
-
WebViewChromium.initForReal
其中方法1在首次打开页面后不再出现,2再次初始化时间从44.6ms缩短到6.4ms。
这里就产生了一种优化思路,在进程空闲并且预期可以打开Web的页面,可以预先初始化这些组件,那么后续打开的用户体验就会好很多。
顺着这个思路,网上有很多方案,例如提前反射调用WebViewChromiumFactoryProviders.startYourEngines
。这种想法很好,但是实践过程中发现并没有什么作用。将反射出来的类及其父类中的declaredMethods
都打印出来,发现找不到相应的startYourEngines
方法。
E [main] search in parent:class com.android.webview.chromium.WebViewChromiumFactoryProvider
E [main] android.content.Context
E [main] method in parent:b,kotlin.Unit,1
E [main] android.webkit.WebViewDelegate
E [main] method in parent:create,kotlin.Unit,1
E [main] java.io.File
E [main] method in parent:d,kotlin.Unit,1
E [main] method in parent:h,kotlin.Unit,0
E [main] android.content.Context
E [main] method in parent:l,kotlin.Unit,1
E [main] method in parent:preloadInZygote,kotlin.Unit,0
E [main] method in parent:setWebLayerRunningInSameProcess,kotlin.Unit,0
E [main] java.lang.Runnable
E [main] method in parent:a,kotlin.Unit,1
E [main] android.content.Context
E [main] method in parent:addWebViewAssetPath,kotlin.Unit,1
E [main] org.chromium.android_webview.AwSettings
E [main] method in parent:c,kotlin.Unit,1
E [main] method in parent:createPacProcessor,kotlin.Unit,0
E [main] android.webkit.WebView
E [main] android.webkit.WebView$PrivateAccess
E [main] method in parent:createWebView,kotlin.Unit,2
E [main] android.content.pm.PackageInfo
E [main] method in parent:e,kotlin.Unit,1
E [main] method in parent:f,kotlin.Unit,0
E [main] method in parent:g,kotlin.Unit,0
E [main] method in parent:getCookieManager,kotlin.Unit,0
E [main] method in parent:getGeolocationPermissions,kotlin.Unit,0
E [main] method in parent:getPacProcessor,kotlin.Unit,0
E [main] method in parent:getServiceWorkerController,kotlin.Unit,0
E [main] method in parent:getStatics,kotlin.Unit,0
E [main] method in parent:getTokenBindingService,kotlin.Unit,0
E [main] method in parent:getTracingController,kotlin.Unit,0
E [main] method in parent:getWebIconDatabase,kotlin.Unit,0
E [main] method in parent:getWebStorage,kotlin.Unit,0
E [main] method in parent:getWebViewClassLoader,kotlin.Unit,0
E [main] android.content.Context
E [main] method in parent:getWebViewDatabase,kotlin.Unit,1
E [main] method in parent:i,kotlin.Unit,0
E [main] java.util.concurrent.Callable
E [main] method in parent:j,kotlin.Unit,1
E [main] java.lang.Runnable
E [main] method in parent:k,kotlin.Unit,1
E [main] method in parent:m,kotlin.Unit,0
E [main] boolean
E [main] method in parent:n,kotlin.Unit,1
查看Chromium中的WebViewChromiumFactoryProviders
源码,确实是有这个方法的。
![](https://img.haomeiwen.com/i3067882/2f8b7ceb4a4be8ce.png)
那么不难得出这个方法被混淆了,无法通过反射访问了。由于这个类是Android系统提供的,对此应用端无法keep。因此提前反射调用WebViewChromiumFactoryProviders.startYourEngines
在实际运行中是无效的,后续的Prefetto图也证明了这个结论。
至于WebViewChromium.initForReal
初始化逻辑就更加复杂了,通过反射也很难完成相应的初始化。
如果是直接在进程创建的时候新建一个WebView呢?
var webView: WebView? = WebView(app)
webView = null
这样对于上述情况是有改善的,那是否会造成内存占用方面的影响呢?看数据:
- 未初始化WebView占用
Applications Memory Usage (in Kilobytes):
Uptime: 168160548 Realtime: 812073693
** MEMINFO in pid 16882 [com.xxxx.xxxx:web] **
Pss Private Private SwapPss Rss Heap Heap Heap
Total Dirty Clean Dirty Total Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------
Native Heap 5945 5912 0 21 7328 18432 13427 5004
Dalvik Heap 8088 3140 4636 20 9804 5707 4281 1426
Dalvik Other 3694 3616 32 2 4444
Stack 1564 1564 0 0 1572
Ashmem 7 0 0 0 392
Other dev 12 0 12 0 416
.so mmap 2328 392 104 6 25612
.jar mmap 715 0 0 0 29232
.apk mmap 3803 312 0 0 12136
.dex mmap 14265 48 840 0 40488
.oat mmap 175 0 0 0 13212
.art mmap 2096 776 768 98 13508
Other mmap 449 40 64 0 2176
Unknown 920 912 0 2 1380
TOTAL 44210 16712 6456 149 161700 24139 17708 6430
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 4684 23312
Native Heap: 5912 7328
Code: 1696 120772
Stack: 1564 1572
Graphics: 0 0
Private Other: 9312
System: 21042
Unknown: 8716
TOTAL PSS: 44210 TOTAL RSS: 161700 TOTAL SWAP PSS: 149
Objects
Views: 0 ViewRootImpl: 0
AppContexts: 3 Activities: 0
Assets: 29 AssetManagers: 0
Local Binders: 24 Proxy Binders: 42
Parcel memory: 12 Parcel count: 48
Death Recipients: 1 OpenSSL Sockets: 1
WebViews: 0
SQL
MEMORY_USED: 81
PAGECACHE_OVERFLOW: 14 MALLOC_SIZE: 46
- 进程启动即初始化WebView占用
Applications Memory Usage (in Kilobytes):
Uptime: 168379408 Realtime: 812292552
** MEMINFO in pid 18341 [com.xxxx.xxxx:web] **
Pss Private Private SwapPss Rss Heap Heap Heap
Total Dirty Clean Dirty Total Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------
Native Heap 7239 7212 0 19 8616 18432 15300 3131
Dalvik Heap 8591 3676 4596 19 10312 6204 4653 1551
Dalvik Other 4013 3908 32 2 4788
Stack 1808 1808 0 0 1816
Ashmem 162 156 0 0 556
Other dev 8 0 8 0 412
.so mmap 3492 420 468 5 27436
.jar mmap 907 0 48 0 30256
.apk mmap 15448 312 12260 0 22608
.dex mmap 24667 48 6904 0 45220
.oat mmap 210 0 0 0 14148
.art mmap 2349 900 784 80 14064
Other mmap 1673 40 1336 0 3212
Unknown 2317 2312 0 1 2776
TOTAL 73010 20792 26436 126 186220 24636 19953 4682
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 5360 24376
Native Heap: 7212 8616
Code: 20460 139816
Stack: 1808 1816
Graphics: 0 0
Private Other: 12388
System: 25782
Unknown: 11596
TOTAL PSS: 73010 TOTAL RSS: 186220 TOTAL SWAP PSS: 126
Objects
Views: 0 ViewRootImpl: 0
AppContexts: 4 Activities: 0
Assets: 32 AssetManagers: 0
Local Binders: 42 Proxy Binders: 44
Parcel memory: 15 Parcel count: 60
Death Recipients: 3 OpenSSL Sockets: 1
WebViews: 0
SQL
MEMORY_USED: 81
PAGECACHE_OVERFLOW: 14 MALLOC_SIZE: 46
简单对比下:
未初始化WebView | 初始化WebView | 差异MB | |
---|---|---|---|
PSS | 44210 | 73010 | 增加28.125MB |
RSS | 161700 | 186220 | 增加23.95MB |
这里讲了内存开销,那收益是什么样的呢?
![](https://img.haomeiwen.com/i3067882/d5588e1a587c2e0d.png)
WebView创建时间下降到了17.9ms。将进程初始化的Perfetto打印出来:
![](https://img.haomeiwen.com/i3067882/02712ca9cb3abbd8.png)
可以看到,
bindApplication
后WebViewChromium
新建花费的时间是882.4ms。
可见提前初始化WebView对提高首次WebView打开速度确实有帮助,但是会产生30MB左右的内存开销(不同机型可能不太一致)。
后续采取的策略是在可能会打开WebView的场景,首先打开Web进程,并根据下发配置来确定是否在进程起来的时候初始化一个WebView。
网友评论