WebViewZygote.java revision f9bd606fbadb9a8595b0074aebf35f35d166aba8
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.ChildZygoteProcess;
24import android.os.Process;
25import android.os.ZygoteProcess;
26import android.text.TextUtils;
27import android.util.Log;
28
29import com.android.internal.annotations.GuardedBy;
30
31import java.io.File;
32import java.util.ArrayList;
33import java.util.List;
34
35/** @hide */
36public class WebViewZygote {
37    private static final String LOGTAG = "WebViewZygote";
38
39    /**
40     * Lock object that protects all other static members.
41     */
42    private static final Object sLock = new Object();
43
44    /**
45     * Instance that maintains the socket connection to the zygote. This is {@code null} if the
46     * zygote is not running or is not connected.
47     */
48    @GuardedBy("sLock")
49    private static ChildZygoteProcess sZygote;
50
51    /**
52     * Information about the selected WebView package. This is set from #onWebViewProviderChanged().
53     */
54    @GuardedBy("sLock")
55    private static PackageInfo sPackage;
56
57    /**
58     * Original ApplicationInfo for the selected WebView package before stub fixup. This is set from
59     * #onWebViewProviderChanged().
60     */
61    @GuardedBy("sLock")
62    private static ApplicationInfo sPackageOriginalAppInfo;
63
64    /**
65     * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote
66     * will not be started.
67     */
68    @GuardedBy("sLock")
69    private static boolean sMultiprocessEnabled = false;
70
71    public static ZygoteProcess getProcess() {
72        synchronized (sLock) {
73            if (sZygote != null) return sZygote;
74
75            connectToZygoteIfNeededLocked();
76            return sZygote;
77        }
78    }
79
80    public static String getPackageName() {
81        synchronized (sLock) {
82            return sPackage.packageName;
83        }
84    }
85
86    public static boolean isMultiprocessEnabled() {
87        synchronized (sLock) {
88            return sMultiprocessEnabled && sPackage != null;
89        }
90    }
91
92    public static void setMultiprocessEnabled(boolean enabled) {
93        synchronized (sLock) {
94            sMultiprocessEnabled = enabled;
95
96            // When toggling between multi-process being on/off, start or stop the
97            // zygote. If it is enabled and the zygote is not yet started, launch it.
98            // Otherwise, kill it. The name may be null if the package information has
99            // not yet been resolved.
100            if (enabled) {
101                connectToZygoteIfNeededLocked();
102            } else {
103                stopZygoteLocked();
104            }
105        }
106    }
107
108    public static void onWebViewProviderChanged(PackageInfo packageInfo,
109                                                ApplicationInfo originalAppInfo) {
110        synchronized (sLock) {
111            sPackage = packageInfo;
112            sPackageOriginalAppInfo = originalAppInfo;
113
114            // If multi-process is not enabled, then do not start the zygote service.
115            if (!sMultiprocessEnabled) {
116                return;
117            }
118
119            stopZygoteLocked();
120        }
121    }
122
123    @GuardedBy("sLock")
124    private static void stopZygoteLocked() {
125        if (sZygote != null) {
126            // Close the connection and kill the zygote process. This will not cause
127            // child processes to be killed by itself. But if this is called in response to
128            // setMultiprocessEnabled() or onWebViewProviderChanged(), the WebViewUpdater
129            // will kill all processes that depend on the WebView package.
130            sZygote.close();
131            Process.killProcess(sZygote.getPid());
132            sZygote = null;
133        }
134    }
135
136    @GuardedBy("sLock")
137    private static void connectToZygoteIfNeededLocked() {
138        if (sZygote != null) {
139            return;
140        }
141
142        if (sPackage == null) {
143            Log.e(LOGTAG, "Cannot connect to zygote, no package specified");
144            return;
145        }
146
147        try {
148            sZygote = Process.zygoteProcess.startChildZygote(
149                    "com.android.internal.os.WebViewZygoteInit",
150                    "webview_zygote",
151                    Process.WEBVIEW_ZYGOTE_UID,
152                    Process.WEBVIEW_ZYGOTE_UID,
153                    null,  // gids
154                    0,  // runtimeFlags
155                    "webview_zygote",  // seInfo
156                    sPackage.applicationInfo.primaryCpuAbi,  // abi
157                    null);  // instructionSet
158
159            // All the work below is usually done by LoadedApk, but the zygote can't talk to
160            // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so
161            // doesn't have an ActivityThread and can't use Binder.
162            // Instead, figure out the paths here, in the system server where we have access to
163            // the package manager. Reuse the logic from LoadedApk to determine the correct
164            // paths and pass them to the zygote as strings.
165            final List<String> zipPaths = new ArrayList<>(10);
166            final List<String> libPaths = new ArrayList<>(10);
167            LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
168            final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
169            final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
170                    TextUtils.join(File.pathSeparator, zipPaths);
171
172            // In the case where the ApplicationInfo has been modified by the stub WebView,
173            // we need to use the original ApplicationInfo to determine what the original classpath
174            // would have been to use as a cache key.
175            LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
176            final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
177                    TextUtils.join(File.pathSeparator, zipPaths);
178
179            ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress());
180
181            Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
182            sZygote.preloadPackageForAbi(zip, librarySearchPath, cacheKey,
183                                         Build.SUPPORTED_ABIS[0]);
184        } catch (Exception e) {
185            Log.e(LOGTAG, "Error connecting to webview zygote", e);
186            stopZygoteLocked();
187        }
188    }
189}
190