WebViewZygote.java revision 5bc14af5fa7f62dabcd882cb894160ef4ddcbf87
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
19import android.app.LoadedApk;
20import android.content.pm.ApplicationInfo;
21import android.content.pm.PackageInfo;
22import android.os.Build;
23import android.os.SystemService;
24import android.os.ZygoteProcess;
25import android.text.TextUtils;
26import android.util.AndroidRuntimeException;
27import android.util.Log;
28
29import com.android.internal.annotations.GuardedBy;
30
31import java.io.File;
32import java.io.IOException;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.List;
36import java.util.concurrent.TimeoutException;
37
38/** @hide */
39public class WebViewZygote {
40    private static final String LOGTAG = "WebViewZygote";
41
42    private static final String WEBVIEW_ZYGOTE_SERVICE_32 = "webview_zygote32";
43    private static final String WEBVIEW_ZYGOTE_SERVICE_64 = "webview_zygote64";
44    private static final String WEBVIEW_ZYGOTE_SOCKET = "webview_zygote";
45
46    /**
47     * Lock object that protects all other static members.
48     */
49    private static final Object sLock = new Object();
50
51    /**
52     * Instance that maintains the socket connection to the zygote. This is null if the zygote
53     * is not running or is not connected.
54     */
55    @GuardedBy("sLock")
56    private static ZygoteProcess sZygote;
57
58    /**
59     * Variable that allows us to determine whether the WebView zygote Service has already been
60     * started.
61     */
62    @GuardedBy("sLock")
63    private static boolean sStartedService = false;
64
65    /**
66     * Information about the selected WebView package. This is set from #onWebViewProviderChanged().
67     */
68    @GuardedBy("sLock")
69    private static PackageInfo sPackage;
70
71    /**
72     * Original ApplicationInfo for the selected WebView package before stub fixup. This is set from
73     * #onWebViewProviderChanged().
74     */
75    @GuardedBy("sLock")
76    private static ApplicationInfo sPackageOriginalAppInfo;
77
78    /**
79     * Flag for whether multi-process WebView is enabled. If this is false, the zygote
80     * will not be started.
81     */
82    @GuardedBy("sLock")
83    private static boolean sMultiprocessEnabled = false;
84
85    public static ZygoteProcess getProcess() {
86        synchronized (sLock) {
87            if (sZygote != null) return sZygote;
88
89            waitForServiceStartAndConnect();
90            return sZygote;
91        }
92    }
93
94    public static String getPackageName() {
95        synchronized (sLock) {
96            return sPackage.packageName;
97        }
98    }
99
100    public static boolean isMultiprocessEnabled() {
101        synchronized (sLock) {
102            return sMultiprocessEnabled && sPackage != null;
103        }
104    }
105
106    public static void setMultiprocessEnabled(boolean enabled) {
107        synchronized (sLock) {
108            sMultiprocessEnabled = enabled;
109
110            // When toggling between multi-process being on/off, start or stop the
111            // service. If it is enabled and the zygote is not yet started, bring up the service.
112            // Otherwise, bring down the service. The name may be null if the package
113            // information has not yet been resolved.
114            final String serviceName = getServiceNameLocked();
115            if (serviceName == null) return;
116
117            if (enabled) {
118                if (!sStartedService) {
119                    SystemService.start(serviceName);
120                    sStartedService = true;
121                }
122            } else {
123                SystemService.stop(serviceName);
124                sStartedService = false;
125                sZygote = null;
126            }
127        }
128    }
129
130    public static void onWebViewProviderChanged(PackageInfo packageInfo,
131                                                ApplicationInfo originalAppInfo) {
132        synchronized (sLock) {
133            sPackage = packageInfo;
134            sPackageOriginalAppInfo = originalAppInfo;
135
136            // If multi-process is not enabled, then do not start the zygote service.
137            if (!sMultiprocessEnabled) {
138                return;
139            }
140
141            final String serviceName = getServiceNameLocked();
142            sZygote = null;
143
144            // The service may enter the RUNNING state before it opens the socket,
145            // so connectToZygoteIfNeededLocked() may still fail.
146            if (SystemService.isStopped(serviceName)) {
147                SystemService.start(serviceName);
148            } else {
149                SystemService.restart(serviceName);
150            }
151            sStartedService = true;
152        }
153    }
154
155    private static void waitForServiceStartAndConnect() {
156        if (!sStartedService) {
157            throw new AndroidRuntimeException("Tried waiting for the WebView Zygote Service to " +
158                    "start running without first starting the service.");
159        }
160
161        String serviceName;
162        synchronized (sLock) {
163            serviceName = getServiceNameLocked();
164        }
165        try {
166            SystemService.waitForState(serviceName, SystemService.State.RUNNING, 5000);
167        } catch (TimeoutException e) {
168            Log.e(LOGTAG, "Timed out waiting for " + serviceName);
169            return;
170        }
171
172        synchronized (sLock) {
173            connectToZygoteIfNeededLocked();
174        }
175    }
176
177    @GuardedBy("sLock")
178    private static String getServiceNameLocked() {
179        if (sPackage == null)
180            return null;
181
182        if (Arrays.asList(Build.SUPPORTED_64_BIT_ABIS).contains(
183                    sPackage.applicationInfo.primaryCpuAbi)) {
184            return WEBVIEW_ZYGOTE_SERVICE_64;
185        }
186
187        return WEBVIEW_ZYGOTE_SERVICE_32;
188    }
189
190    @GuardedBy("sLock")
191    private static void connectToZygoteIfNeededLocked() {
192        if (sZygote != null) {
193            return;
194        }
195
196        if (sPackage == null) {
197            Log.e(LOGTAG, "Cannot connect to zygote, no package specified");
198            return;
199        }
200
201        final String serviceName = getServiceNameLocked();
202        if (!SystemService.isRunning(serviceName)) {
203            Log.e(LOGTAG, serviceName + " is not running");
204            return;
205        }
206
207        try {
208            sZygote = new ZygoteProcess(WEBVIEW_ZYGOTE_SOCKET, null);
209
210            // All the work below is usually done by LoadedApk, but the zygote can't talk to
211            // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so
212            // doesn't have an ActivityThread and can't use Binder.
213            // Instead, figure out the paths here, in the system server where we have access to
214            // the package manager. Reuse the logic from LoadedApk to determine the correct
215            // paths and pass them to the zygote as strings.
216            final List<String> zipPaths = new ArrayList<>(10);
217            final List<String> libPaths = new ArrayList<>(10);
218            LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
219            final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
220            final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
221                    TextUtils.join(File.pathSeparator, zipPaths);
222
223            // In the case where the ApplicationInfo has been modified by the stub WebView,
224            // we need to use the original ApplicationInfo to determine what the original classpath
225            // would have been to use as a cache key.
226            LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
227            final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
228                    TextUtils.join(File.pathSeparator, zipPaths);
229
230            ZygoteProcess.waitForConnectionToZygote(WEBVIEW_ZYGOTE_SOCKET);
231
232            Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
233            sZygote.preloadPackageForAbi(zip, librarySearchPath, cacheKey,
234                                         Build.SUPPORTED_ABIS[0]);
235        } catch (Exception e) {
236            Log.e(LOGTAG, "Error connecting to " + serviceName, e);
237            sZygote = null;
238        }
239    }
240}
241