Android M Bitmap crash - android

I am trying to create a Android Bitmap object(Java) in JNI(C++) through reflection. Below code was working fine until Android L version but not on Android M. In Android M, name of mNativeBitmap is modified to mNativePtr which is taken into consideration. Even after this change code is crashing.
bitmapClazz = env->FindClass("android/graphics/Bitmap");
createBitmapMethod = env->GetStaticMethodID(bitmapClazz, "createBitmap",
"(IILandroid/graphics/Bitmap$Config;)"
"Landroid/graphics/Bitmap;");
#ifdef AND_VER_6_0
jfieldID nativeBitmap = env->GetFieldID(bitmapClazz, "mNativePtr", "J");
#elif AND_VER_5_0
jfieldID nativeBitmap = env->GetFieldID(bitmapClazz, "mNativeBitmap", "J");
#endif
jclass configClazz = env->FindClass("android/graphics/Bitmap$Config");
jmethodID createConfigMethod = env->GetStaticMethodID(configClazz, "nativeToConfig","(I)Landroid/graphics/Bitmap$Config;");
jfieldID RB565FieldId = env->GetStaticFieldID(configClazz, "RGB_565", "Landroid/graphics/Bitmap$Config;");
jobject RGB565Config = env->GetStaticObjectField(configClazz, RB565FieldId);
jobject jBitmap = env->CallStaticObjectMethod( bitmapClazz,
createBitmapMethod,
width,
height,
RGB565Config);
#if defined AND_VER_5_0 || defined AND_VER_6_0
SkBitmap *bitmap =
(SkBitmap *) env->GetLongField(jBitmap, nativeBitmap);
#else
SkBitmap *bitmap =
(SkBitmap *) env->GetIntField(jBitmap, nativeBitmap);
#endif
memcpy((uint8_t*)bitmap->getPixels(),
(uint8_t*)thumbnail_PixelArray, height*width*2);
If I remove the memcpy of bitmap->getPixels() then the code is not crashing. So we suspect some memory corruption due to memcpy call. If anyone has solution to this problem please help me resolve it.

Related

Concatenating byte arrays in Jni android

I am new to JNI and trying to muddle my way through. Please can someone point me in the direction to do the following in JNI. I am a bit out of my depth here, Can JNI handle Byte Arrays. Also were is the best places to find JNI examples.
This is the JAVA code I want to convert
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(key1);
outputStream.write(key2);
byte[] key3 = outputStream.toByteArray();
Thank you in advance
Rob
Here is a literal translation of the Java code you posted:
jclass cls_BAOS = env->FindClass("java/io/ByteArrayOutputStream");
jmethodID ctr_BAOS = env->GetMethodID(cls_BAOS, "<init>", "()V");
jobject baos = env->NewObject(cls_BAOS, ctr_BAOS);
jmethodID mid_BAOS_writeBytes = env->GetMethodID(cls_BAOS, "writeBytes", "([B)V");
env->CallVoidMethod(baos, mid_BAOS_writeBytes, key1);
env->CallVoidMethod(baos, mid_BAOS_writeBytes, key2);
jmethodID mid_BAOS_toByteArray = env->GetMethodID(cls_BAOS, "toByteArray", "()[B");
jbyteArray key3 = (jbytearray)env->CallObjectMethod(baos, mid_BAOS_toByteArray);
And here is an implementation that uses lower-level operations:
jsize key1len = env->GetArrayLength(key1);
jsize key2len = env->GetArrayLength(key2);
jbyteArray key3 = env->NewByteArray(key1+key2);
{
jbyte *key1ptr = env->GetByteArrayElements(key1, nullptr);
env->SetByteArrayRegion(key3, 0, key1len, key1ptr);
env->ReleaseByteArrayElements(key1, key1ptr, JNI_ABORT);
}
{
jbyte *key2ptr = env->GetByteArrayElements(key2, nullptr);
env->SetByteArrayRegion(key3, key1len, key2len, key2ptr);
env->ReleaseByteArrayElements(key2, key2ptr, JNI_ABORT);
}
You could also memcpy both byte arrays to a single C++ array first and then use a single call to SetByteArrayRegion or anything else really, but what's the point? The Java code was simple and readable and all of this JNI code is just adding pointless obfuscation.
Thank you Botje, this is the final code that works.
jclass cls_BAOS = env->FindClass("java/io/ByteArrayOutputStream");
jmethodID ctr_BAOS = env->GetMethodID(cls_BAOS, "<init>", "()V");
jobject baos = env->NewObject(cls_BAOS,ctr_BAOS);
jmethodID mid_BAOS_writeBytes = env->GetMethodID(cls_BAOS, "writeBytes", "([B)V");
env->CallVoidMethod(baos, mid_BAOS_writeBytes, a);
env->CallVoidMethod(baos, mid_BAOS_writeBytes, b);
jmethodID mid_BAOS_toByteArray = env->GetMethodID(cls_BAOS, "toByteArray", "()[B");
jbyteArray key3 = (jbyteArray)env->CallObjectMethod(baos, mid_BAOS_toByteArray);

