AssetManager: use libandroidfw instead of ARSClib for resources.arsc parsing

This reduces startup time and RAM usage. Not yet feature complete with
the ARSClib based implementation, but Gravity Defied runs already
This commit is contained in:
Julian Winkler
2024-01-26 12:42:06 +01:00
parent 6136243fec
commit 3284518418
5 changed files with 197 additions and 90 deletions

View File

@@ -25,6 +25,9 @@ libdl_bio_dep = [
libskia_dep = [ libskia_dep = [
cc.find_library('SkiaSharp') cc.find_library('SkiaSharp')
] ]
libandroidfw_dep = [
cc.find_library('androidfw', dirs : [ '/usr' / get_option('libdir') / 'art', '/usr/local' / get_option('libdir') / 'art', get_option('prefix') / get_option('libdir') / 'art' ]),
]
if fs.is_file('/usr' / get_option('libdir') / 'java/core-all_classes.jar') if fs.is_file('/usr' / get_option('libdir') / 'java/core-all_classes.jar')
bootclasspath = '/usr' / get_option('libdir') / 'java/core-all_classes.jar' bootclasspath = '/usr' / get_option('libdir') / 'java/core-all_classes.jar'
elif fs.is_file('/usr/local' / get_option('libdir') / 'java/core-all_classes.jar') elif fs.is_file('/usr/local' / get_option('libdir') / 'java/core-all_classes.jar')
@@ -117,7 +120,8 @@ libtranslationlayer_so = shared_library('translation_layer_main', [
install_dir : get_option('libdir') / 'java/dex/android_translation_layer/natives', install_dir : get_option('libdir') / 'java/dex/android_translation_layer/natives',
dependencies: [ dependencies: [
dependency('gtk4', version: '>=4.8'), dependency('gl'), dependency('egl'), dependency('wayland-client'), dependency('jni'), dependency('gtk4', version: '>=4.8'), dependency('gl'), dependency('egl'), dependency('wayland-client'), dependency('jni'),
dependency('libportal'), dependency('sqlite3'), libskia_dep, dependency('libavcodec', version: '>=59'), dependency('libdrm') dependency('libportal'), dependency('sqlite3'), libskia_dep, dependency('libavcodec', version: '>=59'), dependency('libdrm'),
libandroidfw_dep
], ],
link_with: [ libandroid_so ], link_with: [ libandroid_so ],
link_args: [ link_args: [

View File

@@ -1,10 +1,13 @@
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <stddef.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <androidfw/androidfw_c_api.h>
#include "defines.h" #include "defines.h"
#include "util.h" #include "util.h"
#include "generated_headers/android_content_res_AssetManager.h" #include "generated_headers/android_content_res_AssetManager.h"
@@ -107,3 +110,117 @@ JNIEXPORT void JNICALL Java_android_content_res_AssetManager_destroyAsset(JNIEnv
printf("closing asset with fd: %d\n", fd); printf("closing asset with fd: %d\n", fd);
close(fd); close(fd);
} }
JNIEXPORT void JNICALL Java_android_content_res_AssetManager_init(JNIEnv *env, jobject this)
{
struct AssetManager *asset_manager = AssetManager_new();
const struct ResTable_config config = {
.density = /*ACONFIGURATION_DENSITY_MEDIUM*/ 160,
.sdkVersion = 24,
};
AssetManager_setConfiguration(asset_manager, &config, NULL);
_SET_LONG_FIELD(this, "mObject", _INTPTR(asset_manager));
}
JNIEXPORT jint JNICALL Java_android_content_res_AssetManager_addAssetPathNative(JNIEnv *env, jobject this, jstring path)
{
int32_t cookie;
struct AssetManager *asset_manager = _PTR(_GET_LONG_FIELD(this, "mObject"));
AssetManager_addAssetPath(asset_manager, _CSTRING(path), &cookie, false, false);
return cookie;
}
JNIEXPORT jint JNICALL Java_android_content_res_AssetManager_loadResourceValue(JNIEnv *env, jobject this, jint ident, jshort density, jobject outValue, jboolean resolve)
{
struct AssetManager *asset_manager = _PTR(_GET_LONG_FIELD(this, "mObject"));
const struct ResTable *res_table = AssetManager_getResources(asset_manager, true);
uint32_t resId = ident;
struct Res_value value;
uint32_t outSpecFlags;
struct ResTable_config outConfig;
ssize_t block = ResTable_getResource(res_table, resId, &value, false, density, &outSpecFlags, &outConfig);
if (resolve) {
block = ResTable_resolveReference(res_table, &value, block, &resId, &outSpecFlags, &outConfig);
}
if (block >= 0) {
_SET_INT_FIELD(outValue, "type", value.dataType);
_SET_INT_FIELD(outValue, "data", value.data);
_SET_INT_FIELD(outValue, "resourceId", resId);
if (value.dataType == TYPE_STRING) {
const struct ResStringPool *string_pool = ResTable_getTableStringBlock(res_table, block);
size_t len;
const char16_t *string = ResStringPool_stringAt(string_pool, value.data, &len);
_SET_OBJ_FIELD(outValue, "string", "Ljava/lang/CharSequence;", (*env)->NewString(env, string, len));
} else {
_SET_OBJ_FIELD(outValue, "string", "Ljava/lang/CharSequence;", NULL);
}
}
return block;
}
JNIEXPORT jobjectArray JNICALL Java_android_content_res_AssetManager_getArrayStringResource(JNIEnv *env, jobject this, jint ident)
{
int i;
struct AssetManager *asset_manager = _PTR(_GET_LONG_FIELD(this, "mObject"));
const struct ResTable *res_table = AssetManager_getResources(asset_manager, true);
const struct bag_entry *bag;
int bag_count = ResTable_lockBag(res_table, ident, &bag);
jobjectArray array = (*env)->NewObjectArray(env, bag_count, (*env)->FindClass(env, "java/lang/String"), NULL);
for (i = 0; i < bag_count; i++) {
if (bag[i].map.value.dataType == TYPE_STRING) {
const struct ResStringPool *string_pool = ResTable_getTableStringBlock(res_table, bag[i].stringBlock);
if (string_pool == NULL)
continue;
size_t len;
const char16_t *string = ResStringPool_stringAt(string_pool, bag[i].map.value.data, &len);
(*env)->SetObjectArrayElement(env, array, i, (*env)->NewString(env, string, len));
}
}
ResTable_unlockBag(res_table, bag);
return array;
}
JNIEXPORT jint JNICALL Java_android_content_res_AssetManager_getResourceIdentifier(JNIEnv *env, jobject this, jstring name, jstring defType, jstring defPackage)
{
const char16_t *name16 = NULL;
const char16_t *defType16 = NULL;
const char16_t *defPackage16 = NULL;
int name_len = 0;
int defType_len = 0;
int defPackage_len = 0;
int ret;
struct AssetManager *asset_manager = _PTR(_GET_LONG_FIELD(this, "mObject"));
const struct ResTable *res_table = AssetManager_getResources(asset_manager, true);
if (name) {
name16 = (*env)->GetStringChars(env, name, NULL);
name_len = (*env)->GetStringLength(env, name);
}
if (defType) {
defType16 = (*env)->GetStringChars(env, defType, NULL);
defType_len = (*env)->GetStringLength(env, defType);
}
if (defPackage) {
defPackage16 = (*env)->GetStringChars(env, defPackage, NULL);
defPackage_len = (*env)->GetStringLength(env, defPackage);
}
ret = ResTable_identifierForName(res_table, name16, name_len, defType16, defType_len, defPackage16, defPackage_len, NULL);
if (name)
(*env)->ReleaseStringChars(env, name, name16);
if (defType)
(*env)->ReleaseStringChars(env, defType, defType16);
if (defPackage)
(*env)->ReleaseStringChars(env, defPackage, defPackage16);
return ret;
}
JNIEXPORT jobject JNICALL Java_android_content_res_AssetManager_getPooledString(JNIEnv *env, jobject this, jint block, jint index)
{
struct AssetManager *asset_manager = _PTR(_GET_LONG_FIELD(this, "mObject"));
const struct ResTable *res_table = AssetManager_getResources(asset_manager, true);
const struct ResStringPool *string_pool = ResTable_getTableStringBlock(res_table, block);
size_t len;
const char16_t *string = ResStringPool_stringAt(string_pool, index, &len);
return (*env)->NewString(env, string, len);
}

View File

@@ -33,6 +33,14 @@ extern "C" {
#define android_content_res_AssetManager_STYLE_CHANGING_CONFIGURATIONS 4L #define android_content_res_AssetManager_STYLE_CHANGING_CONFIGURATIONS 4L
#undef android_content_res_AssetManager_STYLE_DENSITY #undef android_content_res_AssetManager_STYLE_DENSITY
#define android_content_res_AssetManager_STYLE_DENSITY 5L #define android_content_res_AssetManager_STYLE_DENSITY 5L
/*
* Class: android_content_res_AssetManager
* Method: getPooledString
* Signature: (II)Ljava/lang/CharSequence;
*/
JNIEXPORT jobject JNICALL Java_android_content_res_AssetManager_getPooledString
(JNIEnv *, jobject, jint, jint);
/* /*
* Class: android_content_res_AssetManager * Class: android_content_res_AssetManager
* Method: list * Method: list
@@ -73,6 +81,14 @@ JNIEXPORT void JNICALL Java_android_content_res_AssetManager_setLocale
JNIEXPORT jobjectArray JNICALL Java_android_content_res_AssetManager_getLocales JNIEXPORT jobjectArray JNICALL Java_android_content_res_AssetManager_getLocales
(JNIEnv *, jobject); (JNIEnv *, jobject);
/*
* Class: android_content_res_AssetManager
* Method: getResourceIdentifier
* Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_android_content_res_AssetManager_getResourceIdentifier
(JNIEnv *, jobject, jstring, jstring, jstring);
/* /*
* Class: android_content_res_AssetManager * Class: android_content_res_AssetManager
* Method: openAsset * Method: openAsset
@@ -297,6 +313,14 @@ JNIEXPORT jobjectArray JNICALL Java_android_content_res_AssetManager_getArrayStr
JNIEXPORT jintArray JNICALL Java_android_content_res_AssetManager_getArrayStringInfo JNIEXPORT jintArray JNICALL Java_android_content_res_AssetManager_getArrayStringInfo
(JNIEnv *, jobject, jint); (JNIEnv *, jobject, jint);
/*
* Class: android_content_res_AssetManager
* Method: init
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_android_content_res_AssetManager_init
(JNIEnv *, jobject);
/* /*
* Class: android_content_res_AssetManager * Class: android_content_res_AssetManager
* Method: destroy * Method: destroy

View File

@@ -102,7 +102,7 @@ public final class AssetManager {
private final long[] mOffsets = new long[2]; private final long[] mOffsets = new long[2];
// For communication with native code. // For communication with native code.
private int mObject; private long mObject;
private int mNObject; // used by the NDK private int mNObject; // used by the NDK
private StringBlock mStringBlocks[] = null; private StringBlock mStringBlocks[] = null;
@@ -121,18 +121,18 @@ public final class AssetManager {
* {@hide} * {@hide}
*/ */
public AssetManager() { public AssetManager() {
try { tableBlocks = new ArrayList<>();
tableBlocks = new ArrayList<>(); // try {
Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources("resources.arsc"); // Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources("resources.arsc");
while (resources.hasMoreElements()) { // while (resources.hasMoreElements()) {
URL resource = resources.nextElement(); // URL resource = resources.nextElement();
if (!resource.getFile().contains("com.google.android.gms")) { // ignore MicroG .apk // if (!resource.getFile().contains("com.google.android.gms")) { // ignore MicroG .apk
tableBlocks.add(TableBlock.load(resource.openStream())); // tableBlocks.add(TableBlock.load(resource.openStream()));
} // }
} // }
} catch (IOException e) { // } catch (IOException e) {
Log.e(TAG, "failed to load resources.arsc" + e); // Log.e(TAG, "failed to load resources.arsc" + e);
} // }
// FIXME: evaluate if this can be axed // FIXME: evaluate if this can be axed
synchronized (this) { synchronized (this) {
@@ -144,6 +144,19 @@ public final class AssetManager {
if (localLOGV) if (localLOGV)
Log.v(TAG, "New asset manager: " + this); Log.v(TAG, "New asset manager: " + this);
// ensureSystemAssets() // ensureSystemAssets()
try {
Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources("resources.arsc");
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
String path = resource.getPath();
if (!path.contains("com.google.android.gms")) { // ignore MicroG .apk
path = path.substring(path.indexOf("file:") + 5, path.indexOf("!/resources.arsc"));
addAssetPath(path);
}
}
} catch (IOException e) {
Log.e(TAG, "failed to load resources.arsc" + e);
}
} }
} }
@@ -215,18 +228,9 @@ public final class AssetManager {
/*package*/ final CharSequence getResourceText(int id) { /*package*/ final CharSequence getResourceText(int id) {
if (id == 0) if (id == 0)
return ""; return "";
ResValue resValue = tableBlockSearch(id).pickOne().getResValue(); TypedValue value = new TypedValue();
if (resValue.getValueType() == ValueType.REFERENCE) { loadResourceValue(id, (short) 0, value, true);
if(id == resValue.getData()) { return value.coerceToString();
System.out.println("getResourceText: self-reference... returing \"\"");
return "";
}
return getResourceText(resValue.getData());
}
if (resValue.getValueType() == ValueType.INT_COLOR_RGB8) {
return String.format("#%08x", resValue.getData());
}
return resValue.getDataAsPoolString().get();
} }
/** /**
@@ -256,43 +260,15 @@ public final class AssetManager {
* @param id Resource id of the string array * @param id Resource id of the string array
*/ */
/*package*/ final String[] getResourceStringArray(final int id) { /*package*/ final String[] getResourceStringArray(final int id) {
ArrayList<String> values = new ArrayList<String>(); return getArrayStringResource(id);
for (ResValueMap map : tableBlockSearch(id).pickOne().getResValueMapArray().getChildes()) {
if (map.getType() == TypedValue.TYPE_REFERENCE) {
values.add(String.valueOf(getResourceText(map.getData())));
} else if (map.getType() == TypedValue.TYPE_STRING) {
values.add(map.getValueAsString());
} else {
values.add("value of unknown type " + map.getType());
}
}
return values.toArray(new String[0]);
} }
/*package*/ final boolean getResourceValue(int ident, /*package*/ final boolean getResourceValue(int ident,
int density, int density,
TypedValue outValue, TypedValue outValue,
boolean resolveRefs) { boolean resolveRefs) {
/*int block = loadResourceValue(ident, (short) density, outValue, resolveRefs); int block = loadResourceValue(ident, (short) density, outValue, resolveRefs);
if (block >= 0) { return block >= 0;
if (outValue.type != TypedValue.TYPE_STRING) {
return true;
}
outValue.string = mStringBlocks[block].get(outValue.data);
return true;
}*/
EntryGroup entryGroup = tableBlockSearch(ident);
if (entryGroup == null)
return false; // not found
ResValue resValue = entryGroup.pickOne().getResValue();
if (resValue == null)
return false; // not found
outValue.resourceId = ident;
outValue.type = resValue.getType();
outValue.data = resValue.getData();
if (outValue.type == TypedValue.TYPE_STRING)
outValue.string = getResourceText(ident);
return true;
} }
/** /**
@@ -397,13 +373,7 @@ public final class AssetManager {
} }
} }
/*package*/ final CharSequence getPooledString(int block, int id) { /*package*/ native final CharSequence getPooledString(int block, int id);
// System.out.println("Get pooled: block=" + block
// + ", id=#" + Integer.toHexString(id)
// + ", blocks=" + mStringBlocks);
TableString string = tableBlocks.get(block).getStringPool().get(id);
return string != null ? string.get() : null;
}
/** /**
* Open an asset using ACCESS_STREAMING mode. This provides access to * Open an asset using ACCESS_STREAMING mode. This provides access to
@@ -844,24 +814,7 @@ public final class AssetManager {
/** /**
* Retrieve the resource identifier for the given resource name. * Retrieve the resource identifier for the given resource name.
*/ */
/*package*/ /*native*/ final int getResourceIdentifier(String name, String type, String defPackage) { /*package*/ native final int getResourceIdentifier(String name, String type, String defPackage);
System.out.println("getResourceIdentifier(" + name + "," + type + "," + defPackage + ") called");
for (TableBlock tableBlock : tableBlocks) {
for (PackageBlock packageBlock : tableBlock.listPackages()) {
if (packageBlock.getName().equals(defPackage)) {
Entry entry = packageBlock.getEntry("", type, name);
if(entry != null) {
return entry.getResourceId();
} else {
return -1; // TODO: investigate
}
}
}
}
// package not found
return -1;
}
/*package*/ /*native*/ final String getResourceName(int resid) { /*package*/ /*native*/ final String getResourceName(int resid) {
return tableBlockSearch(resid).pickOne().getName(); return tableBlockSearch(resid).pickOne().getName();
@@ -944,12 +897,10 @@ public final class AssetManager {
outValues[d+AssetManager.STYLE_DATA] = 0; outValues[d+AssetManager.STYLE_DATA] = 0;
outValues[d+AssetManager.STYLE_ASSET_COOKIE] = 0; outValues[d+AssetManager.STYLE_ASSET_COOKIE] = 0;
int resId = inAttrs[i]; int resId = inAttrs[i];
EntryGroup entryGroup = tableBlockSearch(resId); EntryGroup entryGroup = null;
if (entryGroup == null) Entry entry = null;
continue;
Entry entry = entryGroup.pickOne();
ValueItem valueItem = null; ValueItem valueItem = null;
while ("attr".equals(entry.getTypeName())) { while (entry == null || "attr".equals(entry.getTypeName())) {
valueItem = null; valueItem = null;
if (xmlCache.containsKey(resId)) { if (xmlCache.containsKey(resId)) {
valueItem = parser.getResXmlAttributeAt(xmlCache.get(resId)); valueItem = parser.getResXmlAttributeAt(xmlCache.get(resId));
@@ -975,6 +926,17 @@ public final class AssetManager {
if (valueItem == null) if (valueItem == null)
continue; continue;
if (valueItem.getValueType() == ValueType.REFERENCE) { if (valueItem.getValueType() == ValueType.REFERENCE) {
resId = valueItem.getData();
TypedValue value = new TypedValue();
int block = loadResourceValue(resId, (short)0, value, true);
if (block >= 0) {
outValues[d + AssetManager.STYLE_RESOURCE_ID] = value.resourceId;
outValues[d + AssetManager.STYLE_TYPE] = value.type;
outValues[d + AssetManager.STYLE_DATA] = value.data;
outValues[d + AssetManager.STYLE_ASSET_COOKIE] = block;
outIndices[++outIndices[0]] = i;
continue;
}
while (valueItem.getValueType() == ValueType.REFERENCE) { while (valueItem.getValueType() == ValueType.REFERENCE) {
resId = valueItem.getData(); resId = valueItem.getData();
if (resId == 0) if (resId == 0)
@@ -1045,7 +1007,7 @@ public final class AssetManager {
return values; return values;
} }
private /* native */ final void init() {} private native final void init();
private native final void destroy(); private native final void destroy();
private final void incRefsLocked(int id) { private final void incRefsLocked(int id) {

View File

@@ -314,8 +314,6 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h
_SET_STATIC_INT_FIELD(display_class, "window_width", d->window_width); _SET_STATIC_INT_FIELD(display_class, "window_width", d->window_width);
_SET_STATIC_INT_FIELD(display_class, "window_height", d->window_height); _SET_STATIC_INT_FIELD(display_class, "window_height", d->window_height);
set_up_handle_cache(env);
/* -- register our JNI library under the appropriate classloader -- */ /* -- register our JNI library under the appropriate classloader -- */
/* 'android/view/View' is part of the "hax.dex" package, any other function from that package would serve just as well */ /* 'android/view/View' is part of the "hax.dex" package, any other function from that package would serve just as well */
@@ -331,6 +329,8 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h
jmethodID loadLibrary_with_classloader = _METHOD(java_runtime_class, "loadLibrary", "(Ljava/lang/String;Ljava/lang/ClassLoader;)V"); jmethodID loadLibrary_with_classloader = _METHOD(java_runtime_class, "loadLibrary", "(Ljava/lang/String;Ljava/lang/ClassLoader;)V");
(*env)->CallVoidMethod(env, java_runtime, loadLibrary_with_classloader, _JSTRING("translation_layer_main"), class_loader); (*env)->CallVoidMethod(env, java_runtime, loadLibrary_with_classloader, _JSTRING("translation_layer_main"), class_loader);
set_up_handle_cache(env);
/* -- misc -- */ /* -- misc -- */
// some apps need the apk path since they directly read their apk // some apps need the apk path since they directly read their apk