summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/AppZygote.java116
-rw-r--r--core/java/android/os/ZygoteProcess.java50
-rw-r--r--core/java/android/webkit/WebViewZygote.java1
-rw-r--r--core/java/com/android/internal/os/AppZygoteInit.java91
-rw-r--r--core/java/com/android/internal/os/ChildZygoteInit.java99
-rw-r--r--core/java/com/android/internal/os/WebViewZygoteInit.java50
-rw-r--r--core/java/com/android/internal/os/Zygote.java6
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java41
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java3
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java11
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java78
11 files changed, 496 insertions, 50 deletions
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
new file mode 100644
index 000000000000..22756934abc0
--- /dev/null
+++ b/core/java/android/os/AppZygote.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2018 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.os;
+
+import android.content.pm.ApplicationInfo;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * AppZygote is responsible for interfacing with an application-specific zygote.
+ *
+ * Application zygotes can pre-load app-specific code and data, and this interface can
+ * be used to spawn isolated services from such an application zygote.
+ *
+ * Note that we'll have only one instance of this per application / uid combination.
+ *
+ * @hide
+ */
+public class AppZygote {
+ private static final String LOG_TAG = "AppZygote";
+
+ private final Object mLock = 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("mLock")
+ private ChildZygoteProcess mZygote;
+
+ private final ApplicationInfo mAppInfo;
+
+ public AppZygote(ApplicationInfo appInfo) {
+ mAppInfo = appInfo;
+ }
+
+ /**
+ * Returns the zygote process associated with this app zygote.
+ * Creates the process if it's not already running.
+ */
+ public ChildZygoteProcess getProcess() {
+ synchronized (mLock) {
+ if (mZygote != null) return mZygote;
+
+ connectToZygoteIfNeededLocked();
+ return mZygote;
+ }
+ }
+
+ /**
+ * Stops the Zygote and kills the zygote process.
+ */
+ public void stopZygote() {
+ synchronized (mLock) {
+ stopZygoteLocked();
+ }
+ }
+
+ public ApplicationInfo getAppInfo() {
+ return mAppInfo;
+ }
+
+ @GuardedBy("mLock")
+ private void stopZygoteLocked() {
+ if (mZygote != null) {
+ // Close the connection and kill the zygote process. This will not cause
+ // child processes to be killed by itself.
+ mZygote.close();
+ Process.killProcess(mZygote.getPid());
+ mZygote = null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void connectToZygoteIfNeededLocked() {
+ String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi :
+ Build.SUPPORTED_ABIS[0];
+ try {
+ mZygote = Process.zygoteProcess.startChildZygote(
+ "com.android.internal.os.AppZygoteInit",
+ mAppInfo.processName + "_zygote",
+ mAppInfo.uid,
+ mAppInfo.uid,
+ null, // gids
+ 0, // runtimeFlags
+ "app_zygote", // seInfo
+ abi, // abi
+ abi, // acceptedAbiList
+ null); // instructionSet
+
+ ZygoteProcess.waitForConnectionToZygote(mZygote.getPrimarySocketAddress());
+ // preload application code in the zygote
+ Log.i(LOG_TAG, "Starting application preload.");
+ mZygote.preloadApp(mAppInfo, abi);
+ Log.i(LOG_TAG, "Application preload done.");
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error connecting to app zygote", e);
+ stopZygoteLocked();
+ }
+ }
+}
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index f136cd6699a7..251c5eebadc4 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -18,6 +18,7 @@ package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;
@@ -34,6 +35,7 @@ import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@@ -673,6 +675,36 @@ public class ZygoteProcess {
}
/**
+ * Instructs the zygote to pre-load the application code for the given Application.
+ * Only the app zygote supports this function.
+ * TODO preloadPackageForAbi() can probably be removed and the callers an use this instead.
+ */
+ public boolean preloadApp(ApplicationInfo appInfo, String abi) throws ZygoteStartFailedEx,
+ IOException {
+ synchronized (mLock) {
+ ZygoteState state = openZygoteSocketIfNeeded(abi);
+ state.writer.write("2");
+ state.writer.newLine();
+
+ state.writer.write("--preload-app");
+ state.writer.newLine();
+
+ // Zygote args needs to be strings, so in order to pass ApplicationInfo,
+ // write it to a Parcel, and base64 the raw Parcel bytes to the other side.
+ Parcel parcel = Parcel.obtain();
+ appInfo.writeToParcel(parcel, 0 /* flags */);
+ String encodedParcelData = Base64.getEncoder().encodeToString(parcel.marshall());
+ parcel.recycle();
+ state.writer.write(encodedParcelData);
+ state.writer.newLine();
+
+ state.writer.flush();
+
+ return (state.inputStream.readInt() == 0);
+ }
+ }
+
+ /**
* Instructs the zygote to pre-load the classes and native libraries at the given paths
* for the specified abi. Not all zygotes support this function.
*/
@@ -763,6 +795,20 @@ public class ZygoteProcess {
* secondary zygotes that inherit data from the zygote that this object
* communicates with. This returns a new ZygoteProcess representing a connection
* to the newly created zygote. Throws an exception if the zygote cannot be started.
+ *
+ * @param processClass The class to use as the child zygote's main entry
+ * point.
+ * @param niceName A more readable name to use for the process.
+ * @param uid The user-id under which the child zygote will run.
+ * @param gid The group-id under which the child zygote will run.
+ * @param gids Additional group-ids associated with the child zygote process.
+ * @param runtimeFlags Additional flags.
+ * @param seInfo null-ok SELinux information for the child zygote process.
+ * @param abi non-null the ABI of the child zygote
+ * @param acceptedAbiList ABIs this child zygote will accept connections for; this
+ * may be different from <code>abi</code> in case the children
+ * spawned from this Zygote only communicate using ABI-safe methods.
+ * @param instructionSet null-ok the instruction set to use.
*/
public ChildZygoteProcess startChildZygote(final String processClass,
final String niceName,
@@ -770,12 +816,14 @@ public class ZygoteProcess {
int runtimeFlags,
String seInfo,
String abi,
+ String acceptedAbiList,
String instructionSet) {
// Create an unguessable address in the global abstract namespace.
final LocalSocketAddress serverAddress = new LocalSocketAddress(
processClass + "/" + UUID.randomUUID().toString());
- final String[] extraArgs = {Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + serverAddress.getName()};
+ final String[] extraArgs = {Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + serverAddress.getName(),
+ Zygote.CHILD_ZYGOTE_ABI_LIST_ARG + acceptedAbiList};
Process.ProcessStartResult result;
try {
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 49e11b8baf51..9f7aa6a2852a 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -159,6 +159,7 @@ public class WebViewZygote {
0, // runtimeFlags
"webview_zygote", // seInfo
sPackage.applicationInfo.primaryCpuAbi, // abi
+ TextUtils.join(",", Build.SUPPORTED_ABIS),
null); // instructionSet
// All the work below is usually done by LoadedApk, but the zygote can't talk to
diff --git a/core/java/com/android/internal/os/AppZygoteInit.java b/core/java/com/android/internal/os/AppZygoteInit.java
new file mode 100644
index 000000000000..09f4b36027cf
--- /dev/null
+++ b/core/java/com/android/internal/os/AppZygoteInit.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 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 com.android.internal.os;
+
+import android.app.LoadedApk;
+import android.content.pm.ApplicationInfo;
+import android.net.LocalSocket;
+import android.util.Log;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * Startup class for an Application zygote process.
+ *
+ * See {@link ZygoteInit} for generic zygote startup documentation.
+ *
+ * @hide
+ */
+class AppZygoteInit {
+ public static final String TAG = "AppZygoteInit";
+
+ private static ZygoteServer sServer;
+
+ private static class AppZygoteServer extends ZygoteServer {
+ @Override
+ protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
+ throws IOException {
+ return new AppZygoteConnection(socket, abiList);
+ }
+ }
+
+ private static class AppZygoteConnection extends ZygoteConnection {
+ AppZygoteConnection(LocalSocket socket, String abiList) throws IOException {
+ super(socket, abiList);
+ }
+
+ @Override
+ protected void preload() {
+ // Nothing to preload by default.
+ }
+
+ @Override
+ protected boolean isPreloadComplete() {
+ // App zygotes don't preload any classes or resources or defaults, all of their
+ // preloading is package specific.
+ return true;
+ }
+
+ @Override
+ protected boolean canPreloadApp() {
+ return true;
+ }
+
+ @Override
+ protected void handlePreloadApp(ApplicationInfo appInfo) {
+ Log.i(TAG, "Beginning application preload for " + appInfo.packageName);
+ LoadedApk loadedApk = new LoadedApk(null, appInfo, null, null, false, true, false);
+ // Initialize the classLoader
+ ClassLoader loader = loadedApk.getClassLoader();
+
+ try {
+ DataOutputStream socketOut = getSocketOutputStream();
+ socketOut.writeInt(loader != null ? 1 : 0);
+ } catch (IOException e) {
+ throw new IllegalStateException("Error writing to command socket", e);
+ }
+
+ Log.i(TAG, "Application preload done");
+ }
+ }
+
+ public static void main(String[] argv) {
+ AppZygoteServer server = new AppZygoteServer();
+ ChildZygoteInit.runZygoteServer(server, argv);
+ }
+}
diff --git a/core/java/com/android/internal/os/ChildZygoteInit.java b/core/java/com/android/internal/os/ChildZygoteInit.java
new file mode 100644
index 000000000000..f90cd0224596
--- /dev/null
+++ b/core/java/com/android/internal/os/ChildZygoteInit.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018 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 com.android.internal.os;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+
+/**
+ * ChildZygoteInit is shared by both the Application and WebView zygote to initialize
+ * and run a (child) Zygote server.
+ *
+ * @hide
+ */
+public class ChildZygoteInit {
+ private static final String TAG = "ChildZygoteInit";
+
+ static String parseSocketNameFromArgs(String[] argv) {
+ for (String arg : argv) {
+ if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
+ return arg.substring(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG.length());
+ }
+ }
+
+ return null;
+ }
+
+ static String parseAbiListFromArgs(String[] argv) {
+ for (String arg : argv) {
+ if (arg.startsWith(Zygote.CHILD_ZYGOTE_ABI_LIST_ARG)) {
+ return arg.substring(Zygote.CHILD_ZYGOTE_ABI_LIST_ARG.length());
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Starts a ZygoteServer and listens for requests
+ *
+ * @param server An instance of a ZygoteServer to listen on
+ * @param args Passed in arguments for this ZygoteServer
+ */
+ static void runZygoteServer(ZygoteServer server, String[] args) {
+ String socketName = parseSocketNameFromArgs(args);
+ if (socketName == null) {
+ throw new NullPointerException("No socketName specified");
+ }
+
+ String abiList = parseAbiListFromArgs(args);
+ if (abiList == null) {
+ throw new NullPointerException("No abiList specified");
+ }
+
+ try {
+ Os.prctl(OsConstants.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+ } catch (ErrnoException ex) {
+ throw new RuntimeException("Failed to set PR_SET_NO_NEW_PRIVS", ex);
+ }
+
+ final Runnable caller;
+ try {
+ server.registerServerSocketAtAbstractName(socketName);
+
+ // Add the abstract socket to the FD whitelist so that the native zygote code
+ // can properly detach it after forking.
+ Zygote.nativeAllowFileAcrossFork("ABSTRACT/" + socketName);
+
+ // The select loop returns early in the child process after a fork and
+ // loops forever in the zygote.
+ caller = server.runSelectLoop(abiList);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Fatal exception:", e);
+ throw e;
+ } finally {
+ server.closeServerSocket();
+ }
+
+ // We're in the child process and have exited the select loop. Proceed to execute the
+ // command.
+ if (caller != null) {
+ caller.run();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index 9f2434e97d7a..2c8e66d010f9 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -18,11 +18,7 @@ package com.android.internal.os;
import android.app.ApplicationLoaders;
import android.net.LocalSocket;
-import android.net.LocalServerSocket;
import android.os.Build;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.WebViewFactory;
@@ -44,8 +40,6 @@ import java.lang.reflect.Method;
class WebViewZygoteInit {
public static final String TAG = "WebViewZygoteInit";
- private static ZygoteServer sServer;
-
private static class WebViewZygoteServer extends ZygoteServer {
@Override
protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
@@ -128,47 +122,7 @@ class WebViewZygoteInit {
public static void main(String argv[]) {
Log.i(TAG, "Starting WebViewZygoteInit");
- String socketName = null;
- for (String arg : argv) {
- Log.i(TAG, arg);
- if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
- socketName = arg.substring(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG.length());
- }
- }
- if (socketName == null) {
- throw new RuntimeException("No " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + " specified");
- }
-
- try {
- Os.prctl(OsConstants.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
- } catch (ErrnoException ex) {
- throw new RuntimeException("Failed to set PR_SET_NO_NEW_PRIVS", ex);
- }
-
- sServer = new WebViewZygoteServer();
-
- final Runnable caller;
- try {
- sServer.registerServerSocketAtAbstractName(socketName);
-
- // Add the abstract socket to the FD whitelist so that the native zygote code
- // can properly detach it after forking.
- Zygote.nativeAllowFileAcrossFork("ABSTRACT/" + socketName);
-
- // The select loop returns early in the child process after a fork and
- // loops forever in the zygote.
- caller = sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
- } catch (RuntimeException e) {
- Log.e(TAG, "Fatal exception:", e);
- throw e;
- } finally {
- sServer.closeServerSocket();
- }
-
- // We're in the child process and have exited the select loop. Proceed to execute the
- // command.
- if (caller != null) {
- caller.run();
- }
+ WebViewZygoteServer server = new WebViewZygoteServer();
+ ChildZygoteInit.runZygoteServer(server, argv);
}
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 65b9fad97d89..d720c689f5de 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -98,6 +98,12 @@ public final class Zygote {
*/
public static final String CHILD_ZYGOTE_SOCKET_NAME_ARG = "--zygote-socket=";
+ /**
+ * An extraArg passed when a zygote process is forking a child-zygote, specifying the
+ * requested ABI for the child Zygote.
+ */
+ public static final String CHILD_ZYGOTE_ABI_LIST_ARG = "--abi-list=";
+
private Zygote() {}
/** Called for some security initialization before any fork. */
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index f182c4d447df..5990d72d89b4 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -27,9 +27,11 @@ import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEO
import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
+import android.content.pm.ApplicationInfo;
import android.net.Credentials;
import android.net.LocalSocket;
import android.os.FactoryTest;
+import android.os.Parcel;
import android.os.Process;
import android.os.SystemProperties;
import android.os.Trace;
@@ -52,6 +54,7 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Base64;
/**
* A connection that can make spawn requests.
@@ -168,6 +171,21 @@ class ZygoteConnection {
return null;
}
+ if (canPreloadApp() && parsedArgs.mPreloadApp != null) {
+ byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp);
+ Parcel appInfoParcel = Parcel.obtain();
+ appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length);
+ appInfoParcel.setDataPosition(0);
+ ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(appInfoParcel);
+ appInfoParcel.recycle();
+ if (appInfo != null) {
+ handlePreloadApp(appInfo);
+ } else {
+ throw new IllegalArgumentException("Failed to deserialize --preload-app");
+ }
+ return null;
+ }
+
if (parsedArgs.apiBlacklistExemptions != null) {
handleApiBlacklistExemptions(parsedArgs.apiBlacklistExemptions);
return null;
@@ -341,7 +359,15 @@ class ZygoteConnection {
protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
String cacheKey) {
- throw new RuntimeException("Zyogte does not support package preloading");
+ throw new RuntimeException("Zygote does not support package preloading");
+ }
+
+ protected boolean canPreloadApp() {
+ return false;
+ }
+
+ protected void handlePreloadApp(ApplicationInfo aInfo) {
+ throw new RuntimeException("Zygote does not support app preloading");
}
/**
@@ -467,6 +493,12 @@ class ZygoteConnection {
String preloadPackage;
/**
+ * A Base64 string representing a serialize ApplicationInfo Parcel,
+ when using --preload-app.
+ */
+ String mPreloadApp;
+
+ /**
* The native library path of the package to preload, when using --preload-package.
*/
String preloadPackageLibs;
@@ -666,6 +698,8 @@ class ZygoteConnection {
instructionSet = arg.substring(arg.indexOf('=') + 1);
} else if (arg.startsWith("--app-data-dir=")) {
appDataDir = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.equals("--preload-app")) {
+ mPreloadApp = args[++curArg];
} else if (arg.equals("--preload-package")) {
preloadPackage = args[++curArg];
preloadPackageLibs = args[++curArg];
@@ -714,6 +748,11 @@ class ZygoteConnection {
throw new IllegalArgumentException(
"Unexpected arguments after --preload-package.");
}
+ } else if (mPreloadApp != null) {
+ if (args.length - curArg > 0) {
+ throw new IllegalArgumentException(
+ "Unexpected arguments after --preload-app.");
+ }
} else if (expectRuntimeArgs) {
if (!seenRuntimeArgs) {
throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a94fa12e4f35..ed39d8302bf7 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2503,6 +2503,9 @@ public final class ActiveServices {
&& r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) {
hostingType = "webview_service";
}
+ if ((r.serviceInfo.flags & ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0) {
+ hostingType = "app_zygote";
+ }
}
// Not running -- get it started, and enqueue this service record
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b053979f1fcd..e171736bd9cc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -235,6 +235,7 @@ import android.media.audiofx.AudioEffect;
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.Uri;
+import android.os.AppZygote;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.BinderProxy;
@@ -452,6 +453,9 @@ public class ActivityManagerService extends IActivityManager.Stub
// before we decide it must be hung.
static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;
+ // How long we wait to kill an application zygote, after the last process using
+ // it has gone away.
+ static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000;
/**
* How long we wait for an provider to be published. Should be longer than
* {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT}.
@@ -1441,6 +1445,7 @@ public class ActivityManagerService extends IActivityManager.Stub
static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
+ static final int KILL_APP_ZYGOTE_MSG = 71;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1644,6 +1649,12 @@ public class ActivityManagerService extends IActivityManager.Stub
false, userId, reason);
}
} break;
+ case KILL_APP_ZYGOTE_MSG: {
+ synchronized (ActivityManagerService.this) {
+ final AppZygote appZygote = (AppZygote) msg.obj;
+ mProcessList.killAppZygoteIfNeededLocked(appZygote);
+ }
+ } break;
case CHECK_EXCESSIVE_POWER_USE_MSG: {
synchronized (ActivityManagerService.this) {
checkExcessivePowerUsageLocked();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 895a86ba9fc7..896452b4ad62 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -34,6 +34,8 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.KILL_APP_ZYGOTE_DELAY_MS;
+import static com.android.server.am.ActivityManagerService.KILL_APP_ZYGOTE_MSG;
import static com.android.server.am.ActivityManagerService.PERSISTENT_MASK;
import static com.android.server.am.ActivityManagerService.PROC_START_TIMEOUT;
import static com.android.server.am.ActivityManagerService.PROC_START_TIMEOUT_MSG;
@@ -58,6 +60,7 @@ import android.graphics.Point;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.net.Uri;
+import android.os.AppZygote;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -75,6 +78,7 @@ import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.EventLog;
import android.util.LongSparseArray;
import android.util.Slog;
@@ -354,6 +358,17 @@ public final class ProcessList {
final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<>();
/**
+ * The currently running application zygotes.
+ */
+ final ProcessMap<AppZygote> mAppZygotes = new ProcessMap<AppZygote>();
+
+ /**
+ * The processes that are forked off an application zygote.
+ */
+ final ArrayMap<AppZygote, ArrayList<ProcessRecord>> mAppZygoteProcesses =
+ new ArrayMap<AppZygote, ArrayList<ProcessRecord>>();
+
+ /**
* Counter for assigning isolated process uids, to avoid frequently reusing the
* same ones.
*/
@@ -1505,6 +1520,54 @@ public final class ProcessList {
}
}
+ @GuardedBy("mService")
+ public void killAppZygoteIfNeededLocked(AppZygote appZygote) {
+ final ApplicationInfo appInfo = appZygote.getAppInfo();
+ ArrayList<ProcessRecord> zygoteProcesses = mAppZygoteProcesses.get(appZygote);
+ if (zygoteProcesses.size() == 0) { // Only remove if no longer in use now
+ mAppZygotes.remove(appInfo.processName, appInfo.uid);
+ mAppZygoteProcesses.remove(appZygote);
+ appZygote.stopZygote();
+ }
+ }
+
+ @GuardedBy("mService")
+ private void removeProcessFromAppZygoteLocked(final ProcessRecord app) {
+ final AppZygote appZygote = mAppZygotes.get(app.info.processName, app.info.uid);
+ if (appZygote != null) {
+ ArrayList<ProcessRecord> zygoteProcesses = mAppZygoteProcesses.get(appZygote);
+ zygoteProcesses.remove(app);
+ if (zygoteProcesses.size() == 0) {
+ Message msg = mService.mHandler.obtainMessage(KILL_APP_ZYGOTE_MSG);
+ msg.obj = appZygote;
+ mService.mHandler.sendMessageDelayed(msg, KILL_APP_ZYGOTE_DELAY_MS);
+ }
+ }
+ }
+
+ private AppZygote createAppZygoteForProcessIfNeeded(final ProcessRecord app) {
+ synchronized (mService) {
+ AppZygote appZygote = mAppZygotes.get(app.info.processName, app.info.uid);
+ final ArrayList<ProcessRecord> zygoteProcessList;
+ if (appZygote == null) {
+ appZygote = new AppZygote(app.info);
+ mAppZygotes.put(app.info.processName, app.info.uid, appZygote);
+ zygoteProcessList = new ArrayList<ProcessRecord>();
+ mAppZygoteProcesses.put(appZygote, zygoteProcessList);
+ } else {
+ mService.mHandler.removeMessages(KILL_APP_ZYGOTE_MSG, appZygote);
+ zygoteProcessList = mAppZygoteProcesses.get(appZygote);
+ }
+ // Note that we already add the app to mAppZygoteProcesses here;
+ // this is so that another thread can't come in and kill the zygote
+ // before we've even tried to start the process. If the process launch
+ // goes wrong, we'll clean this up in removeProcessNameLocked()
+ zygoteProcessList.add(app);
+
+ return appZygote;
+ }
+ }
+
private Process.ProcessStartResult startProcess(String hostingType, String entryPoint,
ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
String seInfo, String requiredAbi, String instructionSet, String invokeWith,
@@ -1525,6 +1588,15 @@ public final class ProcessList {
app.info.dataDir, null, app.info.packageName,
packageNames, visibleVolIds,
new String[] {PROC_START_SEQ_IDENT + app.startSeq});
+ } else if (hostingType.equals("app_zygote")) {
+ final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
+
+ startResult = appZygote.getProcess().start(entryPoint,
+ app.processName, uid, uid, gids, runtimeFlags, mountExternal,
+ app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
+ app.info.dataDir, null, app.info.packageName,
+ packageNames, visibleVolIds,
+ new String[] {PROC_START_SEQ_IDENT + app.startSeq});
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
@@ -2093,6 +2165,12 @@ public final class ProcessList {
old.uidRecord = null;
}
mIsolatedProcesses.remove(uid);
+ // Remove the (expected) ProcessRecord from the app zygote
+ final ProcessRecord record = expecting != null ? expecting : old;
+ if (record != null && record.isolated) {
+ removeProcessFromAppZygoteLocked(record);
+ }
+
return old;
}