• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.webkit;
18 
19 import android.app.LoadedApk;
20 import android.content.pm.PackageInfo;
21 import android.os.Build;
22 import android.os.SystemService;
23 import android.os.ZygoteProcess;
24 import android.text.TextUtils;
25 import android.util.AndroidRuntimeException;
26 import android.util.Log;
27 
28 import com.android.internal.annotations.GuardedBy;
29 
30 import java.io.File;
31 import java.io.IOException;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.List;
35 import java.util.concurrent.TimeoutException;
36 
37 /** @hide */
38 public 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 null if the zygote
52      * 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      * Cache key for the selected WebView package's classloader. This is set from
72      * #onWebViewProviderChanged().
73      */
74     @GuardedBy("sLock")
75     private static String sPackageCacheKey;
76 
77     /**
78      * Flag for whether multi-process WebView is enabled. If this is false, the zygote
79      * will not be started.
80      */
81     @GuardedBy("sLock")
82     private static boolean sMultiprocessEnabled = false;
83 
getProcess()84     public static ZygoteProcess getProcess() {
85         synchronized (sLock) {
86             if (sZygote != null) return sZygote;
87 
88             waitForServiceStartAndConnect();
89             return sZygote;
90         }
91     }
92 
getPackageName()93     public static String getPackageName() {
94         synchronized (sLock) {
95             return sPackage.packageName;
96         }
97     }
98 
isMultiprocessEnabled()99     public static boolean isMultiprocessEnabled() {
100         synchronized (sLock) {
101             return sMultiprocessEnabled && sPackage != null;
102         }
103     }
104 
setMultiprocessEnabled(boolean enabled)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 
onWebViewProviderChanged(PackageInfo packageInfo, String cacheKey)129     public static void onWebViewProviderChanged(PackageInfo packageInfo, String cacheKey) {
130         synchronized (sLock) {
131             sPackage = packageInfo;
132             sPackageCacheKey = cacheKey;
133 
134             // If multi-process is not enabled, then do not start the zygote service.
135             if (!sMultiprocessEnabled) {
136                 return;
137             }
138 
139             final String serviceName = getServiceNameLocked();
140             sZygote = null;
141 
142             // The service may enter the RUNNING state before it opens the socket,
143             // so connectToZygoteIfNeededLocked() may still fail.
144             if (SystemService.isStopped(serviceName)) {
145                 SystemService.start(serviceName);
146             } else {
147                 SystemService.restart(serviceName);
148             }
149             sStartedService = true;
150         }
151     }
152 
waitForServiceStartAndConnect()153     private static void waitForServiceStartAndConnect() {
154         if (!sStartedService) {
155             throw new AndroidRuntimeException("Tried waiting for the WebView Zygote Service to " +
156                     "start running without first starting the service.");
157         }
158 
159         String serviceName;
160         synchronized (sLock) {
161             serviceName = getServiceNameLocked();
162         }
163         try {
164             SystemService.waitForState(serviceName, SystemService.State.RUNNING, 5000);
165         } catch (TimeoutException e) {
166             Log.e(LOGTAG, "Timed out waiting for " + serviceName);
167             return;
168         }
169 
170         synchronized (sLock) {
171             connectToZygoteIfNeededLocked();
172         }
173     }
174 
175     @GuardedBy("sLock")
getServiceNameLocked()176     private static String getServiceNameLocked() {
177         if (sPackage == null)
178             return null;
179 
180         if (Arrays.asList(Build.SUPPORTED_64_BIT_ABIS).contains(
181                     sPackage.applicationInfo.primaryCpuAbi)) {
182             return WEBVIEW_ZYGOTE_SERVICE_64;
183         }
184 
185         return WEBVIEW_ZYGOTE_SERVICE_32;
186     }
187 
188     @GuardedBy("sLock")
connectToZygoteIfNeededLocked()189     private static void connectToZygoteIfNeededLocked() {
190         if (sZygote != null) {
191             return;
192         }
193 
194         if (sPackage == null) {
195             Log.e(LOGTAG, "Cannot connect to zygote, no package specified");
196             return;
197         }
198 
199         final String serviceName = getServiceNameLocked();
200         if (!SystemService.isRunning(serviceName)) {
201             Log.e(LOGTAG, serviceName + " is not running");
202             return;
203         }
204 
205         try {
206             sZygote = new ZygoteProcess(WEBVIEW_ZYGOTE_SOCKET, null);
207 
208             // All the work below is usually done by LoadedApk, but the zygote can't talk to
209             // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so
210             // doesn't have an ActivityThread and can't use Binder.
211             // Instead, figure out the paths here, in the system server where we have access to
212             // the package manager. Reuse the logic from LoadedApk to determine the correct
213             // paths and pass them to the zygote as strings.
214             final List<String> zipPaths = new ArrayList<>(10);
215             final List<String> libPaths = new ArrayList<>(10);
216             LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
217             final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
218             final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
219                     TextUtils.join(File.pathSeparator, zipPaths);
220 
221             ZygoteProcess.waitForConnectionToZygote(WEBVIEW_ZYGOTE_SOCKET);
222 
223             Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
224             sZygote.preloadPackageForAbi(zip, librarySearchPath, sPackageCacheKey,
225                                          Build.SUPPORTED_ABIS[0]);
226         } catch (Exception e) {
227             Log.e(LOGTAG, "Error connecting to " + serviceName, e);
228             sZygote = null;
229         }
230     }
231 }
232