调用 C 函数时 Toast 不显示

Toast not displaying when C function is called

我有一项服务可以使用启动器启动或在启动时运行。问题是当我用 JNI 调用 C 函数时,所有 Toast 通知都不再显示,而如果我只调用 java 方法,Toast 通知显示得很好。是什么原因造成的,我该如何解决?

注: 如果 C 函数做了一些简单的事情,比如回调 java 并终止,Toast 通知将显示。但是,我的函数有一个用于处理中断的无限循环。

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();
    }
}

我的服务

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代码结构

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);
}

编辑: 我已经按照下面的答案更改了代码。看来情况和之前一模一样,可能是我漏掉了什么。

修改后的 C 代码结构

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);
}

好的,所以这个话题需要特别注意。 Android docs 讨论 JavaVM 和 JNIEnv 以及如何与它们交互。您需要做的是存储对 JavaVM 的全局引用,该引用可从 C 代码中的所有线程访问。 JVM 允许线程访问 JNIEnv,这可以让您访问 class 的方法,但不能访问。下一个关键问题是您需要将 jobject 'pointer' 存储到从 C 代码(您可能已经知道)回调的 android 对象。确保在获取 JNIEnv 引用的线程上调用 JNIEnv->DetachThread

又是这样:

  • 获取 JavaVM 引用并将其存储在 C 代码中
  • 已 java 调用 C 代码来存储回调的作业对象引用
  • 启动一个线程进行循环,在该线程上获取对 JNIEnv 的引用以调用 java
  • 在终止线程执行之前调用 DetachThread

另一个麻烦点是从 java 代码访问 c++ 对象,有一个巧妙的技巧,您可以让 c++ 代码 创建 java 对象,并分配给创建的 Java class 上的 private long _cppPointer 成员,然后将此对象添加到您已经指向的 java 对象上的某个集合(例如 activity 或其他东西)

祝你好运。

编辑:我终于想起了another great source关于JNI工作的资料