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