AndroidBufferAllocator and NonSdkApiUsedViolation

When using JME under Android, for API level 30, I get errors from the Play Store Console when they test the app bundle.

More specifically, I get multiple NonSdkApiUsedViolations for the static initializer in AndroidBufferAllocator. The reason is that there are several reflection calls to make fields in java.nio.ByteBuffer… classes accessible. This is not allowed by Google, as it accesses non-SDK parts of the API.

Now, as a workaround I can replace AndroidBufferAllocator in the BufferAllocatorFactory via system properties.

The question is with what to replace it? I can write my own implementation which does nothing in the destroyDirectBuffer() method. Or I can use the “original” ReflectionAllocator which does a lot of reflection stuff. The latter seems a bit risky, as without lengthy debugging I cannot really tell what goes on there and whether (and under what circumstances) it again violates the SDK restrictions.

However, simply doing nothing in destroyDirectBuffer() doesn’t seem right as well. I guess this could easily cause memory leaks.

Any insights on what goes on in destroyDirectBuffer() and what the best way forward is here?

2 Likes

Can you show the full error stack trace?

ReflectionAllocator (used internally by AndroidBufferAllocator ) is a mess, it is also broken on JDK 16.

You can use PrimitiveAllocator which does nothing on destroyDirectBuffer and let it be cleared by GC.

I looked at LibGdx, and they do it with c++ and JNI.

JNIEXPORT jobject JNICALL Java_com_badlogic_gdx_utils_BufferUtils_newDisposableByteBuffer(JNIEnv* env, jclass clazz, jint numBytes) {
//@line:334
   char* ptr = (char*)malloc(numBytes);
   return env->NewDirectByteBuffer(ptr, numBytes);
}
JNIEXPORT void JNICALL Java_com_badlogic_gdx_utils_BufferUtils_freeMemory(JNIEnv* env, jobject thiz, jobject bufferRef)
{
    void* buffer = env->GetDirectBufferAddress(bufferRef);
    free(buffer);
}

Read more on

and

https://www.py4u.net/discuss/641188

I have not worked with JNI so can not help more on the implementation side.

If you ended up implementing this yourself you are welcome to submit a PR into the engine. :slightly_smiling_face:

2 Likes

Also, will appreciate if you open an issue for this error on JME GitHub page.

Regards

1 Like

Thanks for the reply and the pointer to PrimitiveAllocator. I’ll go with Primitive and see if that causes any problems. Let’s see how well GC works under Android in this scenario.

Will open an issue on GitHub as well.

Here is the stacktrace produced by the Google Play Store console’s “pre-launch report”:

StrictMode policy violation: android.os.strictmode.NonSdkApiUsedViolation: Ljava/nio/ByteBufferAsCharBuffer;->bb:Ljava/nio/ByteBuffer;
	at android.os.StrictMode.lambda$static$1(StrictMode.java:407)
	at android.os.-$$Lambda$StrictMode$lu9ekkHJ2HMz0jd3F8K8MnhenxQ.accept(Unknown Source:2)
	at java.lang.Class.getDeclaredField(Native Method)
	at com.jme3.util.AndroidBufferAllocator.<clinit>(AndroidBufferAllocator.java:70)
	at java.lang.Class.classForName(Native Method)
	at java.lang.Class.forName(Class.java:454)
	at java.lang.Class.forName(Class.java:379)
	at com.jme3.util.BufferAllocatorFactory.create(BufferAllocatorFactory.java:25)
	at com.jme3.util.BufferUtils.<clinit>(BufferUtils.java:66)
	at com.jme3.util.BufferUtils.createIntBuffer(BufferUtils.java:926)
	at com.jme3.renderer.android.AndroidGL.<init>(AndroidGL.java:47)
	at com.jme3.system.android.OGLESContext.initInThread(OGLESContext.java:205)
	at com.jme3.system.android.OGLESContext.onSurfaceCreated(OGLESContext.java:184)
	at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1560)
	at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1285)

As you can see, it happens in the static intializer of AndroidBufferAllocator when trying to access a field of a non-SDK class (java.nio.ByteBufferAsFloatBuffer):

static {
        for (String className : wrapperClassNames) {
            try {
                Class clazz = Class.forName(className);

                // loop for all possible field names in android
                for (String fieldName : possibleBufferFieldNames) {
                    try {
                        Field field = clazz.getDeclaredField(fieldName); // <-- this is line 70
                        field.setAccessible(true);
                        fieldIndex.put(clazz, field);
                        break;
                    } catch (NoSuchFieldException e) {
                    }
                }
            } catch (ClassNotFoundException ex) {
            }
        }
    }
2 Likes

This is the GitHub issue: Google Play Store Console pre-launch report shows errors due to usage of non-SDK API classes · Issue #1678 · jMonkeyEngine/jmonkeyengine · GitHub

3 Likes

Thanks,

By the way, I might be interested to give the JNI way a try if someone with JNI knowledge is eager to help me. :slightly_smiling_face:

I am considering to publish my game for Android as well so it’s going to be a problem for me too.

2 Likes

You would not find too much helpful resources on android jni or the NDK, so I recommend taking a quick look on this code and/or the resources linked by IBM on the repo readme and good luck !

And this may help too, listings of available native APIs on android (besides stdio and jni.h) :

https://developer.android.com/ndk/reference

How to proceed with this :
First of all, read all the wiki provided by IBM on jni, then try to know which C header file are you going to work with, then read its documentation on IBM and android NDK if available, most likely you will do this without even an example or a sample.

I have not practiced that much on jni to master it yet, I still studying c++ tho, but it really helps when referring back to IBM for c/c++ for Unix (and ofc android is a Unix).

You will likely use Android mk or C-make, I currently use c-make, but for jme3 the current native building tool is android mk :
https://developer.android.com/ndk/guides/android_mk

2 Likes