Search code examples
javaandroidcjava-native-interfacetoast

Toast not displaying when C function is called


I have a service that can either be started with a launcher or runs on boot. The problem is that when I call a C function with JNI, all the Toast notifications are no longer displayed, whereas if I call only the java method, the Toast notifications display just fine. What causes this and how can I fix it?

Note: If the C function does something simple like calls back to java and terminates, the Toast notifications will display. My function however, has an infinite loop used to handle interrupts.

ServiceLauncher

public class ServiceLauncher extends AppCompatActivity  {
    @Override
    public void onCreate(Bundle savedInstanceState)  {
        Toast.makeText(getBaseContext(), "onCreate", Toast.LENGTH_LONG).show();
        super.onCreate(savedInstanceState);
        startService(new Intent(this, MyService.class));
        finish();
    }
}

MyService

public class MyService extends Service  {
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    public void onDestroy() {
        Toast.makeText(this, "Terminated", Toast.LENGTH_LONG).show();
    }

    @Override
    public int onStartCommand(Intent intent,int flags, int startid)  {
        Toast.makeText(this, "Running", Toast.LENGTH_LONG).show();
        javaMethod(); /*toast displays if this is called*/
        cMethodFromJNI(); /*toast does not display if this is called*/
        return START_STICKY;
    }
}

C code structure

JNIEXPORT jint JNICALL
Java_className_cMethodFromJNI(JNIEnv *env, jclass type) {


    jmethodID mid = (*env)->GetStaticMethodID(env, type, "javaMethod", "(I)V");

   // open and read some fd (not shown)

    for (;;) {
        // wait on change in fd and act on it (not shown)
        callJavaAgain(env,type,mid)
        }
    }
   //close fd (not shown)
    exit(0);
}

EDIT: I've changed the code like so, following the answer below. Seems like the situation is exactly the same as before, perhaps I am missing something.

Revised C code structure

JNIEXPORT jint JNICALL

Java_className_cMethodFromJNI(JNIEnv * env, jclass type) {

    /*cache JVM*/
    int status = ( * env) - > GetJavaVM(env, & jvm);
    if (status != 0) {
        LOGD("failed to retrieve *env");
        exit(1);
    }
    attachedThread();
}

void attachedThread() {
        /* get a new environment and attach a new thread */
        JNIEnv * newEnv;
        JavaVMAttachArgs args;
        args.version = JNI_VERSION_1_6; // choose your JNI version
        args.name = NULL; // if you want to give the java thread a name
        args.group = NULL; // you can assign the java thread to a ThreadGroup
        ( * jvm) - > AttachCurrentThread(jvm, & newEnv, & args);
        jclass cls = ( * newEnv) - > FindClass(newEnv, "fully/qualified/class/name");
        jmethodID mid = ( * newEnv) - > GetStaticMethodID(newEnv, cls, "callJavaAgain", "(V)V");
        // open and read some fd (not shown)

        for (;;) {
            // wait on change in fd and act on it (not shown)
            intermediaryFunction(newEnv, cls, mid);
        }
    }
        //close fd (not shown)
    exit(0);
}

void intermediaryFunction(JNIEnv * newEnv, jclass cls, jmethodID mid) {
    //do some stuff (not shown)
    ( * newEnv) - > CallStaticVoidMethod(newEnv, cls, mid);
}

Solution

  • okay so this topic requires some careful attention. Android docs talk about JavaVM and JNIEnv and how to interface with them. What you will need to do is store a global reference to the JavaVM that's accessible from all threads in the C code. The JVM allows a thread to get access to the JNIEnv which can get you access to a class's methods and what not. The next key problem is that you need to stash a jobject 'pointer' to the android object you call back to from the C code (which you probably already know). Make sure you call JNIEnv->DetachThread on the thread that acquired the JNIEnv reference.

    so again:

    • get JavaVM reference and store it in c code
    • have java call c code to store the jobject reference for your callback
    • start a thread to do looping, acquire a reference to a JNIEnv on that thread to call to java
    • call DetachThread before terminating thread execution

    another point of trouble is accessing c++ objects from java code, there's a neat trick where you can have c++ code create the java objects, and assign into a private long _cppPointer member on the created Java class, then add this object to some collection on a java object you have already pointer to (like an activity or something)

    good luck.

    edit: i finally recalled another great source of information about JNI work