Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3b6ca99b authored by Torne (Richard Coles)'s avatar Torne (Richard Coles)
Browse files

Precreate the classloader for the WebView.

We want to create the classloader for the WebView in advance in the
zygote so that it can preload Java and native code for its children, but
the zygote can't talk to the package manager (so doesn't have a
PackageInfo for the APK) and also doesn't have an ActivityThread, so
constructing a LoadedApk is difficult.

Instead, we use the fact that ApplicationLoaders contains a
process-global cache of classloaders for APKs, and prepopulate a cache
entry without constructing a LoadedApk. This requires making
ApplicationLoaders public. To calculate the correct library paths from
the information the zygote has, we reuse the logic in LoadedApk (which
is already public, and just needs a small change to allow a null
ActivityThread when checking for instrumentation).

The other parameters for classloader creation (target SDK, bundled app,
etc) are hardcoded to usable values for the WebView's case. WebView
never needs to use any system libraries that aren't public so claiming
it's not bundled is fine even when that isn't actually true, and WebView
will always target the current platform API level.

Once the classloader is created, look up the factory class and call
preloadInZygote on it to give it a chance to preload the native library
and do other shared initialisation.

Bug: 21643067
Test: enable multiprocess WebView, examine librank output to see sharing
Change-Id: I696ead637e3f7382bcc58cfaf61eac5921862015
parent 82c9ba9c
Loading
Loading
Loading
Loading
+17 −2
Original line number Diff line number Diff line
@@ -16,17 +16,19 @@

package android.app;

import android.os.Build;
import android.os.Trace;
import android.util.ArrayMap;
import com.android.internal.os.PathClassLoaderFactory;
import dalvik.system.PathClassLoader;

class ApplicationLoaders {
/** @hide */
public class ApplicationLoaders {
    public static ApplicationLoaders getDefault() {
        return gApplicationLoaders;
    }

    public ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
    ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
                                      String librarySearchPath, String libraryPermittedPath,
                                      ClassLoader parent) {
        /*
@@ -80,6 +82,19 @@ class ApplicationLoaders {
        }
    }

    /**
     * Creates a classloader for the WebView APK and places it in the cache of loaders maintained
     * by this class. This is used in the WebView zygote, where its presence in the cache speeds up
     * startup and enables memory sharing.
     */
    public ClassLoader createAndCacheWebViewClassLoader(String packagePath, String libsPath) {
      // The correct paths are calculated by WebViewZygote in the system server and passed to
      // us here. We hardcode the other parameters: WebView always targets the current SDK,
      // does not need to use non-public system libraries, and uses the base classloader as its
      // parent to permit usage of the cache.
      return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null);
    }

    private static native void setupVulkanLayerPath(ClassLoader classLoader, String librarySearchPath);

    /**
+32 −28
Original line number Diff line number Diff line
@@ -339,6 +339,10 @@ public final class LoadedApk {
         * concatenation of both apps' shared library lists.
         */

        String[] instrumentationLibs = null;
        // activityThread will be null when called from the WebView zygote; just assume
        // no instrumentation applies in this case.
        if (activityThread != null) {
            String instrumentationPackageName = activityThread.mInstrumentationPackageName;
            String instrumentationAppDir = activityThread.mInstrumentationAppDir;
            String[] instrumentationSplitAppDirs = activityThread.mInstrumentationSplitAppDirs;
@@ -347,7 +351,6 @@ public final class LoadedApk {
            String instrumentedAppDir = activityThread.mInstrumentedAppDir;
            String[] instrumentedSplitAppDirs = activityThread.mInstrumentedSplitAppDirs;
            String instrumentedLibDir = activityThread.mInstrumentedLibDir;
        String[] instrumentationLibs = null;

            if (appDir.equals(instrumentationAppDir)
                    || appDir.equals(instrumentedAppDir)) {
@@ -374,6 +377,7 @@ public final class LoadedApk {
                    instrumentationLibs = getLibrariesFor(instrumentationPackageName);
                }
            }
        }

        if (outLibPaths != null) {
            if (outLibPaths.isEmpty()) {
+3 −1
Original line number Diff line number Diff line
@@ -56,7 +56,9 @@ import java.util.zip.ZipFile;
@SystemApi
public final class WebViewFactory {

    private static final String CHROMIUM_WEBVIEW_FACTORY =
    // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
    /** @hide */
    public static final String CHROMIUM_WEBVIEW_FACTORY =
            "com.android.webview.chromium.WebViewChromiumFactoryProvider";

    private static final String NULL_WEBVIEW_FACTORY =
+20 −5
Original line number Diff line number Diff line
@@ -16,14 +16,19 @@

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.Log;

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 */
@@ -122,11 +127,21 @@ public class WebViewZygote {
        try {
            sZygote = new ZygoteProcess("webview_zygote", null);

            String packagePath = sPackage.applicationInfo.sourceDir;
            String libsPath = sPackage.applicationInfo.nativeLibraryDir;

            Log.d(LOGTAG, "Preloading package " + packagePath + " " + libsPath);
            sZygote.preloadPackageForAbi(packagePath, libsPath, Build.SUPPORTED_ABIS[0]);
            // 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, 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);

            Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
            sZygote.preloadPackageForAbi(zip, librarySearchPath, Build.SUPPORTED_ABIS[0]);
        } catch (Exception e) {
            Log.e(LOGTAG, "Error connecting to " + serviceName, e);
            sZygote = null;
+24 −1
Original line number Diff line number Diff line
@@ -16,14 +16,17 @@

package com.android.internal.os;

import android.app.ApplicationLoaders;
import android.net.LocalSocket;
import android.os.Build;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.WebViewFactory;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

/**
 * Startup class for the WebView zygote process.
@@ -52,7 +55,27 @@ class WebViewZygoteInit {

        @Override
        protected boolean handlePreloadPackage(String packagePath, String libsPath) {
            // TODO: Use preload information to setup the ClassLoader.
            // Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
            // our children will reuse the same classloader instead of creating their own.
            // This enables us to preload Java and native code in the webview zygote process and
            // have the preloaded versions actually be used post-fork.
            ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
                    packagePath, libsPath);

            // Once we have the classloader, look up the WebViewFactoryProvider implementation and
            // call preloadInZygote() on it to give it the opportunity to preload the native library
            // and perform any other initialisation work that should be shared among the children.
            try {
                Class providerClass = Class.forName(WebViewFactory.CHROMIUM_WEBVIEW_FACTORY, true,
                                                    loader);
                Object result = providerClass.getMethod("preloadInZygote").invoke(null);
                if (!((Boolean)result).booleanValue()) {
                    Log.e(TAG, "preloadInZygote returned false");
                }
            } catch (ClassNotFoundException | NoSuchMethodException | SecurityException |
                     IllegalAccessException | InvocationTargetException e) {
                Log.e(TAG, "Exception while preloading package", e);
            }
            return false;
        }
    }