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