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.ArrayList; 32 import java.util.HashSet; 33 import java.util.List; 34 import java.util.Set; 35 36 /** 37 * A dex load reporter which will notify package manager of any dex file loaded 38 * with {@code BaseDexClassLoader}. 39 * The goals are: 40 * 1) discover secondary dex files so that they can be optimized during the 41 * idle maintenance job. 42 * 2) determine whether or not a dex file is used by an app which does not 43 * own it (in order to select the optimal compilation method). 44 * @hide 45 */ 46 /*package*/ class DexLoadReporter implements BaseDexClassLoader.Reporter { 47 private static final String TAG = "DexLoadReporter"; 48 49 private static final DexLoadReporter INSTANCE = new DexLoadReporter(); 50 51 private static final boolean DEBUG = false; 52 53 // We must guard the access to the list of data directories because 54 // we might have concurrent accesses. Apps might load dex files while 55 // new data dirs are registered (due to creation of LoadedApks via 56 // create createApplicationContext). 57 @GuardedBy("mDataDirs") 58 private final Set<String> mDataDirs; 59 DexLoadReporter()60 private DexLoadReporter() { 61 mDataDirs = new HashSet<>(); 62 } 63 getInstance()64 /*package*/ static DexLoadReporter getInstance() { 65 return INSTANCE; 66 } 67 68 /** 69 * Register an application data directory with the reporter. 70 * The data directories are used to determine if a dex file is secondary dex or not. 71 * Note that this method may be called multiple times for the same app, registering 72 * different data directories. This may happen when apps share the same user id 73 * ({@code android:sharedUserId}). For example, if app1 and app2 share the same user 74 * id, and app1 loads app2 apk, then both data directories will be registered. 75 */ registerAppDataDir(String packageName, String dataDir)76 /*package*/ void registerAppDataDir(String packageName, String dataDir) { 77 if (DEBUG) { 78 Slog.i(TAG, "Package " + packageName + " registering data dir: " + dataDir); 79 } 80 // TODO(calin): A few code paths imply that the data dir 81 // might be null. Investigate when that can happen. 82 if (dataDir != null) { 83 synchronized (mDataDirs) { 84 mDataDirs.add(dataDir); 85 } 86 } 87 } 88 89 @Override report(List<BaseDexClassLoader> classLoadersChain, List<String> classPaths)90 public void report(List<BaseDexClassLoader> classLoadersChain, List<String> classPaths) { 91 if (classLoadersChain.size() != classPaths.size()) { 92 Slog.wtf(TAG, "Bad call to DexLoadReporter: argument size mismatch"); 93 return; 94 } 95 if (classPaths.isEmpty()) { 96 Slog.wtf(TAG, "Bad call to DexLoadReporter: empty dex paths"); 97 return; 98 } 99 100 // The first element of classPaths is the list of dex files that should be registered. 101 // The classpath is represented as a list of dex files separated by File.pathSeparator. 102 String[] dexPathsForRegistration = classPaths.get(0).split(File.pathSeparator); 103 if (dexPathsForRegistration.length == 0) { 104 // No dex files to register. 105 return; 106 } 107 108 // Notify the package manager about the dex loads unconditionally. 109 // The load might be for either a primary or secondary dex file. 110 notifyPackageManager(classLoadersChain, classPaths); 111 // Check for secondary dex files and register them for profiling if possible. 112 // Note that we only register the dex paths belonging to the first class loader. 113 registerSecondaryDexForProfiling(dexPathsForRegistration); 114 } 115 notifyPackageManager(List<BaseDexClassLoader> classLoadersChain, List<String> classPaths)116 private void notifyPackageManager(List<BaseDexClassLoader> classLoadersChain, 117 List<String> classPaths) { 118 // Get the class loader names for the binder call. 119 List<String> classLoadersNames = new ArrayList<>(classPaths.size()); 120 for (BaseDexClassLoader bdc : classLoadersChain) { 121 classLoadersNames.add(bdc.getClass().getName()); 122 } 123 String packageName = ActivityThread.currentPackageName(); 124 try { 125 ActivityThread.getPackageManager().notifyDexLoad( 126 packageName, classLoadersNames, classPaths, 127 VMRuntime.getRuntime().vmInstructionSet()); 128 } catch (RemoteException re) { 129 Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); 130 } 131 } 132 registerSecondaryDexForProfiling(String[] dexPaths)133 private void registerSecondaryDexForProfiling(String[] dexPaths) { 134 if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) { 135 return; 136 } 137 // Make a copy of the current data directories so that we don't keep the lock 138 // while registering for profiling. The registration will perform I/O to 139 // check for or create the profile. 140 String[] dataDirs; 141 synchronized (mDataDirs) { 142 dataDirs = mDataDirs.toArray(new String[0]); 143 } 144 for (String dexPath : dexPaths) { 145 registerSecondaryDexForProfiling(dexPath, dataDirs); 146 } 147 } 148 registerSecondaryDexForProfiling(String dexPath, String[] dataDirs)149 private void registerSecondaryDexForProfiling(String dexPath, String[] dataDirs) { 150 if (!isSecondaryDexFile(dexPath, dataDirs)) { 151 // The dex path is not a secondary dex file. Nothing to do. 152 return; 153 } 154 155 // Secondary dex profiles are stored in the oat directory, next to dex file 156 // and have the same name with 'cur.prof' appended. 157 // NOTE: Keep this in sync with installd expectations. 158 File dexPathFile = new File(dexPath); 159 File secondaryProfileDir = new File(dexPathFile.getParent(), "oat"); 160 File secondaryProfile = new File(secondaryProfileDir, dexPathFile.getName() + ".cur.prof"); 161 162 // Create the profile if not already there. 163 // Returns true if the file was created, false if the file already exists. 164 // or throws exceptions in case of errors. 165 if (!secondaryProfileDir.exists()) { 166 if (!secondaryProfileDir.mkdir()) { 167 Slog.e(TAG, "Could not create the profile directory: " + secondaryProfile); 168 // Do not continue with registration if we could not create the oat dir. 169 return; 170 } 171 } 172 173 try { 174 boolean created = secondaryProfile.createNewFile(); 175 if (DEBUG && created) { 176 Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile); 177 } 178 } catch (IOException ex) { 179 Slog.e(TAG, "Failed to create profile for secondary dex " + dexPath 180 + ":" + ex.getMessage()); 181 // Do not continue with registration if we could not create the profile files. 182 return; 183 } 184 185 // If we got here, the dex paths is a secondary dex and we were able to create the profile. 186 // Register the path to the runtime. 187 VMRuntime.registerAppInfo(secondaryProfile.getPath(), new String[] { dexPath }); 188 } 189 190 // A dex file is a secondary dex file if it is in any of the registered app 191 // data directories. isSecondaryDexFile(String dexPath, String[] dataDirs)192 private boolean isSecondaryDexFile(String dexPath, String[] dataDirs) { 193 for (String dataDir : dataDirs) { 194 if (FileUtils.contains(dataDir, dexPath)) { 195 return true; 196 } 197 } 198 return false; 199 } 200 } 201