Skip to content

Instantly share code, notes, and snippets.

@okamayana
Last active January 15, 2024 23:34
Show Gist options
  • Save okamayana/79c98545eb99c4877979 to your computer and use it in GitHub Desktop.
Save okamayana/79c98545eb99c4877979 to your computer and use it in GitHub Desktop.
Android Studio: Writing and calling JNI functions

Purpose

Writing JNI functions

  1. Create a new Android Studio project. To do so, select File -> New -> New Project.

    • Give the application a name. For example: HelloWorldJni.

    • Set the "Company Domain" entry to be example.com, so that the application package name becomes com.example.helloworldjni.

    • Select Blank Activity in the "Add an activity to Mobile" step. This activity will become your application's "main" activity, and is appropriately named MainActivity.java with layout file activity_main.xml.

  2. In activity_main.xml, change the default TextView to have an android:id with no default android:text. The layout file should look like the following:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingBottom="@dimen/activity_vertical_margin"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                tools:context=".MainActivity">
    
        <TextView
            android:id="@+id/text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    
    </RelativeLayout>
  3. In MainActivity.java, for the sake of simplicity, remove any override methods except for onCreate(Bundle). In a static block inside the class (usually at the top, by convention), add the line System.loadLibrary("helloWorldJni"). This will link the native module with name helloWorldJni with the MainActivity class at runtime, which we will write later. Your MainActivity class should look like the following:

    public class MainActivity extends AppCompatActivity {
    
        static {
            System.loadLibrary("helloWorldJni");
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
  4. Now, in the onCreate(Bundle) method, initialize the TextView from the layout file. We will then set a String onto it, which will be returned by a native JNI method. To do this, declare a native method (usually at the bottom, also by convention) called getNativeString(). This is needed since the compiler still needs to know that such method exists. Then, on the TextView, call its member method setText(String), and pass in the return value of getNativeString(). Your MainActivity class should look like the following:

    public class MainActivity extends AppCompatActivity {
    
        static {
            System.loadLibrary("helloWorldJni");
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            TextView textView = (TextView) findViewById(R.id.text_view);
            textView.setText(getNativeString());
        }
    
        private native String getNativeString();
    }
  5. It's time to write the native method getNativeString() you called above. To do this, create a C++ source file named HelloWorldJni.cpp under src/main/java/jni with the following contents.

    #include <jni.h>
    
    extern "C"
    JNIEXPORT jstring JNICALL Java_com_example_helloworldjni_MainActivity_getNativeString(
            JNIEnv *env, jobject obj) {
        return env->NewStringUTF("Hello World! From native code!");
    }

    There are several points to note about this file:

    • extern "C": Statement to make C++ function names have C linkage. To support function overloading, C++ compilers mangle function names, which means C++ function names are not the same as in C. Without extern "C", your native functions' signatures will not match their declarations in Java (at runtime). Long story short, you need this statement for every method if you are writing native C++ and not C.

    • Method signature: JNI functions need to be named in the following manner:

      JNIEXPORT <RETURN_TYPE> JNICALL Java_<PACKAGE_NAME>_<JAVA_CLASS>_<METHOD_NAME>(
              JNIEnv *env, jobject obj, <METHOD_PARAMETERS>...) {
          ...
      }
      • JNIEXPORT: Contains compiler directives required to ensure the function is exported properly.

      • <RETURN_TYPE>: Return type of the JNI method, usually a native version of a Java type. For example, in the method you just wrote above, you are returning a jstring, which is the native equivalent of String in Java.

      • JNICALL: Contains compiler directives required to ensure the function is treated with the proper JNI calling convention.

      • <JAVA_CLASS>: The connecting Java class this function is tied to. In our example, this would be MainActivity, since that's the Java class that will use this function.

      • <PACKAGE_NAME>: The package name where the previously defined <JAVA_CLASS> resides in. Replace dots (.) with underscores (_).

      • <METHOD_NAME>: This name should be the same as the one you declare inside the connecting <JAVA_CLASS>. In our example, we declared the native method getNativeString(). In this case, <METHOD_NAME> should also be getNativeString().

      • JNIEnv *env: Pointer to a structure (a function table, to be exact) storing all JNI helper function pointers, including the one we call in our example, NewStringUTF(string). To be able to use these functions, you will need to #include <jni.h>.

      • jobject obj: Java object corresponding to the connecting <JAVA_CLASS>.

      • <METHOD_PARAMETERS>...: Comma delimited list of input arguments the native method is supposed to take. In our example, we do not have any input arguments for our getNativeString() function, and is hence left blank.

  6. Ensure that your Android.mk file has correct entries for LOCAL_MODULE and LOCAL_SRC_FILES:

    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE    := helloWorldJni
    LOCAL_SRC_FILES := HelloWorldJni.cpp
    
    include $(BUILD_SHARED_LIBRARY)

And that's it! You should be able to run your app onto a device/emulator as usual.

@tucomel
Copy link

tucomel commented Oct 23, 2019

do you know how to arrange my native methods to group all together inside my java classes?
Im already tried regions, section rule, but android studio arrangement dont understand native keyword
image

@Krammig
Copy link

Krammig commented Jan 15, 2024

Nice article, and helpful.

It would have been nicer though if you buitl this out a bit to show how to pass parameters. I don't many people just want to return a static string :)

If you have the time, please elaborate and show how to pass data to the JNI and to return data back from the .cpp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment