WebView 预加载
WebView 的问题
很多 Android 开发者都碰到类似这样的 ANR :
"main" prio=5 tid=1 Waiting
| group="main" sCount=1 dsCount=0 obj=0x756e2a88 self=0x7253895a00
| sysTid=5256 nice=-10 cgrp=default sched=0/0 handle=0x72577baa98
| state=S schedstat=( 362198032 46014107 637 ) utm=24 stm=11 core=3 HZ=100
| stack=0x7fc2f0d000-0x7fc2f0f000 stackSize=8MB
| held mutexes=
at java.lang.Object.wait!(Native method)
- waiting on <0x0a4a9cf4> (a java.lang.Object)
at java.lang.Thread.parkFor$(Thread.java:2127)
- locked <0x0a4a9cf4> (a java.lang.Object)
at sun.misc.Unsafe.park(Unsafe.java:325)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:161)
at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:421)
at java.util.concurrent.FutureTask.get(FutureTask.java:163)
at android.os.AsyncTask.get(AsyncTask.java:514)
at org.chromium.content.browser.BrowserStartupController.prepareToStartBrowserProcess(BrowserStartupController.java:57)
at org.chromium.content.browser.BrowserStartupController.startBrowserProcessesSync(BrowserStartupController.java:16)
at org.chromium.android_webview.AwBrowserProcess$1.run(AwBrowserProcess.java:14)
at org.chromium.base.ThreadUtils.runOnUiThreadBlocking(ThreadUtils.java:10)
at com.android.webview.chromium.WebViewChromiumFactoryProvider.startChromiumLocked(WebViewChromiumFactoryProvider.java:219)
at com.android.webview.chromium.WebViewChromiumFactoryProvider.ensureChromiumStartedLocked(WebViewChromiumFactoryProvider.java:151)
at com.android.webview.chromium.WebViewChromiumFactoryProvider.startYourEngines(WebViewChromiumFactoryProvider.java:239)
- locked <0x064f5c1d> (a java.lang.Object)
at com.android.webview.chromium.WebViewChromium.init(WebViewChromium.java:30)
at android.webkit.WebView.<init>(WebView.java:636)
at android.webkit.WebView.<init>(WebView.java:572)
at android.webkit.WebView.<init>(WebView.java:555)
at android.webkit.WebView.<init>(WebView.java:542)
at java.lang.reflect.Constructor.newInstance0!(Native method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:430)
at android.view.LayoutInflater.createView(LayoutInflater.java:645)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:787)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:727)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:858)
at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:821)
at android.view.LayoutInflater.inflate(LayoutInflater.java:518)
- locked <0x09493e92> (a java.lang.Object[])
at android.view.LayoutInflater.inflate(LayoutInflater.java:426)
at com.xxx.xxx.xxx.xxx.view.fragment.WebViewFragment.onCreateView(WebViewFragment.java:99)
at android.support.v4.app.Fragment.performCreateView(Fragment.java:2189)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1299)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1528)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1595)
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:757)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2355)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2146)
at android.support.v4.app.FragmentManagerImpl.optimizeAndExecuteOps(FragmentManager.java:2098)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2008)
at android.support.v4.app.FragmentController.execPendingActions(FragmentController.java:388)
at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:607)
at android.support.v7.app.AppCompatActivity.onStart(AppCompatActivity.java:178)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1248)
at android.app.Activity.performStart(Activity.java:6699)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2629)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727)
at android.app.ActivityThread.-wrap12(ActivityThread.java:-1)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1478)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6121)
at java.lang.reflect.Method.invoke!(Native method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
从堆栈来看,根本原因在于 WebView 在实例化的时候,需要先初始化 Chromium 引擎,而 Chromium 引擎又是一个重量级的组件,而且很多初始化的工作都需要在主线程中完成,这样就很容易造成主线程卡顿甚至 ANR。
预加载 Chromium 引擎
有人提出了通过 WebView 或者 WebSettings 中的静态方法来间接的初始化 WebView,比如:
WebSettings.getDefaultUserAgent(Context)在新窗口打开
/** * Returns the default User-Agent used by a WebView. * An instance of WebView could use a different User-Agent if a call * is made to {@link WebSettings#setUserAgentString(String)}. * * @param context a Context object used to access application assets */ public static String getDefaultUserAgent(Context context) { return WebViewFactory.getProvider().getStatics().getDefaultUserAgent(context); }
1
2
3
4
5
6
7
8
9
10
这些静态方法只能完成一小部分初始化的工作,对于最终的优化结果来说,并不会有明显的作用,主要的初始化工作是由 WebViewChromiumAwInit在新窗口打开 来完成的,整个 Chromium 引擎的启动流程如下:
以下是启动 Chromium 引擎的关键代码:
public class WebViewChromiumAwInit {
protected void startChromiumLocked() {
try (ScopedSysTraceEvent event =
ScopedSysTraceEvent.scoped("WebViewChromiumAwInit.startChromiumLocked")) {
TraceEvent.setATraceEnabled(mFactory.getWebViewDelegate().isTraceTagEnabled());
assert Thread.holdsLock(mLock) && ThreadUtils.runningOnUiThread();
// The post-condition of this method is everything is ready, so notify now to cover all
// return paths. (Other threads will not wake-up until we release |mLock|, whatever).
mLock.notifyAll();
if (mStarted) {
return;
}
final Context context = ContextUtils.getApplicationContext();
BuildInfo.setFirebaseAppId(AwFirebaseConfig.getFirebaseAppId());
JNIUtils.setClassLoader(WebViewChromiumAwInit.class.getClassLoader());
ResourceBundle.setAvailablePakLocales(
new String[] {}, AwLocaleConfig.getWebViewSupportedPakLocales());
BundleUtils.setIsBundle(ProductConfig.IS_BUNDLE);
// We are rewriting Java resources in the background.
// NOTE: Any reference to Java resources will cause a crash.
try (ScopedSysTraceEvent e =
ScopedSysTraceEvent.scoped("WebViewChromiumAwInit.LibraryLoader")) {
LibraryLoader.getInstance().ensureInitialized();
}
PathService.override(PathService.DIR_MODULE, "/system/lib/");
PathService.override(DIR_RESOURCE_PAKS_ANDROID, "/system/framework/webview/paks");
initPlatSupportLibrary();
doNetworkInitializations(context);
waitUntilSetUpResources();
// NOTE: Finished writing Java resources. From this point on, it's safe to use them.
AwBrowserProcess.configureChildProcessLauncher();
// finishVariationsInitLocked() must precede native initialization so the seed is
// available when AwFeatureListCreator::SetUpFieldTrials() runs.
finishVariationsInitLocked();
AwBrowserProcess.start();
AwBrowserProcess.handleMinidumpsAndSetMetricsConsent(true /* updateMetricsConsent */);
mSharedStatics = new SharedStatics();
if (BuildInfo.isDebugAndroid()) {
mSharedStatics.setWebContentsDebuggingEnabledUnconditionally(true);
}
mFactory.getWebViewDelegate().setOnTraceEnabledChangeListener(
new WebViewDelegate.OnTraceEnabledChangeListener() {
@Override
public void onTraceEnabledChange(boolean enabled) {
TraceEvent.setATraceEnabled(enabled);
}
});
mStarted = true;
RecordHistogram.recordSparseHistogram("Android.WebView.TargetSdkVersion",
context.getApplicationInfo().targetSdkVersion);
try (ScopedSysTraceEvent e = ScopedSysTraceEvent.scoped(
"WebViewChromiumAwInit.initThreadUnsafeSingletons")) {
// Initialize thread-unsafe singletons.
AwBrowserContext awBrowserContext = getBrowserContextOnUiThread();
mGeolocationPermissions = new GeolocationPermissionsAdapter(
mFactory, awBrowserContext.getGeolocationPermissions());
mWebStorage =
new WebStorageAdapter(mFactory, mBrowserContext.getQuotaManagerBridge());
mAwTracingController = getTracingController();
mServiceWorkerController = awBrowserContext.getServiceWorkerController();
mAwProxyController = new AwProxyController();
}
mFactory.getRunQueue().drainQueue();
maybeLogActiveTrials(context);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
为了避免线程上下文切换导致的副作用,Booster 采用了完全在主线程中初始化的方案:
public class ShadowWebView {
public static void preloadWebView(final Application app) {
try {
app.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
startChromiumEngine();
return false;
}
});
} catch (final Throwable t) {
Log.e(TAG, "Oops!", t);
}
}
private static void startChromiumEngine() {
try {
final long t0 = SystemClock.uptimeMillis();
final Object provider = invokeStaticMethod(Class.forName("android.webkit.WebViewFactory"), "getProvider");
invokeMethod(provider, "startYourEngines", new Class[]{boolean.class}, new Object[]{true});
Log.i(TAG, "Start chromium engine complete: " + (SystemClock.uptimeMillis() - t0) + " ms");
} catch (final Throwable t) {
Log.e(TAG, "Start chromium engine error", t);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ShadowWebView
通过 WebViewTransformer在新窗口打开 在 Application onCreate()
回调中注入 ShadowWebView.preloadWebView()
,当主线程 IDLE
时,启动 Chromium 引擎。
该方案的优点是无侵入、接入成本低,缺点是由于这种方式反射了非公开 API,可能存在兼容性问题。