Improve performance of storage measurement.

When calculating directory sizes of data living on emulated external
storage, translate the path to use the internal backing data, which
avoids going through the emulation layer.  It carefully retreats to
the original path when it runs into trouble.

Testing with a hierarchy of 10 directories deep and 2 directories
and 10 files wide at each level, this change improves performance
from 5900ms before to 250ms after; over 20 times faster (!).

Bug: 8172425
Change-Id: Ia7365416f091e102bf7345a49f7d7209a22580a9
This commit is contained in:
Jeff Sharkey
2013-03-01 16:12:55 -08:00
parent 02a4cec107
commit 63d0a06799
3 changed files with 70 additions and 11 deletions

View File

@ -25,6 +25,7 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.io.File;
import java.io.IOException;
/**
* Provides access to environment variables.
@ -36,12 +37,16 @@ public class Environment {
private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE";
private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE";
private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
/** {@hide} */
public static String DIRECTORY_ANDROID = "Android";
private static final File ROOT_DIRECTORY
= getDirectory("ANDROID_ROOT", "/system");
private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media");
private static final String CANONCIAL_EMULATED_STORAGE_TARGET = getCanonicalPathOrNull(
ENV_EMULATED_STORAGE_TARGET);
private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
@ -178,7 +183,7 @@ public class Environment {
* Gets the Android root directory.
*/
public static File getRootDirectory() {
return ROOT_DIRECTORY;
return DIR_ANDROID_ROOT;
}
/**
@ -632,6 +637,19 @@ public class Environment {
return path == null ? new File(defaultPath) : new File(path);
}
private static String getCanonicalPathOrNull(String variableName) {
String path = System.getenv(variableName);
if (path == null) {
return null;
}
try {
return new File(path).getCanonicalPath();
} catch (IOException e) {
Log.w(TAG, "Unable to resolve canonical path for " + path);
return null;
}
}
private static void throwIfSystem() {
if (Process.myUid() == Process.SYSTEM_UID) {
Log.wtf(TAG, "Static storage paths aren't available from AID_SYSTEM", new Throwable());
@ -649,4 +667,40 @@ public class Environment {
}
return cur;
}
/**
* If the given path exists on emulated external storage, return the
* translated backing path hosted on internal storage. This bypasses any
* emulation later, improving performance. This is <em>only</em> suitable
* for read-only access.
* <p>
* Returns original path if given path doesn't meet these criteria. Callers
* must hold {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}
* permission.
*
* @hide
*/
public static File maybeTranslateEmulatedPathToInternal(File path) {
// Fast return if not emulated, or missing variables
if (!Environment.isExternalStorageEmulated()
|| CANONCIAL_EMULATED_STORAGE_TARGET == null) {
return path;
}
try {
final String rawPath = path.getCanonicalPath();
if (rawPath.startsWith(CANONCIAL_EMULATED_STORAGE_TARGET)) {
final File internalPath = new File(DIR_MEDIA_STORAGE,
rawPath.substring(CANONCIAL_EMULATED_STORAGE_TARGET.length()));
if (internalPath.exists()) {
return internalPath;
}
}
} catch (IOException e) {
Log.w(TAG, "Failed to resolve canonical path for " + path);
}
// Unable to translate to internal path; use original
return path;
}
}

View File

@ -7,6 +7,8 @@
<uses-permission android:name="android.permission.ASEC_DESTROY"/>
<uses-permission android:name="android.permission.ASEC_MOUNT_UNMOUNT"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Used to improve MeasureUtils performance on emulated storage -->
<uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
<uses-permission android:name="android.permission.ACCESS_ALL_EXTERNAL_STORAGE" />

View File

@ -16,16 +16,12 @@
package com.android.defcontainer;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.content.PackageHelper;
import android.app.IntentService;
import android.content.Intent;
import android.content.pm.MacAuthenticatedInputStream;
import android.content.pm.ContainerEncryptionParams;
import android.content.pm.IPackageManager;
import android.content.pm.LimitedLengthInputStream;
import android.content.pm.MacAuthenticatedInputStream;
import android.content.pm.PackageCleanItem;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
@ -43,10 +39,16 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StatFs;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.content.PackageHelper;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
@ -228,9 +230,10 @@ public class DefaultContainerService extends IntentService {
public long calculateDirectorySize(String path) throws RemoteException {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
final File directory = new File(path);
if (directory.exists() && directory.isDirectory()) {
return MeasurementUtils.measureDirectory(path);
final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path));
if (dir.exists() && dir.isDirectory()) {
final String targetPath = dir.getAbsolutePath();
return MeasurementUtils.measureDirectory(targetPath);
} else {
return 0L;
}