1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.app; 18 19 import android.os.FileUtils; 20 import android.os.RemoteException; 21 import android.os.SystemProperties; 22 import android.util.Slog; 23 24 import com.android.internal.annotations.GuardedBy; 25 26 import dalvik.system.BaseDexClassLoader; 27 import dalvik.system.VMRuntime; 28 29 import java.io.File; 30 import java.io.IOException; 31 import java.util.HashSet; 32 import java.util.Map; 33 import java.util.Set; 34 35 /** 36 * A dex load reporter which will notify package manager of any dex file loaded 37 * with {@code BaseDexClassLoader}. 38 * The goals are: 39 * 1) discover secondary dex files so that they can be optimized during the 40 * idle maintenance job. 41 * 2) determine whether or not a dex file is used by an app which does not 42 * own it (in order to select the optimal compilation method). 43 * @hide 44 */ 45 /*package*/ class DexLoadReporter implements BaseDexClassLoader.Reporter { 46 private static final String TAG = "DexLoadReporter"; 47 48 private static final DexLoadReporter INSTANCE = new DexLoadReporter(); 49 50 private static final boolean DEBUG = false; 51 52 // We must guard the access to the list of data directories because 53 // we might have concurrent accesses. Apps might load dex files while 54 // new data dirs are registered (due to creation of LoadedApks via 55 // create createApplicationContext). 56 @GuardedBy("mDataDirs") 57 private final Set<String> mDataDirs; 58 DexLoadReporter()59 private DexLoadReporter() { 60 mDataDirs = new HashSet<>(); 61 } 62 getInstance()63 /*package*/ static DexLoadReporter getInstance() { 64 return INSTANCE; 65 } 66 67 /** 68 * Register an application data directory with the reporter. 69 * The data directories are used to determine if a dex file is secondary dex or not. 70 * Note that this method may be called multiple times for the same app, registering 71 * different data directories. This may happen when apps share the same user id 72 * ({@code android:sharedUserId}). For example, if app1 and app2 share the same user 73 * id, and app1 loads app2 apk, then both data directories will be registered. 74 */ registerAppDataDir(String packageName, String dataDir)75 /*package*/ void registerAppDataDir(String packageName, String dataDir) { 76 if (DEBUG) { 77 Slog.i(TAG, "Package " + packageName + " registering data dir: " + dataDir); 78 } 79 // TODO(calin): A few code paths imply that the data dir 80 // might be null. Investigate when that can happen. 81 if (dataDir != null) { 82 synchronized (mDataDirs) { 83 mDataDirs.add(dataDir); 84 } 85 } 86 } 87 88 @Override report(Map<String, String> classLoaderContextMap)89 public void report(Map<String, String> classLoaderContextMap) { 90 if (classLoaderContextMap.isEmpty()) { 91 Slog.wtf(TAG, "Bad call to DexLoadReporter: empty classLoaderContextMap"); 92 return; 93 } 94 95 // Notify the package manager about the dex loads unconditionally. 96 // The load might be for either a primary or secondary dex file. 97 notifyPackageManager(classLoaderContextMap); 98 // Check for secondary dex files and register them for profiling if possible. 99 // Note that we only register the dex paths belonging to the first class loader. 100 registerSecondaryDexForProfiling(classLoaderContextMap.keySet()); 101 } 102 notifyPackageManager(Map<String, String> classLoaderContextMap)103 private void notifyPackageManager(Map<String, String> classLoaderContextMap) { 104 // Get the class loader names for the binder call. 105 String packageName = ActivityThread.currentPackageName(); 106 try { 107 ActivityThread.getPackageManager().notifyDexLoad(packageName, 108 classLoaderContextMap, VMRuntime.getRuntime().vmInstructionSet()); 109 } catch (RemoteException re) { 110 Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); 111 } 112 } 113 registerSecondaryDexForProfiling(Set<String> dexPaths)114 private void registerSecondaryDexForProfiling(Set<String> dexPaths) { 115 if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) { 116 return; 117 } 118 // Make a copy of the current data directories so that we don't keep the lock 119 // while registering for profiling. The registration will perform I/O to 120 // check for or create the profile. 121 String[] dataDirs; 122 synchronized (mDataDirs) { 123 dataDirs = mDataDirs.toArray(new String[0]); 124 } 125 for (String dexPath : dexPaths) { 126 registerSecondaryDexForProfiling(dexPath, dataDirs); 127 } 128 } 129 registerSecondaryDexForProfiling(String dexPath, String[] dataDirs)130 private void registerSecondaryDexForProfiling(String dexPath, String[] dataDirs) { 131 if (!isSecondaryDexFile(dexPath, dataDirs)) { 132 // The dex path is not a secondary dex file. Nothing to do. 133 return; 134 } 135 136 // Secondary dex profiles are stored in the oat directory, next to dex file 137 // and have the same name with 'cur.prof' appended. 138 // NOTE: Keep this in sync with installd expectations. 139 File dexPathFile = new File(dexPath); 140 File secondaryProfileDir = new File(dexPathFile.getParent(), "oat"); 141 File secondaryProfile = new File(secondaryProfileDir, dexPathFile.getName() + ".cur.prof"); 142 143 // Create the profile if not already there. 144 // Returns true if the file was created, false if the file already exists. 145 // or throws exceptions in case of errors. 146 if (!secondaryProfileDir.exists()) { 147 if (!secondaryProfileDir.mkdir()) { 148 Slog.e(TAG, "Could not create the profile directory: " + secondaryProfile); 149 // Do not continue with registration if we could not create the oat dir. 150 return; 151 } 152 } 153 154 try { 155 boolean created = secondaryProfile.createNewFile(); 156 if (DEBUG && created) { 157 Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile); 158 } 159 } catch (IOException ex) { 160 Slog.e(TAG, "Failed to create profile for secondary dex " + dexPath 161 + ":" + ex.getMessage()); 162 // Do not continue with registration if we could not create the profile files. 163 return; 164 } 165 166 // If we got here, the dex paths is a secondary dex and we were able to create the profile. 167 // Register the path to the runtime. 168 VMRuntime.registerAppInfo(secondaryProfile.getPath(), new String[] { dexPath }); 169 } 170 171 // A dex file is a secondary dex file if it is in any of the registered app 172 // data directories. isSecondaryDexFile(String dexPath, String[] dataDirs)173 private boolean isSecondaryDexFile(String dexPath, String[] dataDirs) { 174 for (String dataDir : dataDirs) { 175 if (FileUtils.contains(dataDir, dexPath)) { 176 return true; 177 } 178 } 179 return false; 180 } 181 } 182