/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.webkit; import android.app.LoadedApk; import android.content.pm.PackageInfo; import android.os.Build; import android.os.SystemService; import android.os.ZygoteProcess; import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeoutException; /** @hide */ public class WebViewZygote { private static final String LOGTAG = "WebViewZygote"; private static final String WEBVIEW_ZYGOTE_SERVICE_32 = "webview_zygote32"; private static final String WEBVIEW_ZYGOTE_SERVICE_64 = "webview_zygote64"; private static final String WEBVIEW_ZYGOTE_SOCKET = "webview_zygote"; /** * Lock object that protects all other static members. */ private static final Object sLock = new Object(); /** * Instance that maintains the socket connection to the zygote. This is null if the zygote * is not running or is not connected. */ @GuardedBy("sLock") private static ZygoteProcess sZygote; /** * Variable that allows us to determine whether the WebView zygote Service has already been * started. */ @GuardedBy("sLock") private static boolean sStartedService = false; /** * Information about the selected WebView package. This is set from #onWebViewProviderChanged(). */ @GuardedBy("sLock") private static PackageInfo sPackage; /** * Cache key for the selected WebView package's classloader. This is set from * #onWebViewProviderChanged(). */ @GuardedBy("sLock") private static String sPackageCacheKey; /** * Flag for whether multi-process WebView is enabled. If this is false, the zygote * will not be started. */ @GuardedBy("sLock") private static boolean sMultiprocessEnabled = false; public static ZygoteProcess getProcess() { synchronized (sLock) { if (sZygote != null) return sZygote; waitForServiceStartAndConnect(); return sZygote; } } public static String getPackageName() { synchronized (sLock) { return sPackage.packageName; } } public static boolean isMultiprocessEnabled() { synchronized (sLock) { return sMultiprocessEnabled && sPackage != null; } } public static void setMultiprocessEnabled(boolean enabled) { synchronized (sLock) { sMultiprocessEnabled = enabled; // When toggling between multi-process being on/off, start or stop the // service. If it is enabled and the zygote is not yet started, bring up the service. // Otherwise, bring down the service. The name may be null if the package // information has not yet been resolved. final String serviceName = getServiceNameLocked(); if (serviceName == null) return; if (enabled) { if (!sStartedService) { SystemService.start(serviceName); sStartedService = true; } } else { SystemService.stop(serviceName); sStartedService = false; sZygote = null; } } } public static void onWebViewProviderChanged(PackageInfo packageInfo, String cacheKey) { synchronized (sLock) { sPackage = packageInfo; sPackageCacheKey = cacheKey; // If multi-process is not enabled, then do not start the zygote service. if (!sMultiprocessEnabled) { return; } final String serviceName = getServiceNameLocked(); sZygote = null; // The service may enter the RUNNING state before it opens the socket, // so connectToZygoteIfNeededLocked() may still fail. if (SystemService.isStopped(serviceName)) { SystemService.start(serviceName); } else { SystemService.restart(serviceName); } sStartedService = true; } } private static void waitForServiceStartAndConnect() { if (!sStartedService) { throw new AndroidRuntimeException("Tried waiting for the WebView Zygote Service to " + "start running without first starting the service."); } String serviceName; synchronized (sLock) { serviceName = getServiceNameLocked(); } try { SystemService.waitForState(serviceName, SystemService.State.RUNNING, 5000); } catch (TimeoutException e) { Log.e(LOGTAG, "Timed out waiting for " + serviceName); return; } synchronized (sLock) { connectToZygoteIfNeededLocked(); } } @GuardedBy("sLock") private static String getServiceNameLocked() { if (sPackage == null) return null; if (Arrays.asList(Build.SUPPORTED_64_BIT_ABIS).contains( sPackage.applicationInfo.primaryCpuAbi)) { return WEBVIEW_ZYGOTE_SERVICE_64; } return WEBVIEW_ZYGOTE_SERVICE_32; } @GuardedBy("sLock") private static void connectToZygoteIfNeededLocked() { if (sZygote != null) { return; } if (sPackage == null) { Log.e(LOGTAG, "Cannot connect to zygote, no package specified"); return; } final String serviceName = getServiceNameLocked(); if (!SystemService.isRunning(serviceName)) { Log.e(LOGTAG, serviceName + " is not running"); return; } try { sZygote = new ZygoteProcess(WEBVIEW_ZYGOTE_SOCKET, null); // All the work below is usually done by LoadedApk, but the zygote can't talk to // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so // doesn't have an ActivityThread and can't use Binder. // Instead, figure out the paths here, in the system server where we have access to // the package manager. Reuse the logic from LoadedApk to determine the correct // paths and pass them to the zygote as strings. final List zipPaths = new ArrayList<>(10); final List libPaths = new ArrayList<>(10); LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths); final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths); final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) : TextUtils.join(File.pathSeparator, zipPaths); ZygoteProcess.waitForConnectionToZygote(WEBVIEW_ZYGOTE_SOCKET); Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath); sZygote.preloadPackageForAbi(zip, librarySearchPath, sPackageCacheKey, Build.SUPPORTED_ABIS[0]); } catch (Exception e) { Log.e(LOGTAG, "Error connecting to " + serviceName, e); sZygote = null; } } }