diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 9b64f0cbbb25c..39b9d66a9623f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -5579,7 +5579,7 @@ public final class ActivityThread { // Make sure we do this before calling onCreate so that we can capture the // complete application startup. if (SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) { - BaseDexClassLoader.setReporter(DexLoadReporter.INSTANCE); + BaseDexClassLoader.setReporter(DexLoadReporter.getInstance()); } // Install the Network Security Config Provider. This must happen before the application diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java index 009c44833e570..13f288ab74549 100644 --- a/core/java/android/app/DexLoadReporter.java +++ b/core/java/android/app/DexLoadReporter.java @@ -16,13 +16,21 @@ package android.app; +import android.os.FileUtils; import android.os.RemoteException; +import android.os.SystemProperties; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; + import dalvik.system.BaseDexClassLoader; import dalvik.system.VMRuntime; +import java.io.File; +import java.io.IOException; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * A dex load reporter which will notify package manager of any dex file loaded @@ -37,15 +45,60 @@ import java.util.List; /*package*/ class DexLoadReporter implements BaseDexClassLoader.Reporter { private static final String TAG = "DexLoadReporter"; - /*package*/ static final DexLoadReporter INSTANCE = new DexLoadReporter(); + private static final DexLoadReporter INSTANCE = new DexLoadReporter(); - private DexLoadReporter() {} + private static final boolean DEBUG = false; + + // We must guard the access to the list of data directories because + // we might have concurrent accesses. Apps might load dex files while + // new data dirs are registered (due to creation of LoadedApks via + // create createApplicationContext). + @GuardedBy("mDataDirs") + private final Set mDataDirs; + + private DexLoadReporter() { + mDataDirs = new HashSet<>(); + } + + /*package*/ static DexLoadReporter getInstance() { + return INSTANCE; + } + + /** + * Register an application data directory with the reporter. + * The data directories are used to determine if a dex file is secondary dex or not. + * Note that this method may be called multiple times for the same app, registering + * different data directories. This may happen when apps share the same user id + * ({@code android:sharedUserId}). For example, if app1 and app2 share the same user + * id, and app1 loads app2 apk, then both data directories will be registered. + */ + /*package*/ void registerAppDataDir(String packageName, String dataDir) { + if (DEBUG) { + Slog.i(TAG, "Package " + packageName + " registering data dir: " + dataDir); + } + // TODO(calin): A few code paths imply that the data dir + // might be null. Investigate when that can happen. + if (dataDir != null) { + synchronized (mDataDirs) { + mDataDirs.add(dataDir); + } + } + } @Override public void report(List dexPaths) { if (dexPaths.isEmpty()) { return; } + // Notify the package manager about the dex loads unconditionally. + // The load might be for either a primary or secondary dex file. + notifyPackageManager(dexPaths); + // Check for secondary dex files and register them for profiling if + // possible. + registerSecondaryDexForProfiling(dexPaths); + } + + private void notifyPackageManager(List dexPaths) { String packageName = ActivityThread.currentPackageName(); try { ActivityThread.getPackageManager().notifyDexLoad( @@ -54,4 +107,62 @@ import java.util.List; Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); } } + + private void registerSecondaryDexForProfiling(List dexPaths) { + if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) { + return; + } + // Make a copy of the current data directories so that we don't keep the lock + // while registering for profiling. The registration will perform I/O to + // check for or create the profile. + String[] dataDirs; + synchronized (mDataDirs) { + dataDirs = mDataDirs.toArray(new String[0]); + } + for (String dexPath : dexPaths) { + registerSecondaryDexForProfiling(dexPath, dataDirs); + } + } + + private void registerSecondaryDexForProfiling(String dexPath, String[] dataDirs) { + if (!isSecondaryDexFile(dexPath, dataDirs)) { + // The dex path is not a secondary dex file. Nothing to do. + return; + } + File secondaryProfile = getSecondaryProfileFile(dexPath); + try { + // Create the profile if not already there. + // Returns true if the file was created, false if the file already exists. + // or throws exceptions in case of errors. + boolean created = secondaryProfile.createNewFile(); + if (DEBUG && created) { + Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile); + } + } catch (IOException ex) { + Slog.e(TAG, "Failed to create profile for secondary dex " + secondaryProfile + + ":" + ex.getMessage()); + // Don't move forward with the registration if we failed to create the profile. + return; + } + + VMRuntime.registerAppInfo(secondaryProfile.getPath(), new String[] { dexPath }); + } + + // A dex file is a secondary dex file if it is in any of the registered app + // data directories. + private boolean isSecondaryDexFile(String dexPath, String[] dataDirs) { + for (String dataDir : dataDirs) { + if (FileUtils.contains(dataDir, dexPath)) { + return true; + } + } + return false; + } + + // Secondary dex profiles are stored next to the dex file and have the same + // name with '.prof' appended. + // NOTE: Keep in sync with installd. + private File getSecondaryProfileFile(String dexPath) { + return new File(dexPath + ".prof"); + } } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 8299c50ed005f..cf41e4e5dc9c8 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -750,6 +750,11 @@ public final class LoadedApk { VMRuntime.registerAppInfo(profileFile.getPath(), codePaths.toArray(new String[codePaths.size()])); + + // Register the app data directory with the reporter. It will + // help deciding whether or not a dex file is the primary apk or a + // secondary dex. + DexLoadReporter.getInstance().registerAppDataDir(mPackageName, mDataDir); } /**