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.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 
     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     }
     88 
     89     @Override
     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 
    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 
    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 
    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.
    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