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