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 waitForZygote(); 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 /** 233 * Wait until a connection to the Zygote can be established. 234 */ waitForZygote()235 private static void waitForZygote() { 236 while (true) { 237 try { 238 final ZygoteProcess.ZygoteState zs = 239 ZygoteProcess.ZygoteState.connect(WEBVIEW_ZYGOTE_SOCKET); 240 zs.close(); 241 break; 242 } catch (IOException ioe) { 243 Log.w(LOGTAG, "Got error connecting to zygote, retrying. msg= " + ioe.getMessage()); 244 } 245 246 try { 247 Thread.sleep(1000); 248 } catch (InterruptedException ie) { 249 } 250 } 251 } 252 } 253