“This is the ninth day of my participation in the August Gwen Challenge.

Exception handling

Examples of exceptions

Here is an example of an error:

First, we made the following changes in MainActivity:

private String key = "123";

public native void testException();
Copy the code

Then write the following Native methods:

JNIEXPORT void JNICALL Java_com_n0texpecterr0r_ndkdemo_MainActivity_testException(JNIEnv *env, jobject instance) { jclass cls_this = env->GetObjectClass(instance); JfieldID fid = env->GetFieldID(cls_this, "key1", "Ljava/lang/String; ); // An exception is thrown here, and the Java layer can catch Error or Throwable. Android_log_print (ANDROID_LOG_DEBUG, "NDKException", "flag1"); Jstring key = static_cast<jstring>(env->GetObjectField(instance, fid)); char* str = const_cast<char *>(env->GetStringUTFChars(key, NULL)); __android_log_print(ANDROID_LOG_DEBUG, "NDKException", "flag2"); }Copy the code

Run the program, found the program crash. Let’s look at logcat:

/com.n0texpecterr0r.ndkdemo D/NDKException: flag1

You can see that the following code can still execute despite the exception being thrown. However, when we tried to use FID, it caused the program to crash.

Let’s look at the error message in logcat:

JNI DETECTED ERROR IN APPLICATION: JNI GetObjectField called with pending exception java.lang.NoSuchFieldError: no "Ljava/lang/String;" field "key1" in class "Lcom/n0texpecterr0r/ndkdemo/MainActivity;" or its superclasses

      at void com.n0texpecterr0r.ndkdemo.MainActivity.testException() (MainActivity.java:-2)

      at void com.n0texpecterr0r.ndkdemo.MainActivity.onCreate(android.os.Bundle) (MainActivity.java:20)

      ...
Copy the code

As you can see, NoSuchFieldError is raised with the message “no “Ljava/lang/String; field “key1” in class “Lcom/n0texpecterr0r/ndkdemo/MainActivity;” Filed key1 or its superclasses”, makeKey1 is not filed for MainActivity and its parent class

When errors occur in the Native layer, exceptions of the Error type are thrown, which can be captured by Throwable or Error. After the exception is captured, The Java code can continue to execute.

Remedial measures

To ensure the normal execution of Java and Native code, we need to:

  1. Manually clear ExceptionClear in Native layer to ensure that the code can run.
  2. Implement remedies to keep C/C++ code running.

For example:

JNIEXPORT void JNICALL Java_com_n0texpecterr0r_ndkdemo_MainActivity_testException(JNIEnv *env, jobject instance) { jclass cls_this = env->GetObjectClass(instance); JfieldID fid = env->GetFieldID(cls_this, "key1", "Ljava/lang/String; ); jthrowable err = env->ExceptionOccurred(); if (err ! Env ->ExceptionClear(); Cls_this, "key", "Ljava/lang/String;" ); } jstring key = static_cast<jstring>(env->GetObjectField(instance, fid)); char* str = const_cast<char *>(env->GetStringUTFChars(key, NULL)); }Copy the code

Running the program shows that we have successfully remedied the problem and the Java code executes successfully.

Manually throwing an exception

We can also throw exceptions manually using the ThrowNew method. As follows:

if (strcmp(str,"efg") ! = 0) {/ / get exception Class jclass cls_err = env - > FindClass (" Java/lang/IllegalArgumentException "); Env ->ThrowNew(cls_err, "key value is invalid! ); }Copy the code

As you can see, we successfully threw an IllegalArgumentException. You can then catch the exception in Java:

try {

    testException();

}catch (IllegalArgumentException e){

    Log.e("Exception", e.getMessage());

}
Copy the code

Look at Logcat to see the exception we caught:

E/Exception: key value is invalid!

The NDK uses Bitmap objects in Java

The paper

Bitmap is a commonly used image manipulation class in Android development. It contains the width, height, format, and information about each pixel of the image. The NDK provides bitmap.h for us to use the Android bitmap class.

bitmap.h

The NDK provides us with Bitmap headers, i.e. We can start by looking at what information is contained in this header file:

AndroidBitmapInfo

AndroidBitmapInfo is a class that contains information commonly used by Bitmap. It is defined as follows:

// AndroidBitmap_getInfo() to get the typedef struct {// // Uint32_t height; // Uint32_t stride; // Bitmap pixel format int32_t format; Uint32_t flags; // Uint32_t flags; } AndroidBitmapInfo;Copy the code
AndroidBitmapFormat

AndroidBitmapFormat is an enumeration that represents the image format of a Bitmap, corresponding to the Java side. It is defined as follows:

enum AndroidBitmapFormat {

    ANDROID_BITMAP_FORMAT_NONE      = 0,

    ANDROID_BITMAP_FORMAT_RGBA_8888 = 1,

    ANDROID_BITMAP_FORMAT_RGB_565   = 4,

    ANDROID_BITMAP_FORMAT_RGBA_4444 = 7,

    ANDROID_BITMAP_FORMAT_A_8       = 8,

};
Copy the code
Interface return code

This area defines the result of the Bitmap operation, which corresponds to success, error parameters, JNI exceptions, and memory allocation errors. You may notice that there is an exception at the end, which is actually caused by the Google programmer writing the wrong Result when writing the code.

#define ANDROID_BITMAP_RESULT_SUCCESS            0

#define ANDROID_BITMAP_RESULT_BAD_PARAMETER     -1

#define ANDROID_BITMAP_RESULT_JNI_EXCEPTION     -2

#define ANDROID_BITMAP_RESULT_ALLOCATION_FAILED -3


#define ANDROID_BITMAP_RESUT_SUCCESS ANDROID_BITMAP_RESULT_SUCCESS
Copy the code
Bitmap manipulation function declaration

The functions that operate on Bitmap are declared here

  • AndroidBitmap_getInfo: Obtains the current bitmap information.
  • AndroidBitmap_lockPixels: Locks the current Bitmap pixel. The Bitmap object is not recycled during the lock and must be called after it is usedAndroidBitmap_unlockPixelsFunction to unlock the pixel.
  • AndroidBitmap_unlockPixels: Unlocks pixels.
int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap, AndroidBitmapInfo* info);

int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);

int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);
Copy the code
Compilation problems

It is important to note that if we want to use Bitmap, we need to link the Jnigraphics library, otherwise the following error may occur

E:\test\NdkBitmap\app\src\main\cpp/native-lib.cpp:52: undefined reference to AndroidBitmap_getInfo

E:\test\NdkBitmap\app\src\main\cpp/native-lib.cpp:53: undefined reference to AndroidBitmap_getInfo

E:\test\NdkBitmap\app\src\main\cpp/native-lib.cpp:59: undefined reference to AndroidBitmap_lockPixels

E:\test\NdkBitmap\app\src\main\cpp/native-lib.cpp:64: undefined reference to AndroidBitmap_lockPixels

E:\test\NdkBitmap\app\src\main\cpp/native-lib.cpp:86: undefined reference to AndroidBitmap_unlockPixels

E:\test\NdkBitmap\app\src\main\cpp/native-lib.cpp:87: undefined reference to AndroidBitmap_unlockPixels

So we need to add **-ljnigraphics** to cmake

target_link_libraries(native-lib -ljnigraphics ${log-lib})
Copy the code

Dynamic registration

In fact, the way we used to register native methods in Java is called static registration, that is, Java_< package name >_< class name >_< method name > so that the native method can be found. Such function names are very long and difficult to manage, and we can be free from such naming restrictions if we use dynamic registration.

At the same time, we need to be clear that Java class invokes Native methods through VM. When invoking, we need to find Native functions in SO library through VM. If this function needs to be invoked frequently, it will consume a lot of time. Therefore, we can register native functions into VM in JNI_Onload function through dynamic registration to reduce the searching time.

JNI_Onload

Before I introduce dynamic registration

When we call the system.loadLibrary () method, we automatically look up and call a function called JNI_Onload in that library. Its function prototype is as follows:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
}
Copy the code

Since this function is called when JNI is loaded, it has several uses:

  • JNI_Onload notifies the VM of the current VERSION of JNI used by the SO library (the oldest version 1.1 is returned by default)
  • The data is initialized in JNI_Onload
  • Native methods in Java classes are dynamically registered in JNI_Onload.

The Android system loads the JNI dependency library in one of the following ways:

  1. If JNI_Lib defines a JNI_Onload function, pass the JNI_Onload function
  2. If JNI_Lib does not define the JNI_Onload function, DVM calls itdvmResolveNativeMethodDynamic parsing.

Implementation of dynamic registration

We can dynamically registerNativeMethods by calling the registerNativeMethods method in JNI_Onload.

For example, suppose we have a statically registered Native file as follows

#include <string> #include <jni.h> extern "C" JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz ) { return env->NewStringUTF("Hello from JNI !" ); }Copy the code

We can rewrite it as follows to complete the dynamic registration of methods

extern "C" jstring native_hello(JNIEnv* env, jobject thiz ) { return env->NewStringUTF("Hello from JNI !" ); } JNINativeMethod gMethods[] = { {"stringFromJNI", "()Ljava/lang/String;" , (void*)native_hello},// bind}; int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = env->FindClass(className); if (clazz == NULL) { return JNI_FALSE; } if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE; } / * * for all registered local method * / int registerNatives (JNIEnv * env) {const char * kClassName = "com/example/hellojni/hellojni"; Return registerNativeMethods(env, kClassName, gMethods, sizeof(gMethods)/sizeof(gMethods[0])); /* */ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) ! = JNI_OK) { return -1; } assert(env ! = NULL); if (! RegisterNatives (env)) {// Register return -1; } // success result = JNI_VERSION_1_4; return result; }Copy the code

It can be seen that in the above method, we created a method mapping table to map the Native methods in Java to the functions in the Native layer.

JNINativeMethod is a structure defined in JNI

typedef struct {    

    const char* name;          

    const char* signature;  

    void* fnPtr;              

} JNINativeMethod;   
Copy the code

Where name represents the Java function name, signature represents the function signature, and fnPtr is a function pointer pointing to a C function, the corresponding function of a native method.

.so file

We all know that when the NDK is used in a project, it generates so files for our Java code to call. When we write a library in some Java language, it may also use a. So file internally. So the so file is very important to us Android developers, so what is it?

ABI

Android currently supports seven different CPU architectures: ARMv5, ARMv7, x86, MIPS, ARMv8, MIPS64, and X86_64. Each of these CPU architectures is associated with an ABI.

The ABI stands for Application Binary Interface. It defines how binaries (such as.so files) will run on the platform. Everything from the instruction set used to memory alignment to the system libraries available. Each CPU architecture corresponds to an API: Armeabi, ArmeabI-V7A, x86, MIPS, ARM64-V8A, MIPS64, x86_64

What is a.so file

So is an abbreviation for shared object. It is a binary file containing binary code that can be run directly by the machine. Because of this, decompiling the SO library is more difficult than decompiling normal Java code. So is mainly applied in Unix and Linux operating systems. From large operating systems to small dedicated software, SO is indispensable. In fact, Windows has something similar to so, namely the common DLL (dynamic link library), but they are the same thing, just a different name.

Benefits of using SO

  • Let developers maximize the use of existing C and C++ code for reuse, leveraging decades of code excellence in the software world
  • Binary, which does not account for compilation overhead, implements the same functionality faster than a pure Java implementation
  • The memory is not limited by a SINGLE VM application, which can reduce the OOM.

Issues that need attention

Although many devices support more than one ABI (ARM64 and x86 devices can also run armeabI-V7A and Armeabi binaries at the same time, for example), this often involves going through an emulation layer (such as the one x86 devices use to emulate ARM), resulting in a performance loss. Therefore, it is better to provide specific SO files for specific platforms to avoid the simulation layer and get better performance.