blob: 0204dff9bf9d9954b7aedbca8d176842278621d0 [file] [log] [blame]
/*
* 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<String> zipPaths = new ArrayList<>(10);
final List<String> 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;
}
}
}