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