AssetAtlasServicepublic class AssetAtlasService extends IAssetAtlas.Stub This service is responsible for packing preloaded bitmaps into a single
atlas texture. The resulting texture can be shared across processes to
reduce overall memory usage. |
Fields Summary |
---|
public static final String | ASSET_ATLAS_SERVICEName of the AssetAtlasService . | private static final String | LOG_TAG | private static final boolean | DEBUG_ATLAS | private static final boolean | DEBUG_ATLAS_TEXTURE | private static final int | MIN_SIZE | private static final int | MAX_SIZE | private static final int | STEP | private static final float | PACKING_THRESHOLD | private static final int | ATLAS_MAP_ENTRY_FIELD_COUNT | private static final int | GRAPHIC_BUFFER_USAGE | private final AtomicBoolean | mAtlasReady | private final android.content.Context | mContext | private final String | mVersionName | private android.view.GraphicBuffer | mBuffer | private long[] | mAtlasMap |
Constructors Summary |
---|
public AssetAtlasService(android.content.Context context)Creates a new service. Upon creating, the service will gather the list of
assets to consider for packing into the atlas and spawn a new thread to
start the packing work.
mContext = context;
mVersionName = queryVersionName(context);
Collection<Bitmap> bitmaps = new HashSet<Bitmap>(300);
int totalPixelCount = 0;
// We only care about drawables that hold bitmaps
final Resources resources = context.getResources();
final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables();
final int count = drawables.size();
for (int i = 0; i < count; i++) {
try {
totalPixelCount += drawables.valueAt(i).addAtlasableBitmaps(bitmaps);
} catch (Throwable t) {
Log.e("AssetAtlas", "Failed to fetch preloaded drawable state", t);
throw t;
}
}
ArrayList<Bitmap> sortedBitmaps = new ArrayList<Bitmap>(bitmaps);
// Our algorithms perform better when the bitmaps are first sorted
// The comparator will sort the bitmap by width first, then by height
Collections.sort(sortedBitmaps, new Comparator<Bitmap>() {
@Override
public int compare(Bitmap b1, Bitmap b2) {
if (b1.getWidth() == b2.getWidth()) {
return b2.getHeight() - b1.getHeight();
}
return b2.getWidth() - b1.getWidth();
}
});
// Kick off the packing work on a worker thread
new Thread(new Renderer(sortedBitmaps, totalPixelCount)).start();
|
Methods Summary |
---|
private boolean | checkBuildIdentifier(java.io.BufferedReader reader, java.lang.String versionName)Compares the next line in the specified buffered reader to the current
build identifier. Returns whether the two values are equal.
String deviceBuildId = getBuildIdentifier(versionName);
String buildId = reader.readLine();
return deviceBuildId.equals(buildId);
| private com.android.server.AssetAtlasService$Configuration | chooseConfiguration(java.util.ArrayList bitmaps, int pixelCount, java.lang.String versionName)Returns the best known atlas configuration. This method will either
read the configuration from disk or start a brute-force search
and save the result out to disk.
Configuration config = null;
final File dataFile = getDataFile();
if (dataFile.exists()) {
config = readConfiguration(dataFile, versionName);
}
if (config == null) {
config = computeBestConfiguration(bitmaps, pixelCount);
if (config != null) writeConfiguration(config, dataFile, versionName);
}
return config;
| private static com.android.server.AssetAtlasService$Configuration | computeBestConfiguration(java.util.ArrayList bitmaps, int pixelCount)Finds the best atlas configuration to pack the list of supplied bitmaps.
This method takes advantage of multi-core systems by spawning a number
of threads equal to the number of available cores.
if (DEBUG_ATLAS) Log.d(LOG_TAG, "Computing best atlas configuration...");
long begin = System.nanoTime();
List<WorkerResult> results = Collections.synchronizedList(new ArrayList<WorkerResult>());
// Don't bother with an extra thread if there's only one processor
int cpuCount = Runtime.getRuntime().availableProcessors();
if (cpuCount == 1) {
new ComputeWorker(MIN_SIZE, MAX_SIZE, STEP, bitmaps, pixelCount, results, null).run();
} else {
int start = MIN_SIZE;
int end = MAX_SIZE - (cpuCount - 1) * STEP;
int step = STEP * cpuCount;
final CountDownLatch signal = new CountDownLatch(cpuCount);
for (int i = 0; i < cpuCount; i++, start += STEP, end += STEP) {
ComputeWorker worker = new ComputeWorker(start, end, step,
bitmaps, pixelCount, results, signal);
new Thread(worker, "Atlas Worker #" + (i + 1)).start();
}
try {
signal.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.w(LOG_TAG, "Could not complete configuration computation");
return null;
}
}
// Maximize the number of packed bitmaps, minimize the texture size
Collections.sort(results, new Comparator<WorkerResult>() {
@Override
public int compare(WorkerResult r1, WorkerResult r2) {
int delta = r2.count - r1.count;
if (delta != 0) return delta;
return r1.width * r1.height - r2.width * r2.height;
}
});
if (DEBUG_ATLAS) {
float delay = (System.nanoTime() - begin) / 1000.0f / 1000.0f / 1000.0f;
Log.d(LOG_TAG, String.format("Found best atlas configuration in %.2fs", delay));
}
WorkerResult result = results.get(0);
return new Configuration(result.type, result.width, result.height, result.count);
| private static void | deleteDataFile()
Log.w(LOG_TAG, "Current configuration inconsistent with assets list");
if (!getDataFile().delete()) {
Log.w(LOG_TAG, "Could not delete the current configuration");
}
| public android.view.GraphicBuffer | getBuffer()
return mAtlasReady.get() ? mBuffer : null;
| private java.lang.String | getBuildIdentifier(java.lang.String versionName)Returns an identifier for the current build that can be used to detect
likely changes to framework resources. The build identifier is made of
several distinct values:
build fingerprint/framework version name/file size of framework resources apk
Only the build fingerprint should be necessary on user builds but
the other values are useful to detect changes on eng builds during
development.
This identifier does not attempt to be exact: a new identifier does not
necessarily mean the preloaded drawables have changed. It is important
however that whenever the list of preloaded drawables changes, this
identifier changes as well.
return SystemProperties.get("ro.build.fingerprint", "") + '/" + versionName + '/" +
String.valueOf(getFrameworkResourcesFile().length());
| private static java.io.File | getDataFile()Returns the path to the file containing the best computed
atlas configuration.
File systemDirectory = new File(Environment.getDataDirectory(), "system");
return new File(systemDirectory, "framework_atlas.config");
| private java.io.File | getFrameworkResourcesFile()
return new File(mContext.getApplicationInfo().sourceDir);
| public long[] | getMap()
return mAtlasReady.get() ? mAtlasMap : null;
| public boolean | isCompatible(int ppid)
return ppid == android.os.Process.myPpid();
| private static native long | nAcquireAtlasCanvas(android.graphics.Canvas canvas, int width, int height)
| private static native void | nReleaseAtlasCanvas(android.graphics.Canvas canvas, long bitmap)
| private static native boolean | nUploadAtlas(android.view.GraphicBuffer buffer, long bitmap)
| private static java.lang.String | queryVersionName(android.content.Context context)Queries the version name stored in framework's AndroidManifest.
The version name can be used to identify possible changes to
framework resources.
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
return info.versionName;
} catch (PackageManager.NameNotFoundException e) {
Log.w(LOG_TAG, "Could not get package info", e);
}
return null;
| private com.android.server.AssetAtlasService$Configuration | readConfiguration(java.io.File file, java.lang.String versionName)Reads an atlas configuration from the specified file. This method
returns null if an error occurs or if the configuration is invalid.
BufferedReader reader = null;
Configuration config = null;
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
if (checkBuildIdentifier(reader, versionName)) {
Atlas.Type type = Atlas.Type.valueOf(reader.readLine());
int width = readInt(reader, MIN_SIZE, MAX_SIZE);
int height = readInt(reader, MIN_SIZE, MAX_SIZE);
int count = readInt(reader, 0, Integer.MAX_VALUE);
int flags = readInt(reader, Integer.MIN_VALUE, Integer.MAX_VALUE);
config = new Configuration(type, width, height, count, flags);
}
} catch (IllegalArgumentException e) {
Log.w(LOG_TAG, "Invalid parameter value in " + file, e);
} catch (FileNotFoundException e) {
Log.w(LOG_TAG, "Could not read " + file, e);
} catch (IOException e) {
Log.w(LOG_TAG, "Could not read " + file, e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// Ignore
}
}
}
return config;
| private static int | readInt(java.io.BufferedReader reader, int min, int max)
return Math.max(min, Math.min(max, Integer.parseInt(reader.readLine())));
| public void | systemRunning()Callback invoked by the server thread to indicate we can now run
3rd party code.
| private void | writeConfiguration(com.android.server.AssetAtlasService$Configuration config, java.io.File file, java.lang.String versionName)Writes the specified atlas configuration to the specified file.
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
writer.write(getBuildIdentifier(versionName));
writer.newLine();
writer.write(config.type.toString());
writer.newLine();
writer.write(String.valueOf(config.width));
writer.newLine();
writer.write(String.valueOf(config.height));
writer.newLine();
writer.write(String.valueOf(config.count));
writer.newLine();
writer.write(String.valueOf(config.flags));
writer.newLine();
} catch (FileNotFoundException e) {
Log.w(LOG_TAG, "Could not write " + file, e);
} catch (IOException e) {
Log.w(LOG_TAG, "Could not write " + file, e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
// Ignore
}
}
}
|
|