| /* |
| * 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.ApplicationInfo; |
| import android.content.pm.PackageInfo; |
| import android.os.AsyncTask; |
| import android.os.Build; |
| import android.os.ChildZygoteProcess; |
| import android.os.Process; |
| import android.os.ZygoteProcess; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.GuardedBy; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** @hide */ |
| public class WebViewZygote { |
| private static final String LOGTAG = "WebViewZygote"; |
| |
| /** |
| * 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 {@code null} if the |
| * zygote is not running or is not connected. |
| */ |
| @GuardedBy("sLock") |
| private static ChildZygoteProcess sZygote; |
| |
| /** |
| * Information about the selected WebView package. This is set from #onWebViewProviderChanged(). |
| */ |
| @GuardedBy("sLock") |
| private static PackageInfo sPackage; |
| |
| /** |
| * Original ApplicationInfo for the selected WebView package before stub fixup. This is set from |
| * #onWebViewProviderChanged(). |
| */ |
| @GuardedBy("sLock") |
| private static ApplicationInfo sPackageOriginalAppInfo; |
| |
| /** |
| * Flag for whether multi-process WebView is enabled. If this is {@code 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; |
| |
| connectToZygoteIfNeededLocked(); |
| 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 |
| // zygote. If it is enabled and the zygote is not yet started, launch it. |
| // Otherwise, kill it. The name may be null if the package information has |
| // not yet been resolved. |
| if (enabled) { |
| // Run on a background thread as this waits for the zygote to start and we don't |
| // want to block the caller on this. It's okay if this is delayed as anyone trying |
| // to use the zygote will call it first anyway. |
| AsyncTask.THREAD_POOL_EXECUTOR.execute(WebViewZygote::getProcess); |
| } else { |
| // No need to run this in the background, it's very brief. |
| stopZygoteLocked(); |
| } |
| } |
| } |
| |
| public static void onWebViewProviderChanged(PackageInfo packageInfo, |
| ApplicationInfo originalAppInfo) { |
| synchronized (sLock) { |
| sPackage = packageInfo; |
| sPackageOriginalAppInfo = originalAppInfo; |
| |
| // If multi-process is not enabled, then do not start the zygote service. |
| if (!sMultiprocessEnabled) { |
| return; |
| } |
| |
| stopZygoteLocked(); |
| } |
| } |
| |
| @GuardedBy("sLock") |
| private static void stopZygoteLocked() { |
| if (sZygote != null) { |
| // Close the connection and kill the zygote process. This will not cause |
| // child processes to be killed by itself. But if this is called in response to |
| // setMultiprocessEnabled() or onWebViewProviderChanged(), the WebViewUpdater |
| // will kill all processes that depend on the WebView package. |
| sZygote.close(); |
| Process.killProcess(sZygote.getPid()); |
| sZygote = null; |
| } |
| } |
| |
| @GuardedBy("sLock") |
| private static void connectToZygoteIfNeededLocked() { |
| if (sZygote != null) { |
| return; |
| } |
| |
| if (sPackage == null) { |
| Log.e(LOGTAG, "Cannot connect to zygote, no package specified"); |
| return; |
| } |
| |
| try { |
| sZygote = Process.ZYGOTE_PROCESS.startChildZygote( |
| "com.android.internal.os.WebViewZygoteInit", |
| "webview_zygote", |
| Process.WEBVIEW_ZYGOTE_UID, |
| Process.WEBVIEW_ZYGOTE_UID, |
| null, // gids |
| 0, // runtimeFlags |
| "webview_zygote", // seInfo |
| sPackage.applicationInfo.primaryCpuAbi, // abi |
| null); // instructionSet |
| |
| // 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); |
| |
| String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo); |
| |
| // In the case where the ApplicationInfo has been modified by the stub WebView, |
| // we need to use the original ApplicationInfo to determine what the original classpath |
| // would have been to use as a cache key. |
| LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null); |
| final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) : |
| TextUtils.join(File.pathSeparator, zipPaths); |
| |
| ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress()); |
| |
| Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath); |
| sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey, |
| Build.SUPPORTED_ABIS[0]); |
| } catch (Exception e) { |
| Log.e(LOGTAG, "Error connecting to webview zygote", e); |
| stopZygoteLocked(); |
| } |
| } |
| } |