blob: 229bee55e9115ca7326b2dc770626b71942e660b [file] [log] [blame]
Calin Juravled9732c82017-03-13 19:02:32 -07001/*
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
17package android.app;
18
Calin Juravlea9f46f72017-03-13 23:30:30 -070019import android.os.FileUtils;
Calin Juravled9732c82017-03-13 19:02:32 -070020import android.os.RemoteException;
Calin Juravlea9f46f72017-03-13 23:30:30 -070021import android.os.SystemProperties;
Calin Juravled9732c82017-03-13 19:02:32 -070022import android.util.Slog;
23
Calin Juravlea9f46f72017-03-13 23:30:30 -070024import com.android.internal.annotations.GuardedBy;
25
Calin Juravled9732c82017-03-13 19:02:32 -070026import dalvik.system.BaseDexClassLoader;
27import dalvik.system.VMRuntime;
28
Calin Juravlea9f46f72017-03-13 23:30:30 -070029import java.io.File;
30import java.io.IOException;
Calin Juravlea18e9922017-07-22 12:33:41 -070031import java.util.ArrayList;
Calin Juravlea9f46f72017-03-13 23:30:30 -070032import java.util.HashSet;
Calin Juravled9732c82017-03-13 19:02:32 -070033import java.util.List;
Calin Juravlea9f46f72017-03-13 23:30:30 -070034import java.util.Set;
Calin Juravled9732c82017-03-13 19:02:32 -070035
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
Calin Juravlea9f46f72017-03-13 23:30:30 -070049 private static final DexLoadReporter INSTANCE = new DexLoadReporter();
Calin Juravled9732c82017-03-13 19:02:32 -070050
Calin Juravlea9f46f72017-03-13 23:30:30 -070051 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
60 private DexLoadReporter() {
61 mDataDirs = new HashSet<>();
62 }
63
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 */
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 }
Calin Juravled9732c82017-03-13 19:02:32 -070088
89 @Override
Alan Stokesb6c3a602018-11-02 12:10:42 +000090 public void report(List<ClassLoader> classLoadersChain, List<String> classPaths) {
Calin Juravlecd8fbd252017-07-22 12:33:41 -070091 if (classLoadersChain.size() != classPaths.size()) {
92 Slog.wtf(TAG, "Bad call to DexLoadReporter: argument size mismatch");
Calin Juravled9732c82017-03-13 19:02:32 -070093 return;
94 }
Calin Juravlecd8fbd252017-07-22 12:33:41 -070095 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
Calin Juravlea9f46f72017-03-13 23:30:30 -0700108 // Notify the package manager about the dex loads unconditionally.
109 // The load might be for either a primary or secondary dex file.
Calin Juravlecd8fbd252017-07-22 12:33:41 -0700110 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);
Calin Juravlea9f46f72017-03-13 23:30:30 -0700114 }
115
Alan Stokesb6c3a602018-11-02 12:10:42 +0000116 private void notifyPackageManager(List<ClassLoader> classLoadersChain,
Calin Juravlecd8fbd252017-07-22 12:33:41 -0700117 List<String> classPaths) {
Calin Juravlea18e9922017-07-22 12:33:41 -0700118 // Get the class loader names for the binder call.
119 List<String> classLoadersNames = new ArrayList<>(classPaths.size());
Alan Stokesb6c3a602018-11-02 12:10:42 +0000120 for (ClassLoader classLoader : classLoadersChain) {
121 classLoadersNames.add(classLoader.getClass().getName());
Calin Juravlea18e9922017-07-22 12:33:41 -0700122 }
Calin Juravled9732c82017-03-13 19:02:32 -0700123 String packageName = ActivityThread.currentPackageName();
124 try {
125 ActivityThread.getPackageManager().notifyDexLoad(
Calin Juravlea18e9922017-07-22 12:33:41 -0700126 packageName, classLoadersNames, classPaths,
Calin Juravlecd8fbd252017-07-22 12:33:41 -0700127 VMRuntime.getRuntime().vmInstructionSet());
Calin Juravled9732c82017-03-13 19:02:32 -0700128 } catch (RemoteException re) {
129 Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
130 }
131 }
Calin Juravlea9f46f72017-03-13 23:30:30 -0700132
Calin Juravlecd8fbd252017-07-22 12:33:41 -0700133 private void registerSecondaryDexForProfiling(String[] dexPaths) {
Calin Juravlea9f46f72017-03-13 23:30:30 -0700134 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
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 }
Calin Juravle6214f1b2017-07-27 16:05:25 -0700154
Calin Juravled35451d2017-09-04 17:32:23 -0700155 // Secondary dex profiles are stored in the oat directory, next to dex file
156 // and have the same name with 'cur.prof' appended.
Calin Juravle6214f1b2017-07-27 16:05:25 -0700157 // NOTE: Keep this in sync with installd expectations.
Calin Juravled35451d2017-09-04 17:32:23 -0700158 File dexPathFile = new File(dexPath);
159 File secondaryProfileDir = new File(dexPathFile.getParent(), "oat");
160 File secondaryProfile = new File(secondaryProfileDir, dexPathFile.getName() + ".cur.prof");
Calin Juravle6214f1b2017-07-27 16:05:25 -0700161
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 {
Calin Juravlea9f46f72017-03-13 23:30:30 -0700174 boolean created = secondaryProfile.createNewFile();
175 if (DEBUG && created) {
176 Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile);
177 }
178 } catch (IOException ex) {
Calin Juravle6214f1b2017-07-27 16:05:25 -0700179 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.
Calin Juravlea9f46f72017-03-13 23:30:30 -0700182 return;
183 }
184
Calin Juravle6214f1b2017-07-27 16:05:25 -0700185 // 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.
Calin Juravlea9f46f72017-03-13 23:30:30 -0700187 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.
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 }
Calin Juravled9732c82017-03-13 19:02:32 -0700200}