Error when trying to access nativeLibraryDir

i'm trying to access getPackageManager.getApplicationInfo in jni.
const char* getNativeLibPath(JNIEnv* env, jobject thiz, const char* libraryName, const char* packageName) {
jclass contextClass = env->GetObjectClass(thiz);
jmethodID getPackageManager = env->GetMethodID(contextClass, "getPackageManager", "()Landroid/content/pm/PackageManager;");
jobject instantiatePackageManager = env->CallObjectMethod(thiz, getPackageManager);
jclass packageManagerClass = env->GetObjectClass(instantiatePackageManager);
jmethodID getApplicationInfo = env->GetMethodID(packageManagerClass, "getApplicationInfo", "(Ljava/lang/String;I)Landroid/content/pm/ApplicationInfo;");
jobject instantiateApplicationInfo = env->CallObjectMethod(thiz, getApplicationInfo, packageName, 0);
jclass applicationInfoClass = env->GetObjectClass(instantiateApplicationInfo);
jfieldID nativeLibraryDir = env->GetFieldID(applicationInfoClass, "nativeLibraryDir", "Ljava/lang/String;");
auto string = (jstring) env->GetObjectField(instantiateApplicationInfo, nativeLibraryDir);
const char* returnValue = env->GetStringUTFChars(string, nullptr);
std::string appendedResult = std::string(returnValue) + std::string("/") + std::string(libraryName);
return appendedResult.c_str();
}
This is my code for it. However for some reason i'm getting this error: JNI ERROR (app bug): accessed stale WeakGlobal 0x74eecd21ff (index 1324143135 in a table of size 38) JNI DETECTED ERROR IN APPLICATION: use of deleted weak global reference 0x74eecd21ff
Any help is appreciated!
Your code has at least three problems:
You call getApplicationInfo with a const char * which expects a Java string:
jobject instantiateApplicationInfo = env->CallObjectMethod(instantiatePackageManager, getApplicationInfo, env->NewStringUTF(packageName), 0);
You need to call env->ReleaseStringUTF(returnValue) to release the string on the Java side
You cannot return a const char * like that. Either return the std::string directly, or allocate memory with new char[] and let the caller free it.

Try impl C library on Android but app crashes

