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