I want to add C library to my project. lzfse for decode img via apple algorithm.
I have added c files to project
added CmakeLines file:
externalNativeBuild {
cmake {
path "src/main/lzfse/CMakeLists.txt"
version "3.10.2"
}
}
I have written JNI for decode:
JNIEXPORT jint JNICALL
Java_com_android_Decompressor_decode(
JNIEnv* env, jclass cls, jobject src, jobject dst
) {
uint8_t* src_buffer = (*env)->GetDirectBufferAddress(env,src);
const size_t src_size = (const size_t) (*env)->GetDirectBufferCapacity(env, src);
uint8_t* dst_buffer = (*env)->GetDirectBufferAddress(env,dst);
size_t dst_size = (size_t) (*env)->GetDirectBufferCapacity(env, dst);
jlong test = lzfse_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, NULL);
return (jint) test;
}
then I call from kt decode fun:
val buf = ByteBuffer.wrap(byteArray)
val buf_out = ByteBuffer.allocateDirect(byteArray.size *20)
val size= decode(dstArray = buf_out, srcArray = buf)
But
My app just crashes
2020-02-25 20:12:25.717 28603-28603/com.android A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 28603 (), pid 28603 ()
Where did I lose?
Wrapping a ByteArray does not result in a direct buffer (at least on Android).
You will need to call GetByteArrayElements to get a (possibly copied) pointer.
I have fixed the crash! After some time I have decided to publish the working code.
So, the call native c++ from project:
private val dstArray: ByteBuffer = ByteBuffer.allocateDirect(DESTINATION_BUFFER_CAPACITY)
private val srcArray: ByteBuffer = ByteBuffer.allocateDirect(SRC_BUFFER_CAPACITY)
decode(srcArray.put(byteArray), dstArray)
Here instead of:
val buf = ByteBuffer.wrap(byteArray)
val size= decode(dstArray = buf_out, srcArray = buf)
I allocate a direct buffer via ByteBuffer.allocateDirect and put the source array in buffer srcArray.put(byteArray).
Because as #Botje said in his post :
Wrapping a ByteArray does not result in a direct buffer (at least on
Android).
my JNI:
JNIEXPORT JNICALL
Java_com_android_Decompressor_decode(
JNIEnv *env, jclass cls, jobject src, jobject dst
) {
uint8_t *src_buffer = (*env)->GetDirectBufferAddress(env, src);
const size_t src_size = (const size_t) (*env)->GetDirectBufferCapacity(env, src);
uint8_t *dst_buffer = (*env)->GetDirectBufferAddress(env, dst);
size_t dst_size = (size_t) (*env)->GetDirectBufferCapacity(env, dst);
lzfse_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, NULL);
}
So I almost haven't changed JNI.
But in my first code sample, I have found that all works until the line:
//...
return (jint) test;
The line caused the crash.
In my case, I didn't need return value so I rewrite JNI function without return any values, but I think it possible to find where I have failed with the return value.
Thanks, guys who tried to give some help, I hope my post also can be helpful for someone.

How to set IntField from native jni to Java Android 10

After update my project to support Android 10, a crash produced in the JNI level related to non-SDK interface restrictions in Android 10 :
JNI DETECTED ERROR IN APPLICATION: JNI SetIntField called with pending exception java.lang.NoSuchFieldError: no "I" field "value" in class "Ljava/lang/Integer;" or its superclasses
which come from this part of code :
jclass clazz = (*env)->GetObjectClass(env, outputObj);
jfieldID mi = (*env)->GetFieldID(env, clazz, "value", "I");
(*env)->SetIntField(env, outputObj, mi, pListLen);
To fix that i replace it by :
jclass clazz = (*env)->GetObjectClass(env, outputObj);
jmethodID intValueMethod = (*env)->GetMethodID(env, clazz, "intValue", "()I");
jint result = (*env)->CallIntMethod(env, outputObj,intValueMethod,pListLen);
After that my application doesn't crash and find the integer value correctly, but i want to set the result integer on Java code using the same method SetIntField.
Could you please give me a way or method to set the result on the Java part.
As changing the intger field is not a good practice on JNI part, so i changed the way instead changing it by reflection method
jclass clazz = (*env)->GetObjectClass(env, outputBufLen);
jmethodID value_of = (*env)->GetStaticMethodID(env,clazz, "valueOf", "(I)Ljava/lang/Integer;");
jobject result = (*env)->CallStaticObjectMethod(env,clazz, value_of, pOutBufLen);
I set the result under an int Array and i return it :
int result = (*env)->CallStaticObjectMethod(env,clazz, value_of, pOutBufLen);
jintArray resultArray = (*env)->NewIntArray(env, 2);
jint fill[2];
fill[0] = result;
fill[1] = (jint) pOutBufLen;
(*env)->SetIntArrayRegion(env, result, 0, 2, fill);
return (resultArray);

How to compare two jbyte Arrays in Android NDK

I am new to NDK and learning.
I managed to call the native method from java code but don't know how can I compare two jbyte arrays
here is what I have:
jbyte bytes1[] = {48, -126, 1,4};
jbyte bytes2[] = {48, -126, 1,4};
jclass cls = (*env)->GetObjectClass(env, bytes1);
jmethodID mid = (*env)->GetMethodID(env, cls, "equals", "([B)Z");
jboolean isEqual = (*env)->CallBooleanMethod(env, bytes1, mid, bytes2);
I know above code is wrong but is there a correct way of comparing the byte arrays
Use memcmp if you need to compare two jbyte arrays.
#include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
In your example,
jboolean isEqual = (memcmp(bytes1, bytes2, 4) == 0);

Categories

Resources